Pre Merge pull request !707 from Lemon/feature/bpm

pull/707/MERGE
Lemon 2025-02-22 08:23:24 +00:00 committed by Gitee
commit ec9c06fd7a
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
8 changed files with 320 additions and 34 deletions

View File

@ -75,6 +75,7 @@
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
//
import StartUserNode from './nodes/StartUserNode.vue' import StartUserNode from './nodes/StartUserNode.vue'
import EndEventNode from './nodes/EndEventNode.vue' import EndEventNode from './nodes/EndEventNode.vue'
import UserTaskNode from './nodes/UserTaskNode.vue' import UserTaskNode from './nodes/UserTaskNode.vue'
@ -85,30 +86,43 @@ import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue' import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouterNode from './nodes/RouterNode.vue' import RouterNode from './nodes/RouterNode.vue'
import TriggerNode from './nodes/TriggerNode.vue' import TriggerNode from './nodes/TriggerNode.vue'
//
import { SimpleFlowNode, NodeType } from './consts' import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
//
defineOptions({ defineOptions({
name: 'ProcessNodeTree' name: 'ProcessNodeTree'
}) })
// props
const props = defineProps({ const props = defineProps({
//
parentNode: { parentNode: {
type: Object as () => SimpleFlowNode, type: Object as () => SimpleFlowNode,
default: () => null default: () => null
}, },
//
flowNode: { flowNode: {
type: Object as () => SimpleFlowNode, type: Object as () => SimpleFlowNode,
default: () => null default: () => null
} }
}) })
//
const emits = defineEmits<{ const emits = defineEmits<{
//
'update:flowNode': [node: SimpleFlowNode | undefined] 'update:flowNode': [node: SimpleFlowNode | undefined]
//
'find:recursiveFindParentNode': [ 'find:recursiveFindParentNode': [
nodeList: SimpleFlowNode[], nodeList: SimpleFlowNode[], //
curentNode: SimpleFlowNode, curentNode: SimpleFlowNode, //
nodeType: number nodeType: number //
] ]
}>() }>()
// 使 useWatchNode hook
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// //
@ -116,27 +130,33 @@ const handleModelValueUpdate = (updateValue) => {
emits('update:flowNode', updateValue) emits('update:flowNode', updateValue)
} }
//
const findFromParentNode = (nodeList: SimpleFlowNode[], nodeType: number) => { const findFromParentNode = (nodeList: SimpleFlowNode[], nodeType: number) => {
emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType) emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
} }
// //
const recursiveFindParentNode = ( const recursiveFindParentNode = (
nodeList: SimpleFlowNode[], nodeList: SimpleFlowNode[], //
findNode: SimpleFlowNode, findNode: SimpleFlowNode, //
nodeType: number nodeType: number //
) => { ) => {
//
if (!findNode) { if (!findNode) {
return return
} }
//
if (findNode.type === NodeType.START_USER_NODE) { if (findNode.type === NodeType.START_USER_NODE) {
nodeList.push(findNode) nodeList.push(findNode)
return return
} }
//
if (findNode.type === nodeType) { if (findNode.type === nodeType) {
nodeList.push(findNode) nodeList.push(findNode)
} }
//
emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType) emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
} }
</script> </script>

View File

@ -233,6 +233,8 @@ export type TimeoutHandler = {
timeDuration?: string timeDuration?: string
// 执行动作是自动提醒, 最大提醒次数 // 执行动作是自动提醒, 最大提醒次数
maxRemindCount?: number maxRemindCount?: number
// 执行动作是自动跳转时跳转的节点ID
returnNodeId?: string
} }
/** /**
@ -291,6 +293,16 @@ export enum RejectHandlerType {
*/ */
RETURN_USER_TASK = 2 RETURN_USER_TASK = 2
} }
export enum DelaySettingType {
/**
*
*/
FINISH_PROCESS = 1,
/**
*
*/
RETURN_USER_TASK = 2
}
// 用户任务超时处理类型枚举 // 用户任务超时处理类型枚举
export enum TimeoutHandlerType { export enum TimeoutHandlerType {
/** /**
@ -304,7 +316,11 @@ export enum TimeoutHandlerType {
/** /**
* *
*/ */
REJECT = 3 REJECT = 3,
/**
*
*/
AUTO_JUMP = 4
} }
// 用户任务的审批人为空时,处理类型枚举 // 用户任务的审批人为空时,处理类型枚举
export enum AssignEmptyHandlerType { export enum AssignEmptyHandlerType {
@ -570,7 +586,8 @@ export const TIME_UNIT_TYPES: DictDataVO[] = [
export const TIMEOUT_HANDLER_TYPES: DictDataVO[] = [ export const TIMEOUT_HANDLER_TYPES: DictDataVO[] = [
{ label: '自动提醒', value: 1 }, { label: '自动提醒', value: 1 },
{ label: '自动同意', value: 2 }, { label: '自动同意', value: 2 },
{ label: '自动拒绝', value: 3 } { label: '自动拒绝', value: 3 },
{ label: '自动跳转', value: 4 }
] ]
export const REJECT_HANDLER_TYPES: DictDataVO[] = [ export const REJECT_HANDLER_TYPES: DictDataVO[] = [
{ label: '终止流程', value: RejectHandlerType.FINISH_PROCESS }, { label: '终止流程', value: RejectHandlerType.FINISH_PROCESS },
@ -700,6 +717,8 @@ export type DelaySetting = {
delayType: number delayType: number
// 延迟时间表达式 // 延迟时间表达式
delayTime: string delayTime: string
autoJumpEnable?: boolean
returnNodeId?: string
} }
/** /**
* *

View File

@ -156,6 +156,7 @@ export type UserTaskFormType = {
assignStartUserHandlerType?: AssignStartUserHandlerType assignStartUserHandlerType?: AssignStartUserHandlerType
timeDuration?: number timeDuration?: number
maxRemindCount?: number maxRemindCount?: number
timeoutReturnNodeId?: string
buttonsSetting: any[] buttonsSetting: any[]
taskCreateListenerEnable?: boolean taskCreateListenerEnable?: boolean
taskCreateListenerPath?: string taskCreateListenerPath?: string

View File

@ -63,9 +63,43 @@ const props = defineProps({
}) })
const settingVisible = ref(false) const settingVisible = ref(false)
const currentNode = ref<SimpleFlowNode>(props.conditionNode) const currentNode = ref<SimpleFlowNode>(props.conditionNode)
const condition = ref<any>() const condition = ref<any>({
conditionType: ConditionType.RULE, //
conditionExpression: '',
conditionGroups: {
and: true,
conditions: [{
and: true,
rules: [{
opCode: '==',
leftSide: '',
rightSide: ''
}]
}]
}
})
const open = () => { const open = () => {
condition.value = currentNode.value.conditionSetting // 使使
if (currentNode.value.conditionSetting) {
condition.value = JSON.parse(JSON.stringify(currentNode.value.conditionSetting))
} else {
//
condition.value = {
conditionType: ConditionType.RULE,
conditionExpression: '',
conditionGroups: {
and: true,
conditions: [{
and: true,
rules: [{
opCode: '==',
leftSide: '',
rightSide: ''
}]
}]
}
}
}
settingVisible.value = true settingVisible.value = true
} }
@ -123,15 +157,13 @@ const saveConfig = async () => {
return false return false
} }
currentNode.value.showText = showText currentNode.value.showText = showText
currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType // ,
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) { currentNode.value.conditionSetting = JSON.parse(JSON.stringify({
currentNode.value.conditionSetting.conditionGroups = undefined ...currentNode.value.conditionSetting,
currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression conditionType: condition.value?.conditionType,
} conditionExpression: condition.value?.conditionType === ConditionType.EXPRESSION ? condition.value?.conditionExpression : undefined,
if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) { conditionGroups: condition.value?.conditionType === ConditionType.RULE ? condition.value?.conditionGroups : undefined
currentNode.value.conditionSetting!.conditionExpression = undefined }))
currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups
}
} }
settingVisible.value = false settingVisible.value = false
return true return true

View File

@ -65,6 +65,36 @@
/> />
<el-text>后进入下一节点</el-text> <el-text>后进入下一节点</el-text>
</el-form-item> </el-form-item>
<el-divider content-position="left">延迟结束后</el-divider>
<el-form-item label="自动跳转" prop="autoJumpEnable">
<el-switch
v-model="configForm.autoJumpEnable"
active-text="开启"
inactive-text="关闭"
@change="autoJumpChange"
/>
</el-form-item>
<el-form-item
v-if="!configForm.autoJumpEnable"
label="跳转节点"
prop="returnNodeId"
>
<el-select
filterable
v-model="configForm.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-form> </el-form>
</div> </div>
<template #footer> <template #footer>
@ -104,19 +134,44 @@ const currentNode = useWatchNode(props)
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.DELAY_TIMER_NODE) const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.DELAY_TIMER_NODE)
// //
const formRef = ref() // Ref const formRef = ref() // Ref
const emits = defineEmits<{
'find:return-task-nodes': [nodeList: SimpleFlowNode[]]
}>()
// //
const formRules = reactive({ const formRules = reactive({
delayType: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }], delayType: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
timeDuration: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }], timeDuration: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
dateTime: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }] dateTime: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
returnNodeId: [{ required: true, message: '跳转节点不能为空', trigger: 'change' }]
}) })
// //
const configForm = ref({ const configForm = ref({
delayType: DelayTypeEnum.FIXED_TIME_DURATION, delayType: DelayTypeEnum.FIXED_TIME_DURATION,
timeDuration: 1, timeDuration: 1,
timeUnit: TimeUnitType.HOUR, timeUnit: TimeUnitType.HOUR,
dateTime: '' dateTime: '',
autoJumpEnable: true, //
returnNodeId: undefined as string | undefined // Change type to string | undefined
}) })
//
const returnTaskList = ref<SimpleFlowNode[]>([])
//
const autoJumpChange = () => {
if (!configForm.value.autoJumpEnable) {
//
const matchNodeList: SimpleFlowNode[] = []
emits('find:return-task-nodes', matchNodeList)
returnTaskList.value = matchNodeList
} else {
//
configForm.value.returnNodeId = undefined
}
}
// //
const saveConfig = async () => { const saveConfig = async () => {
if (!formRef) return false if (!formRef) return false
@ -129,13 +184,17 @@ const saveConfig = async () => {
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) { if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
currentNode.value.delaySetting = { currentNode.value.delaySetting = {
delayType: configForm.value.delayType, delayType: configForm.value.delayType,
delayTime: getIsoTimeDuration() delayTime: getIsoTimeDuration(),
autoJumpEnable: configForm.value.autoJumpEnable,
returnNodeId: !configForm.value.autoJumpEnable ? configForm.value.returnNodeId || undefined : undefined
} }
} }
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) { if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
currentNode.value.delaySetting = { currentNode.value.delaySetting = {
delayType: configForm.value.delayType, delayType: configForm.value.delayType,
delayTime: configForm.value.dateTime delayTime: configForm.value.dateTime,
autoJumpEnable: configForm.value.autoJumpEnable,
returnNodeId: !configForm.value.autoJumpEnable ? configForm.value.returnNodeId || undefined : undefined
} }
} }
settingVisible.value = false settingVisible.value = false
@ -169,6 +228,16 @@ const showDelayTimerNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name nodeName.value = node.name
if (node.delaySetting) { if (node.delaySetting) {
configForm.value.delayType = node.delaySetting.delayType configForm.value.delayType = node.delaySetting.delayType
configForm.value.autoJumpEnable = node.delaySetting.autoJumpEnable ?? true
//
const matchNodeList: SimpleFlowNode[] = []
emits('find:return-task-nodes', matchNodeList)
returnTaskList.value = matchNodeList
//
configForm.value.returnNodeId = node.delaySetting.returnNodeId
// //
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) { if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
const strTimeDuration = node.delaySetting.delayTime const strTimeDuration = node.delaySetting.delayTime

View File

@ -347,6 +347,25 @@
> >
<el-input-number v-model="configForm.maxRemindCount" :min="1" :max="10" /> <el-input-number v-model="configForm.maxRemindCount" :min="1" :max="10" />
</el-form-item> </el-form-item>
<el-form-item
v-if="configForm.timeoutHandlerEnable && configForm.timeoutHandlerType === TimeoutHandlerType.AUTO_JUMP"
label="跳转节点"
prop="timeoutReturnNodeId"
>
<el-select
filterable
v-model="configForm.timeoutReturnNodeId"
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>
</div> </div>
<el-divider content-position="left">{{ nodeTypeName }}人为空时</el-divider> <el-divider content-position="left">{{ nodeTypeName }}人为空时</el-divider>
@ -729,7 +748,9 @@ const saveConfig = async () => {
enable: configForm.value.timeoutHandlerEnable!, enable: configForm.value.timeoutHandlerEnable!,
type: cTimeoutType.value, type: cTimeoutType.value,
timeDuration: isoTimeDuration.value, timeDuration: isoTimeDuration.value,
maxRemindCount: cTimeoutMaxRemindCount.value maxRemindCount: cTimeoutMaxRemindCount.value,
returnNodeId: configForm.value.timeoutHandlerType === TimeoutHandlerType.AUTO_JUMP ?
configForm.value.timeoutReturnNodeId : undefined
} }
// //
currentNode.value.assignEmptyHandler = { currentNode.value.assignEmptyHandler = {
@ -803,15 +824,24 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
returnTaskList.value = matchNodeList returnTaskList.value = matchNodeList
// 2.4 // 2.4
configForm.value.timeoutHandlerEnable = node.timeoutHandler?.enable configForm.value.timeoutHandlerEnable = node.timeoutHandler?.enable
if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) { if (node.timeoutHandler?.enable) {
const strTimeDuration = node.timeoutHandler.timeDuration configForm.value.timeoutHandlerType = node.timeoutHandler.type
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1) configForm.value.timeoutReturnNodeId = node.timeoutHandler.returnNodeId
let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
configForm.value.timeDuration = parseInt(parseTime) if (node.timeoutHandler.timeDuration) {
timeUnit.value = convertTimeUnit(parseTimeUnit) const strTimeDuration = node.timeoutHandler.timeDuration
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
configForm.value.timeDuration = parseInt(parseTime)
timeUnit.value = convertTimeUnit(parseTimeUnit)
}
if (node.timeoutHandler.type === TimeoutHandlerType.AUTO_JUMP) {
const matchNodeList: SimpleFlowNode[] = []
emits('find:returnTaskNodes', matchNodeList)
returnTaskList.value = matchNodeList
}
} }
configForm.value.timeoutHandlerType = node.timeoutHandler?.type
configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount
// 2.5 // 2.5
configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type
configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds
@ -902,6 +932,11 @@ function useTimeoutHandler() {
const timeoutHandlerTypeChanged = () => { const timeoutHandlerTypeChanged = () => {
if (configForm.value.timeoutHandlerType === TimeoutHandlerType.REMINDER) { if (configForm.value.timeoutHandlerType === TimeoutHandlerType.REMINDER) {
configForm.value.maxRemindCount = 1 // 1 configForm.value.maxRemindCount = 1 // 1
} else if (configForm.value.timeoutHandlerType === TimeoutHandlerType.AUTO_JUMP) {
//
const matchNodeList: SimpleFlowNode[] = []
emits('find:returnTaskNodes', matchNodeList)
returnTaskList.value = matchNodeList
} }
} }

View File

@ -50,6 +50,7 @@
v-if="!readonly && currentNode" v-if="!readonly && currentNode"
ref="nodeSetting" ref="nodeSetting"
:flow-node="currentNode" :flow-node="currentNode"
@find:return-task-nodes="findReturnTaskNodes"
/> />
</div> </div>
</template> </template>
@ -65,11 +66,16 @@ const props = defineProps({
flowNode: { flowNode: {
type: Object as () => SimpleFlowNode, type: Object as () => SimpleFlowNode,
required: true required: true
},
parentNode: {
type: Object as () => SimpleFlowNode,
default: null
} }
}) })
// //
const emits = defineEmits<{ const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined] 'update:flowNode': [node: SimpleFlowNode | undefined]
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
}>() }>()
// //
const readonly = inject<Boolean>('readonly') const readonly = inject<Boolean>('readonly')
@ -92,6 +98,76 @@ const openNodeConfig = () => {
const deleteNode = () => { const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode) emits('update:flowNode', currentNode.value.childNode)
} }
//
const findReturnTaskNodes = (matchNodeList: SimpleFlowNode[]) => {
//
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
//
const rootNode = findRootNode(currentNode.value)
if (rootNode.type === NodeType.USER_TASK_NODE) {
//
if (rootNode.childNode) {
findAllUserTaskNodes(rootNode.childNode, matchNodeList)
}
} else {
//
findAllUserTaskNodes(rootNode, matchNodeList)
}
//
const currentNodeId = currentNode.value.id
const filteredList = matchNodeList.filter(node => node.id !== currentNodeId)
matchNodeList.length = 0
matchNodeList.push(...filteredList)
}
//
const findRootNode = (node: SimpleFlowNode): SimpleFlowNode => {
let parent: SimpleFlowNode | null = props.parentNode
let current: SimpleFlowNode = node
while (parent !== null) {
current = parent
parent = findParentNode(current)
}
return current
}
//
const findParentNode = (node: SimpleFlowNode): SimpleFlowNode | null => {
if (!props.parentNode) return null
if (props.parentNode.childNode === node) return props.parentNode
return null
}
//
const findAllUserTaskNodes = (node: SimpleFlowNode, matchNodeList: SimpleFlowNode[]) => {
if (!node) return
//
if (node.type === NodeType.USER_TASK_NODE) {
if (!matchNodeList.some(n => n.id === node.id)) {
matchNodeList.push(node)
}
}
//
if (node.childNode) {
findAllUserTaskNodes(node.childNode, matchNodeList)
}
//
if (node.type === NodeType.CONDITION_BRANCH_NODE && node.conditionNodes) {
node.conditionNodes.forEach(conditionNode => {
if (conditionNode.childNode) {
findAllUserTaskNodes(conditionNode.childNode, matchNodeList)
}
})
}
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -169,8 +169,42 @@ const deleteNode = () => {
const findReturnTaskNodes = ( const findReturnTaskNodes = (
matchNodeList: SimpleFlowNode[] // matchNodeList: SimpleFlowNode[] //
) => { ) => {
// //
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE) emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
//
findChildUserTaskNodes(currentNode.value, matchNodeList)
//
const currentNodeId = currentNode.value.id
const filteredList = matchNodeList.filter(node => node.id !== currentNodeId)
matchNodeList.length = 0 //
matchNodeList.push(...filteredList) //
}
//
const findChildUserTaskNodes = (node: SimpleFlowNode, matchNodeList: SimpleFlowNode[]) => {
if (!node) return
//
if (node.childNode) {
if (node.childNode.type === NodeType.USER_TASK_NODE) {
matchNodeList.push(node.childNode)
}
findChildUserTaskNodes(node.childNode, matchNodeList)
}
//
if (node.type === NodeType.CONDITION_BRANCH_NODE && node.conditionNodes) {
node.conditionNodes.forEach(conditionNode => {
if (conditionNode.childNode && conditionNode.childNode.type === NodeType.USER_TASK_NODE) {
matchNodeList.push(conditionNode.childNode)
}
if (conditionNode.childNode) {
findChildUserTaskNodes(conditionNode.childNode, matchNodeList)
}
})
}
} }
// //