【功能新增】 仿钉钉流程模型增加浏览模式
parent
d477b35f83
commit
137b33e7cf
|
@ -6,6 +6,7 @@
|
|||
v-model:visible="popoverShow"
|
||||
placement="right-start"
|
||||
width="auto"
|
||||
v-if="!readonly"
|
||||
>
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
|
@ -78,6 +79,8 @@ const props = defineProps({
|
|||
|
||||
const emits = defineEmits(['update:childNode'])
|
||||
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
|
||||
const addNode = (type: number) => {
|
||||
popoverShow.value = false
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
/>
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" :flow-node="currentNode" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import StartUserNode from './nodes/StartUserNode.vue'
|
||||
|
|
|
@ -57,7 +57,7 @@ const props = defineProps({
|
|||
required: true
|
||||
}
|
||||
})
|
||||
const loading = ref(true)
|
||||
const loading = ref(false)
|
||||
const formFields = ref<string[]>([])
|
||||
const formType = ref(20)
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
|
@ -66,6 +66,7 @@ const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
|||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const deptTreeOptions = ref()
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
provide('readonly', false)
|
||||
provide('formFields', formFields)
|
||||
provide('formType', formType)
|
||||
provide('roleList', roleOptions)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div class="simple-flow-canvas" v-loading="loading">
|
||||
<div class="simple-flow-container">
|
||||
<div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode } from './consts'
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleProcessRender'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const loading = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
processNodeTree.value = newValue
|
||||
}
|
||||
)
|
||||
const processNodeTree = ref<SimpleFlowNode | undefined>(props.flowNode)
|
||||
provide('readonly', true)
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
// 放大
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
// 缩小
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
|
||||
// onMounted(async () => {
|
||||
// try {
|
||||
// loading.value = true
|
||||
// if (props.view) {
|
||||
// processNodeTree.value = props.view.simpleModel
|
||||
// }
|
||||
// } finally {
|
||||
// loading.value = false
|
||||
// }
|
||||
// })
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
// @ts-ignore
|
||||
import { DictDataVO } from '@/api/system/dict/types'
|
||||
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
|
@ -96,6 +96,8 @@ export interface SimpleFlowNode {
|
|||
conditionGroups?: ConditionGroup
|
||||
// 是否默认的条件
|
||||
defaultFlow?: boolean
|
||||
// 活动的状态,用于前端节点状态展示
|
||||
activityStatus? : TaskStatusEnum
|
||||
}
|
||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||
export enum CandidateStrategy {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
|
||||
import SimpleProcessViewer from './SimpleProcessViewer.vue'
|
||||
import '../theme/simple-process-designer.scss'
|
||||
|
||||
export { SimpleProcessDesigner }
|
||||
export { SimpleProcessDesigner, SimpleProcessViewer }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { cloneDeep } from 'lodash-es'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
|
@ -476,3 +477,26 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
|||
blurEvent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据节点任务状态,获取节点任务状态样式
|
||||
*/
|
||||
export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined) : string {
|
||||
if (!taskStatus) {
|
||||
return ''
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.APPROVE ) {
|
||||
return 'status-pass'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.RUNNING ) {
|
||||
return 'status-running'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.REJECT ) {
|
||||
return 'status-reject'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.CANCEL ) {
|
||||
return 'status-cancel'
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="node-title-container">
|
||||
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
|
@ -24,9 +24,9 @@
|
|||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.COPY_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
</div>
|
||||
<CopyTaskNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<CopyTaskNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
@ -57,7 +57,8 @@ const props = defineProps({
|
|||
const emits = defineEmits<{
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
|
@ -66,6 +67,9 @@ const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.
|
|||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
|
|
|
@ -1,13 +1,27 @@
|
|||
<template>
|
||||
<div class="end-node-wrapper">
|
||||
<div class="end-node-box">
|
||||
<div class="end-node-box" :class="taskStatusClass">
|
||||
<span class="node-fixed-name" title="结束">结束</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode } from '../consts'
|
||||
import { useWatchNode, useTaskStatusClass } from '../node'
|
||||
|
||||
defineOptions({
|
||||
name: 'EndEventNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点任务状态样式
|
||||
const taskStatusClass = useTaskStatusClass(currentNode.value?.activityStatus)
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<el-button class="branch-node-add" color="#67c23a" @click="addCondition" plain>添加条件</el-button>
|
||||
<div v-if="readonly" class="branch-node-readonly" :class="taskStatusClass">
|
||||
<span class="iconfont icon-exclusive icon-size"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#67c23a" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
|
@ -17,9 +23,9 @@
|
|||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !item.showText }">
|
||||
<div class="node-box" :class="[{ 'node-config-error': !item.showText }, `${useTaskStatusClass(item.activityStatus)}`]">
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<div v-if="!readonly && showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="input-max-width editable-title-input"
|
||||
|
@ -39,7 +45,10 @@
|
|||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
|
@ -87,6 +96,7 @@ import NodeHandler from '../NodeHandler.vue'
|
|||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { getDefaultConditionNodeName } from '../utils'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
@ -109,9 +119,11 @@ const emits = defineEmits<{
|
|||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// const conditionNodes = computed(() => currentNode.value.conditionNodes);
|
||||
// 节点状态样式
|
||||
const taskStatusClass = useTaskStatusClass(currentNode.value?.activityStatus)
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
|
@ -135,6 +147,9 @@ const clickEvent = (index: number) => {
|
|||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<el-button class="branch-node-add" color="#345da2" @click="addCondition" plain>添加条件</el-button>
|
||||
<div v-if="readonly" class="branch-node-readonly">
|
||||
<span class="iconfont icon-inclusive icon-size"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#345da2" @click="addCondition" plain>添加条件</el-button>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
|
@ -38,7 +41,7 @@
|
|||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
|
||||
<div class="node-toolbar" v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length">
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
|
@ -50,7 +53,7 @@
|
|||
</div>
|
||||
<div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length"
|
||||
v-if="!readonly && index != 0 && index + 1 !== currentNode.conditionNodes?.length"
|
||||
@click="moveNode(index, -1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-left" />
|
||||
|
@ -58,7 +61,7 @@
|
|||
|
||||
<div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||
v-if="!readonly && currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||
@click="moveNode(index, 1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-right" />
|
||||
|
@ -108,6 +111,8 @@ const emits = defineEmits<{
|
|||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
|
||||
|
@ -133,6 +138,9 @@ const clickEvent = (index: number) => {
|
|||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<el-button class="branch-node-add" color="#626aef" @click="addCondition" plain>添加分支</el-button>
|
||||
<div v-if="readonly" class="branch-node-readonly">
|
||||
<span class="iconfont icon-parallel icon-size"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#626aef" @click="addCondition" plain>添加分支</el-button>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
|
@ -39,7 +42,7 @@
|
|||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
|
@ -49,18 +52,6 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length" @click="moveNode(index, -1)">
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div> -->
|
||||
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||
@click="moveNode(index, 1)">
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div> -->
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
</div>
|
||||
|
@ -106,6 +97,8 @@ const emits = defineEmits<{
|
|||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[{ 'node-config-error': !currentNode.showText }, `${taskStatusClass}`]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon start-user"
|
||||
><span class="iconfont icon-start-user"></span
|
||||
|
@ -26,18 +29,18 @@
|
|||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.START_USER_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
</div>
|
||||
</div>
|
||||
<StartUserNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<StartUserNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import { SimpleFlowNode, NODE_DEFAULT_TEXT, NodeType } from '../consts'
|
||||
import StartUserNodeConfig from '../nodes-config/StartUserNodeConfig.vue'
|
||||
defineOptions({
|
||||
|
@ -49,21 +52,28 @@ const props = defineProps({
|
|||
default: () => null
|
||||
}
|
||||
})
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
// 定义事件,更新父组件。
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点任务状态样式
|
||||
const taskStatusClass = useTaskStatusClass(currentNode.value?.activityStatus)
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[{ 'node-config-error': !currentNode.showText }, `${taskStatusClass}`]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
|
@ -24,9 +27,9 @@
|
|||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
|
@ -45,7 +48,7 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
|
||||
defineOptions({
|
||||
|
@ -61,13 +64,21 @@ const emits = defineEmits<{
|
|||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点状态样式
|
||||
const taskStatusClass = useTaskStatusClass(currentNode.value?.activityStatus)
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
.simple-flow-canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
overflow: auto;
|
||||
background-color: #fafafa;
|
||||
user-select: none;
|
||||
// user-select: none;
|
||||
|
||||
.simple-flow-container {
|
||||
position: relative;
|
||||
|
@ -84,12 +82,32 @@
|
|||
background-color: #fff;
|
||||
flex-direction: column;
|
||||
border: 2px solid transparent;
|
||||
// border-color: #0089ff;
|
||||
border-radius: 8px;
|
||||
// border-color: #0089ff;
|
||||
box-shadow: 0 1px 4px 0 rgba(10, 30, 65, 0.16);
|
||||
transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
|
||||
&.status-pass {
|
||||
border-color: #67c23a;
|
||||
background-color: #a9da90;
|
||||
}
|
||||
&.status-pass:hover {
|
||||
border-color: #67c23a;
|
||||
}
|
||||
&.status-running {
|
||||
border-color: #5a9cf8;
|
||||
background-color: #e7f0fe;
|
||||
}
|
||||
&.status-running:hover {
|
||||
border-color: #5a9cf8;
|
||||
}
|
||||
&.status-reject {
|
||||
border-color: #e47470;
|
||||
background-color: #f6e5e5;
|
||||
}
|
||||
&.status-reject:hover {
|
||||
border-color: #e47470;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #0089ff;
|
||||
.node-toolbar {
|
||||
|
@ -280,15 +298,10 @@
|
|||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
// bottom: 5px;
|
||||
bottom: 0px;
|
||||
top:0;
|
||||
z-index: 0;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
// height: calc(100% - 5px);
|
||||
margin: auto;
|
||||
background-color: #dedede;
|
||||
content: '';
|
||||
|
@ -361,6 +374,36 @@
|
|||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.branch-node-readonly {
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid #dedede;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
transform: translateX(-50%);
|
||||
transform-origin: center center;
|
||||
|
||||
&.status-pass {
|
||||
border-color: #6bb63c;
|
||||
background-color: #e9f4e2;
|
||||
}
|
||||
&.status-pass:hover {
|
||||
border-color: #6bb63c;
|
||||
}
|
||||
|
||||
.icon-size {
|
||||
font-size: 22px;
|
||||
color: #67c23a;
|
||||
}
|
||||
}
|
||||
|
||||
.branch-node-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -454,7 +497,6 @@
|
|||
padding: 3px 4px;
|
||||
color: #212121;
|
||||
cursor: pointer;
|
||||
// background: #2c2c2c;
|
||||
background: #fafafa;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
|
||||
|
@ -473,12 +515,36 @@
|
|||
align-items: center;
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
border: 2px solid #fafafa;
|
||||
color: #212121;
|
||||
// background: #6e6e6e;
|
||||
background: #fafafa;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.status-pass {
|
||||
border-color: #6bb63c;
|
||||
background-color: #a9da90;
|
||||
}
|
||||
|
||||
&.status-pass:hover {
|
||||
border-color: #6bb63c;
|
||||
}
|
||||
|
||||
&.status-reject {
|
||||
border-color: #e47470;
|
||||
background-color: #f6e5e5;
|
||||
}
|
||||
&.status-reject:hover {
|
||||
border-color: #e47470;
|
||||
}
|
||||
|
||||
&.status-cancel {
|
||||
border-color: #919398;
|
||||
background-color: #eaeaeb
|
||||
}
|
||||
&.status-cancel:hover {
|
||||
border-color: #919398;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<SimpleProcessViewer :flow-node="simpleModel" />
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import { BpmProcessInstanceStatus } from '@/utils/constants'
|
||||
import { SimpleFlowNode, NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { SimpleProcessViewer } from '@/components/SimpleProcessDesignerV2/src/'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceSimpleViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
id: propTypes.string // 流程实例的编号
|
||||
})
|
||||
|
||||
// const view = ref({
|
||||
// simpleModel: undefined
|
||||
// }) // simple 模型数据
|
||||
|
||||
const simpleModel = ref()
|
||||
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.loading,
|
||||
async (value) => {
|
||||
if (value && props.id) {
|
||||
const modelView = await ProcessInstanceApi.getProcessInstanceBpmnModelView(props.id)
|
||||
if (modelView) {
|
||||
// 已经拒绝的活动节点编号集合,只包括 UserTask
|
||||
const rejectedTaskActivityIds: string[] = modelView.rejectedTaskActivityIds
|
||||
// 进行中的活动节点编号集合, 只包括 UserTask
|
||||
const unfinishedTaskActivityIds: string[] = modelView.unfinishedTaskActivityIds
|
||||
// 已经完成的活动节点编号集合, 包括 UserTask、Gateway 等
|
||||
const finishedActivityIds: string[] = modelView.finishedTaskActivityIds
|
||||
// 已经完成的连线节点编号集合,只包括 SequenceFlow
|
||||
const finishedSequenceFlowActivityIds: string[] = modelView.finishedSequenceFlowActivityIds
|
||||
setSimpleModelNodeTaskStatus(
|
||||
modelView.simpleModel,
|
||||
modelView.processInstance.status,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
console.log("modelView.simpleModel==>", modelView.simpleModel)
|
||||
simpleModel.value = modelView.simpleModel
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const setSimpleModelNodeTaskStatus = (
|
||||
simpleModel: SimpleFlowNode | undefined,
|
||||
processStatus: number,
|
||||
rejectedTaskActivityIds: string[],
|
||||
unfinishedTaskActivityIds: string[],
|
||||
finishedActivityIds: string[],
|
||||
finishedSequenceFlowActivityIds: string[],
|
||||
|
||||
) => {
|
||||
if (!simpleModel) {
|
||||
return
|
||||
}
|
||||
// 结束节点
|
||||
if (simpleModel.type === NodeType.END_EVENT_NODE) {
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = processStatus
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 审批节点
|
||||
if (
|
||||
simpleModel.type === NodeType.START_USER_NODE ||
|
||||
simpleModel.type === NodeType.USER_TASK_NODE
|
||||
) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
if (rejectedTaskActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.REJECT
|
||||
} else if(unfinishedTaskActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.RUNNING
|
||||
} else if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
}
|
||||
// TODO 是不是还缺一个 cancel 的状态
|
||||
}
|
||||
|
||||
// 抄送节点
|
||||
if (simpleModel.type === NodeType.COPY_TASK_NODE) {
|
||||
// 抄送节点 只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
// 条件节点 对应 SequenceFlow
|
||||
if (simpleModel.type === NodeType.CONDITION_NODE) {
|
||||
// 条件节点。只有通过和未执行状态
|
||||
if (finishedSequenceFlowActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
|
||||
// 网关节点
|
||||
if (
|
||||
simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 网关节点。只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
simpleModel.conditionNodes?.forEach((node) => {
|
||||
setSimpleModelNodeTaskStatus(
|
||||
node,
|
||||
processStatus,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
setSimpleModelNodeTaskStatus(
|
||||
simpleModel.childNode,
|
||||
processStatus,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
}
|
||||
/** 监听 bpmnXml */
|
||||
// watch(
|
||||
// () => props.bpmnXml,
|
||||
// (value) => {
|
||||
// view.value.bpmnXml = value
|
||||
// }
|
||||
// )
|
||||
</script>
|
||||
<style>
|
||||
.box-card {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<SimpleProcessDesigner :model-id="modelId" />
|
||||
<ContentWrap :bodyStyle="{ padding: '0px 0px' }" class="position-relative">
|
||||
<SimpleProcessDesigner :model-id="modelId" />
|
||||
</ContentWrap>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
|
||||
|
|
Loading…
Reference in New Issue