仿钉钉流程设计器- 审批节点配置新增拒绝处理方式
This commit is contained in:
parent
142b0f7203
commit
0e7dbbb04d
@ -1,37 +1,42 @@
|
||||
<template>
|
||||
<div class="node-handler-wrapper">
|
||||
<div class="node-handler" v-if="props.showAdd">
|
||||
<el-popover trigger="hover" v-model:visible="popoverShow" placement="right-start" width="auto">
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
<div class="approve handler-item-icon">
|
||||
<span class="iconfont icon-approve icon-size"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">审批人</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
|
||||
<div class="handler-item-icon copy">
|
||||
<span class="iconfont icon-size icon-copy"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">抄送</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.EXCLUSIVE_NODE)">
|
||||
<div class="handler-item-icon condition">
|
||||
<span class="iconfont icon-size icon-exclusive"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">条件分支</div>
|
||||
<el-popover
|
||||
trigger="hover"
|
||||
v-model:visible="popoverShow"
|
||||
placement="right-start"
|
||||
width="auto"
|
||||
>
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
<div class="approve handler-item-icon">
|
||||
<span class="iconfont icon-approve icon-size"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">审批人</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||
</template>
|
||||
<div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
|
||||
<div class="handler-item-icon copy">
|
||||
<span class="iconfont icon-size icon-copy"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">抄送</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.EXCLUSIVE_NODE)">
|
||||
<div class="handler-item-icon condition">
|
||||
<span class="iconfont icon-size icon-exclusive"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">条件分支</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_NAME, ApproveMethodType, CandidateStrategy } from './consts'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_NAME, ApproveMethodType, RejectHandlerType, CandidateStrategy } from './consts'
|
||||
import { generateUUID } from '@/utils'
|
||||
defineOptions({
|
||||
name: 'NodeHandler'
|
||||
@ -71,6 +76,9 @@ const addNode = (type: number) => {
|
||||
// 超时处理
|
||||
timeoutHandler: {
|
||||
enable: false
|
||||
},
|
||||
rejectHandler: {
|
||||
type: RejectHandlerType.TERMINATION
|
||||
}
|
||||
},
|
||||
childNode: props.childNode
|
||||
|
@ -1,58 +1,103 @@
|
||||
<template>
|
||||
<!-- 开始节点 -->
|
||||
<StartEventNode
|
||||
v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE"
|
||||
:flow-node ="currentNode"
|
||||
@update:model-value="handleModelValueUpdate" />
|
||||
v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
/>
|
||||
<!-- 审批节点 -->
|
||||
<UserTaskNode
|
||||
v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
|
||||
:flow-node ="currentNode" @update:model-value="handleModelValueUpdate"/>
|
||||
<UserTaskNode
|
||||
v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 抄送节点 -->
|
||||
<CopyTaskNode
|
||||
v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
|
||||
:flow-node ="currentNode" @update:model-value="handleModelValueUpdate"/>
|
||||
v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
/>
|
||||
<!-- 条件节点 -->
|
||||
<ExclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.EXCLUSIVE_NODE"
|
||||
:flow-node ="currentNode" @update:model-value="handleModelValueUpdate"/>
|
||||
<ExclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.EXCLUSIVE_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 递归显示孩子节点 -->
|
||||
<ProcessNodeTree v-if="currentNode && currentNode.childNode" v-model:flow-node="currentNode.childNode"/>
|
||||
<ProcessNodeTree
|
||||
v-if="currentNode && currentNode.childNode"
|
||||
v-model:flow-node="currentNode.childNode"
|
||||
:parent-node= "currentNode"
|
||||
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||
/>
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"/>
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import StartEventNode from './nodes/StartEventNode.vue';
|
||||
import EndEventNode from './nodes/EndEventNode.vue';
|
||||
import UserTaskNode from './nodes/UserTaskNode.vue';
|
||||
import CopyTaskNode from './nodes/CopyTaskNode.vue';
|
||||
import ExclusiveNode from './nodes/ExclusiveNode.vue';
|
||||
import { SimpleFlowNode, NodeType } from './consts';
|
||||
<script setup lang="ts">
|
||||
import StartEventNode from './nodes/StartEventNode.vue'
|
||||
import EndEventNode from './nodes/EndEventNode.vue'
|
||||
import UserTaskNode from './nodes/UserTaskNode.vue'
|
||||
import CopyTaskNode from './nodes/CopyTaskNode.vue'
|
||||
import ExclusiveNode from './nodes/ExclusiveNode.vue'
|
||||
import { SimpleFlowNode, NodeType } from './consts'
|
||||
defineOptions({
|
||||
name: 'ProcessNodeTree'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode : {
|
||||
parentNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: () => null
|
||||
default: () => null
|
||||
},
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['update:flowNode'])
|
||||
const emits = defineEmits<{
|
||||
'update:flowNode',
|
||||
'find:recursiveFindParentNode': [nodeList: SimpleFlowNode[], curentNode: SimpleFlowNode, nodeType: number]
|
||||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode);
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
|
||||
// 重要:监控节点变化. 重新绘制节点
|
||||
watch(() => props.flowNode, (newValue) => {
|
||||
currentNode.value = newValue;
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
currentNode.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
const handleModelValueUpdate = (updateValue) => {
|
||||
console.log('Process Node Tree handleModelValueUpdate', updateValue)
|
||||
emits('update:flowNode', updateValue);
|
||||
}
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
emits('update:flowNode', updateValue)
|
||||
}
|
||||
|
||||
</style>
|
||||
const findFromParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
nodeType: number
|
||||
) => {
|
||||
emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
|
||||
}
|
||||
|
||||
// 递归从父节点中查询匹配的节点
|
||||
const recursiveFindParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
findNode: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!findNode || findNode.type === NodeType.START_EVENT_NODE) {
|
||||
return
|
||||
}
|
||||
|
||||
if (findNode.type === nodeType) {
|
||||
nodeList.push(findNode)
|
||||
}
|
||||
emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
@ -55,6 +55,18 @@ const processNodeTree = ref<SimpleFlowNode>({
|
||||
}
|
||||
})
|
||||
|
||||
// const rootNode = ref<SimpleFlowNode>({
|
||||
// name: '开始',
|
||||
// type: NodeType.START_EVENT_NODE,
|
||||
// id: 'StartEvent_1'
|
||||
// })
|
||||
|
||||
// const childNode = ref<SimpleFlowNode>({
|
||||
// id: 'EndEvent_1',
|
||||
// name: '结束',
|
||||
// type: NodeType.END_EVENT_NODE
|
||||
// })
|
||||
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
const saveSimpleFlowModel = async () => {
|
||||
@ -148,6 +160,8 @@ onMounted(async () => {
|
||||
if (result) {
|
||||
console.log('the result is ', result)
|
||||
processNodeTree.value = result
|
||||
// rootNode.value = result
|
||||
// childNode.value = result.childNode
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -62,6 +62,17 @@ export enum TimeUnitType {
|
||||
DAY = 3
|
||||
}
|
||||
|
||||
export enum RejectHandlerType {
|
||||
/**
|
||||
* 结束流程
|
||||
*/
|
||||
TERMINATION = 1,
|
||||
/**
|
||||
* 驳回到指定节点
|
||||
*/
|
||||
RETURN_PRE_USER_TASK = 2
|
||||
}
|
||||
|
||||
// 条件配置类型 ( 用于条件节点配置 )
|
||||
export enum ConditionConfigType {
|
||||
|
||||
@ -186,12 +197,6 @@ NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
|
||||
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
|
||||
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
|
||||
|
||||
export const TIME_UNIT_MAP = new Map<number,string>()
|
||||
NODE_DEFAULT_NAME.set(1, 'M')
|
||||
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
|
||||
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
|
||||
|
||||
|
||||
export const APPROVE_METHODS: DictDataVO [] = [
|
||||
{ label: '单人审批', value: 1 },
|
||||
{ label: '多人会签(需所有审批人同意)', value: 2 },
|
||||
@ -216,6 +221,10 @@ export const TIMEOUT_HANDLER_ACTION_TYPES: DictDataVO [] = [
|
||||
{ label: '自动同意', value: 2 },
|
||||
{ label: '自动拒绝', value: 3 },
|
||||
]
|
||||
export const REJECT_HANDLER_TYPES: DictDataVO [] = [
|
||||
{ label: '结束流程', value: RejectHandlerType.TERMINATION },
|
||||
{ label: '驳回到指定节点', value: RejectHandlerType.RETURN_PRE_USER_TASK }
|
||||
]
|
||||
|
||||
// 比较运算符
|
||||
export const COMPARISON_OPERATORS : DictDataVO = [
|
||||
|
@ -131,7 +131,6 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="currentNode.attributes.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
@ -144,7 +143,6 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批方式" prop="approveMethod">
|
||||
<el-radio-group v-model="currentNode.attributes.approveMethod">
|
||||
<div class="flex-col">
|
||||
@ -163,8 +161,35 @@
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="超时处理" prop="timeoutHandlerEnable">
|
||||
<el-divider content-position="left">审批人拒绝时</el-divider>
|
||||
<el-form-item label="处理方式" prop="rejectHandler">
|
||||
<el-radio-group v-model="currentNode.attributes.rejectHandler.type" @change="rejectHandlerTypeChange">
|
||||
<el-radio
|
||||
:border="true"
|
||||
v-for="item in REJECT_HANDLER_TYPES"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="currentNode.attributes.rejectHandler.type == RejectHandlerType.RETURN_PRE_USER_TASK"
|
||||
label="驳回节点"
|
||||
prop="rejectHandlerNode"
|
||||
>
|
||||
<el-select v-model="currentNode.attributes.rejectHandler.returnNodeId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in returnTaskList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">审批人超时未处理时</el-divider>
|
||||
<el-form-item label="启用开关" prop="timeoutHandlerEnable">
|
||||
<el-switch
|
||||
v-model="currentNode.attributes.timeoutHandler.enable"
|
||||
active-text="开启"
|
||||
@ -281,8 +306,10 @@ import {
|
||||
NodeType,
|
||||
ApproveMethodType,
|
||||
TimeUnitType,
|
||||
RejectHandlerType,
|
||||
TIMEOUT_HANDLER_ACTION_TYPES,
|
||||
TIME_UNIT_TYPES,
|
||||
REJECT_HANDLER_TYPES,
|
||||
NODE_DEFAULT_NAME
|
||||
} from '../consts'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
@ -303,6 +330,9 @@ const props = defineProps({
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emits = defineEmits<{
|
||||
'find:returnTaskNodes': [nodeList: SimpleFlowNode[]]
|
||||
}>()
|
||||
|
||||
const notAllowedMultiApprovers = ref(false)
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
@ -316,7 +346,7 @@ const deptTreeOptions = inject('deptTree') // 部门树
|
||||
const formType = inject('formType') // 表单类型
|
||||
const formFields = inject<Ref<string[]>>('formFields')
|
||||
const candidateParamArray = ref<any[]>([])
|
||||
|
||||
const returnTaskList = ref<SimpleFlowNode[]>([])
|
||||
const closeDrawer = () => {
|
||||
settingVisible.value = false
|
||||
}
|
||||
@ -443,6 +473,10 @@ const setCurrentNode = (node: SimpleFlowNode) => {
|
||||
timeDuration.value = parseInt(parseTime)
|
||||
timeUnit.value = convertTimeUnit(parseTimeUnit)
|
||||
}
|
||||
// 查找可以驳回的用户节点
|
||||
const matchNodeList = [];
|
||||
emits('find:returnTaskNodes', matchNodeList);
|
||||
returnTaskList.value = matchNodeList;
|
||||
}
|
||||
|
||||
defineExpose({ open, setCurrentNode }) // 暴露方法给父组件
|
||||
@ -483,6 +517,12 @@ const blurEvent = () => {
|
||||
currentNode.value.name =
|
||||
currentNode.value.name || (NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string)
|
||||
}
|
||||
const rejectHandlerTypeChange = () => {
|
||||
if (currentNode.value.attributes?.rejectHandler.type === RejectHandlerType.RETURN_PRE_USER_TASK) {
|
||||
|
||||
console.log('nodeList is {}', returnTaskList.value);
|
||||
}
|
||||
}
|
||||
// 默认 6小时
|
||||
const timeDuration = ref(6)
|
||||
const timeUnit = ref(TimeUnitType.HOUR)
|
||||
|
@ -57,7 +57,11 @@
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
<!-- 递归显示子节点 -->
|
||||
<ProcessNodeTree v-if="item && item.childNode" v-model:flow-node="item.childNode" />
|
||||
<ProcessNodeTree
|
||||
v-if="item && item.childNode"
|
||||
:parent-node="item"
|
||||
v-model:flow-node="item.childNode"
|
||||
@find:recursive-find-parent-node="recursiveFindParentNode"/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
@ -76,6 +80,10 @@ defineOptions({
|
||||
name: 'ExclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
// parentNode : {
|
||||
// type: Object as () => SimpleFlowNode,
|
||||
// required: true
|
||||
// },
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
@ -83,7 +91,9 @@ const props = defineProps({
|
||||
})
|
||||
// 定义事件,更新父组件
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined],
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number],
|
||||
'find:recursiveFindParentNode': [nodeList: SimpleFlowNode[], curentNode: SimpleFlowNode, nodeType: number]
|
||||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
@ -156,7 +166,21 @@ const moveNode = (index: number, to: number) => {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 递归从父节点中查询匹配的节点
|
||||
const recursiveFindParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
nodeList.push(node)
|
||||
}
|
||||
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.EXCLUSIVE_NODE) 继续查找
|
||||
emits('find:parentNode', nodeList, nodeType)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -38,6 +38,7 @@
|
||||
v-if="currentNode"
|
||||
ref="nodeSetting"
|
||||
:flow-node="currentNode"
|
||||
@find:return-task-nodes="findReturnTaskNodes"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@ -55,7 +56,8 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined],
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
|
||||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
@ -106,5 +108,12 @@ const copyNode = () => {
|
||||
currentNode.value = newCopyNode
|
||||
emits('update:modelValue', currentNode.value)
|
||||
}
|
||||
// 查找可以驳回用户节点
|
||||
const findReturnTaskNodes = (
|
||||
matchNodeList: SimpleFlowNode[], // 匹配的节点
|
||||
) => {
|
||||
// 从父节点查找
|
||||
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
Loading…
Reference in New Issue
Block a user