修改为next、分支修改为branches、新增ServiceNode节点

This commit is contained in:
caixiaofeng 2024-12-11 13:31:44 +08:00
parent 48cbcf35ed
commit 3fe2c36607
11 changed files with 193 additions and 74 deletions

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import TreeNode from './nodes/TreeNode.vue' import TreeNode from './nodes/TreeNode.vue'
import Panel from './panels/index.vue' import Panel from './panels/index.vue'
import type { ErrorInfo, FlowNode, TimerNode } from './nodes/type' import type { ErrorInfo, FlowNode, ServiceNode, TimerNode } from './nodes/type'
import type { import type {
ApprovalNode, ApprovalNode,
BranchNode, BranchNode,
@ -73,13 +73,13 @@ const nextId = (): string => {
if (node.id === id) { if (node.id === id) {
return true return true
} }
if (node.child) { if (node.next) {
return findId(node.child, id) return findId(node.next, id)
} }
if ('children' in node) { if ('branches' in node) {
const branchNode = node as BranchNode const branchNode = node as BranchNode
if (branchNode.children && branchNode.children.length > 0) { if (branchNode.branches && branchNode.branches.length > 0) {
return branchNode.children.some((item) => { return branchNode.branches.some((item) => {
return findId(item, id) return findId(item, id)
}) })
} }
@ -92,53 +92,53 @@ const nextId = (): string => {
return id return id
} }
const addExclusive = (node: FlowNode) => { const addExclusive = (node: FlowNode) => {
const child = node.child const next = node.next
const id = nextId() const id = nextId()
const exclusiveNode = { const exclusiveNode = {
id: id, id: id,
pid: node.id, pid: node.id,
type: 'exclusive', type: 'exclusive',
name: '独占网关', name: '独占网关',
child: child, next: next,
children: [] branches: []
} as ExclusiveNode } as ExclusiveNode
if (child) { if (next) {
child.pid = id next.pid = id
} }
addCondition(exclusiveNode) addCondition(exclusiveNode)
addCondition(exclusiveNode) addCondition(exclusiveNode)
node.child = exclusiveNode node.next = exclusiveNode
if (exclusiveNode.children.length > 0) { if (exclusiveNode.branches.length > 0) {
const condition = exclusiveNode.children[exclusiveNode.children.length - 1] as ConditionNode const condition = exclusiveNode.branches[exclusiveNode.branches.length - 1] as ConditionNode
condition.def = true condition.def = true
condition.name = '默认条件' condition.name = '默认条件'
} }
} }
const addCondition = (node: FlowNode) => { const addCondition = (node: FlowNode) => {
const exclusive = node as ExclusiveNode const exclusive = node as ExclusiveNode
exclusive.children.splice(exclusive.children.length - 1, 0, { exclusive.branches.splice(exclusive.branches.length - 1, 0, {
id: nextId(), id: nextId(),
pid: exclusive.id, pid: exclusive.id,
type: 'condition', type: 'condition',
def: false, def: false,
name: `条件${exclusive.children.length + 1}`, name: `条件${exclusive.branches.length + 1}`,
conditions: { conditions: {
operator: 'and', operator: 'and',
conditions: [], conditions: [],
groups: [] groups: []
} as FilterRules, } as FilterRules,
child: undefined next: undefined
}) })
} }
const addCc = (node: FlowNode) => { const addCc = (node: FlowNode) => {
const child = node.child const next = node.next
const id = nextId() const id = nextId()
node.child = { node.next = {
id: id, id: id,
pid: node.id, pid: node.id,
type: 'cc', type: 'cc',
name: '抄送人', name: '抄送人',
child: child, next: next,
assigneeType: 'user', assigneeType: 'user',
formUser: '', formUser: '',
formRole: '', formRole: '',
@ -150,38 +150,38 @@ const addCc = (node: FlowNode) => {
self: false, self: false,
formProperties: [] formProperties: []
} as CcNode } as CcNode
if (child) { if (next) {
child.pid = id next.pid = id
} }
} }
const addTimer = (node: FlowNode) => { const addTimer = (node: FlowNode) => {
const child = node.child const next = node.next
const id = nextId() const id = nextId()
node.child = { node.next = {
id: id, id: id,
pid: node.id, pid: node.id,
name: '计时等待', name: '计时等待',
type: 'timer', type: 'timer',
child: child, next: next,
waitType: 'duration', waitType: 'duration',
unit: 'PT%sS', unit: 'PT%sS',
duration: 0, duration: 0,
timeDate: undefined timeDate: undefined
} as TimerNode } as TimerNode
if (child) { if (next) {
child.pid = id next.pid = id
} }
} }
const addNotify = (node: FlowNode) => { const addNotify = (node: FlowNode) => {
const child = node.child const next = node.next
const id = nextId() const id = nextId()
node.child = { node.next = {
id: id, id: id,
pid: node.id, pid: node.id,
name: '消息通知', name: '消息通知',
type: 'notify', type: 'notify',
child: child, next: next,
assigneeType: 'user', assigneeType: 'user',
formUser: '', formUser: '',
formRole: '', formRole: '',
@ -195,20 +195,36 @@ const addNotify = (node: FlowNode) => {
subject: '', subject: '',
content: '' content: ''
} as NotifyNode } as NotifyNode
if (child) { if (next) {
child.pid = id next.pid = id
}
}
const addService = (node: FlowNode) => {
const next = node.next
const id = nextId()
node.next = {
id: id,
pid: node.id,
type: 'service',
name: '服务节点',
next: next,
implementationType: '',
implementation: '',
} as ServiceNode
if (next) {
next.pid = id
} }
} }
const addApproval = (node: FlowNode) => { const addApproval = (node: FlowNode) => {
const child = node.child const next = node.next
const id = nextId() const id = nextId()
node.child = { node.next = {
id: id, id: id,
pid: node.id, pid: node.id,
type: 'approval', type: 'approval',
name: '审批人', name: '审批人',
executionListeners: [], executionListeners: [],
child: child, next: next,
// //
assigneeType: 'user', assigneeType: 'user',
formUser: '', formUser: '',
@ -234,8 +250,8 @@ const addApproval = (node: FlowNode) => {
minusMulti: false minusMulti: false
} }
} as ApprovalNode } as ApprovalNode
if (child) { if (next) {
child.pid = id next.pid = id
} }
} }
const addNode = (type: NodeType, node: FlowNode) => { const addNode = (type: NodeType, node: FlowNode) => {
@ -245,6 +261,7 @@ const addNode = (type: NodeType, node: FlowNode) => {
cc: addCc, cc: addCc,
timer: addTimer, timer: addTimer,
notify: addNotify, notify: addNotify,
service: addService,
approval: addApproval approval: addApproval
} }
const fun = addMap[type] const fun = addMap[type]
@ -257,32 +274,32 @@ const delNode = (del: FlowNode) => {
const delNodeNext = (next: FlowNode, del: FlowNode) => { const delNodeNext = (next: FlowNode, del: FlowNode) => {
delete nodesError.value[del.id] delete nodesError.value[del.id]
if (next.id === del.pid) { if (next.id === del.pid) {
if ('children' in next && next.child?.id !== del.id) { if ('branches' in next && next.next?.id !== del.id) {
const branchNode = next as BranchNode const branchNode = next as BranchNode
const index = branchNode.children.findIndex((item) => item.id === del.id) const index = branchNode.branches.findIndex((item) => item.id === del.id)
if (index !== -1) { if (index !== -1) {
if (branchNode.children.length <= 2) { if (branchNode.branches.length <= 2) {
delError(branchNode) delError(branchNode)
delNode(branchNode) delNode(branchNode)
} else { } else {
delError(del) delError(del)
branchNode.children.splice(index, 1) branchNode.branches.splice(index, 1)
} }
} }
} else { } else {
if (del.child && del.child.pid) { if (del.next && del.next.pid) {
del.child.pid = next.id del.next.pid = next.id
} }
next.child = del.child next.next = del.next
} }
} else { } else {
if (next.child) { if (next.next) {
delNodeNext(next.child, del) delNodeNext(next.next, del)
} }
if ('children' in next) { if ('branches' in next) {
const nextBranch = next as BranchNode const nextBranch = next as BranchNode
if (nextBranch.children && nextBranch.children.length > 0) { if (nextBranch.branches && nextBranch.branches.length > 0) {
nextBranch.children.forEach((item) => { nextBranch.branches.forEach((item) => {
delNodeNext(item, del) delNodeNext(item, del)
}) })
} }
@ -291,13 +308,13 @@ const delNodeNext = (next: FlowNode, del: FlowNode) => {
} }
const delError = (node: FlowNode) => { const delError = (node: FlowNode) => {
delete nodesError.value[node.id] delete nodesError.value[node.id]
if (node.child) { if (node.next) {
delError(node.child) delError(node.next)
} }
if ('children' in node) { if ('branches' in node) {
const branchNode = node as BranchNode const branchNode = node as BranchNode
if (branchNode.children && branchNode.children.length > 0) { if (branchNode.branches && branchNode.branches.length > 0) {
branchNode.children.forEach((item) => { branchNode.branches.forEach((item) => {
delError(item) delError(item)
}) })
} }

View File

@ -30,6 +30,10 @@ const addNotifyNode = () => {
$emits('addNode', 'notify') $emits('addNode', 'notify')
popoverRef.value?.hide() popoverRef.value?.hide()
} }
const addServiceNode = () => {
$emits('addNode', 'service')
popoverRef.value?.hide()
}
</script> </script>
<template> <template>
@ -62,6 +66,10 @@ const addNotifyNode = () => {
<svg-icon name="el:BellFilled" /> <svg-icon name="el:BellFilled" />
<el-text>消息通知</el-text> <el-text>消息通知</el-text>
</div> </div>
<div class="node-select" @click="addServiceNode">
<svg-icon name="el:Tools" />
<el-text>服务节点</el-text>
</div>
</el-space> </el-space>
<template #reference> <template #reference>
<el-button <el-button
@ -117,6 +125,10 @@ const addNotifyNode = () => {
&.BellFilled { &.BellFilled {
background-color: #95d475; background-color: #95d475;
} }
&.Tools {
background-color: #ffc107;
}
} }
.el-text { .el-text {

View File

@ -13,13 +13,13 @@ const { nodesError } = inject<{
const content = ref<string>('') const content = ref<string>('')
watchEffect(() => { watchEffect(() => {
const errors: ErrorInfo[] = [] const errors: ErrorInfo[] = []
const { id, name, def, conditions, child } = props.node const { id, name, def, conditions, next } = props.node
if (def) { if (def) {
content.value = '不满足其他条件,进入此分支' content.value = '不满足其他条件,进入此分支'
} else if (conditions.conditions.length > 0 || (conditions.groups?.length || 0) > 0) { } else if (conditions.conditions.length > 0 || (conditions.groups?.length || 0) > 0) {
const count = conditions.conditions.length + (conditions.groups?.length || 0) const count = conditions.conditions.length + (conditions.groups?.length || 0)
content.value = `已设置(${count})个条件` content.value = `已设置(${count})个条件`
if (!child) { if (!next) {
errors.push({ id: id, name: name, message: '分支下节点为空' }) errors.push({ id: id, name: name, message: '分支下节点为空' })
} }
} else { } else {

View File

@ -17,14 +17,14 @@ const addNode = (type: NodeType, node?: FlowNode) => {
$emits('addNode', type, node || props.node) $emits('addNode', type, node || props.node)
} }
const moveRight = (index: number) => { const moveRight = (index: number) => {
const node = props.node.children[index] const node = props.node.branches[index]
props.node.children.splice(index, 1) props.node.branches.splice(index, 1)
props.node.children.splice(index + 1, 0, node) props.node.branches.splice(index + 1, 0, node)
} }
const moveLeft = (index: number) => { const moveLeft = (index: number) => {
const node = props.node.children[index] const node = props.node.branches[index]
props.node.children.splice(index, 1) props.node.branches.splice(index, 1)
props.node.children.splice(index - 1, 0, node) props.node.branches.splice(index - 1, 0, node)
} }
</script> </script>
@ -33,12 +33,12 @@ const moveLeft = (index: number) => {
<div class="add-branch"> <div class="add-branch">
<slot :addNode="addNode" :readOnly="readOnly"></slot> <slot :addNode="addNode" :readOnly="readOnly"></slot>
</div> </div>
<div v-for="(item, index) in node.children" :key="item.id" class="col-box"> <div v-for="(item, index) in node.branches" :key="item.id" class="col-box">
<template v-if="index === 0"> <template v-if="index === 0">
<div class="top-left-border"></div> <div class="top-left-border"></div>
<div class="bottom-left-border" /> <div class="bottom-left-border" />
</template> </template>
<template v-else-if="node.children.length === index + 1"> <template v-else-if="node.branches.length === index + 1">
<div class="top-right-border"></div> <div class="top-right-border"></div>
<div class="bottom-right-border" /> <div class="bottom-right-border" />
</template> </template>
@ -47,14 +47,14 @@ const moveLeft = (index: number) => {
<div <div
class="move-left" class="move-left"
@click.stop="moveLeft(index)" @click.stop="moveLeft(index)"
v-show="index !== 0 && node.children.length !== index + 1 && !readOnly" v-show="index !== 0 && node.branches.length !== index + 1 && !readOnly"
> >
<svg-icon name="el:ArrowLeft" /> <svg-icon name="el:ArrowLeft" />
</div> </div>
<div <div
class="move-right" class="move-right"
@click.stop="moveRight(index)" @click.stop="moveRight(index)"
v-show="![index + 1, index + 2].includes(node.children.length) && !readOnly" v-show="![index + 1, index + 2].includes(node.branches.length) && !readOnly"
> >
<svg-icon name="el:ArrowRight" /> <svg-icon name="el:ArrowRight" />
</div> </div>

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import Node from './Node.vue'
import type { ErrorInfo, ServiceNode } from './type'
import type { Ref } from 'vue'
const props = defineProps<{
node: ServiceNode
}>()
const { nodesError } = inject<{
nodesError: Ref<Recordable<ErrorInfo[]>>
}>('flowDesign', { nodesError: ref({}) })
const content = ref<string>('')
watchEffect(() => {
const errors: ErrorInfo[] = []
const { id, name, implementationType, implementation } = props.node
if (!implementationType) {
errors.push({ id: id, name: name, message: '执行类型为空' })
content.value = '执行类型为空'
} else if (!implementation) {
errors.push({ id: id, name: name, message: '执行值为空' })
content.value = '执行值为空'
} else {
content.value = `执行服务`
}
//
if (errors.length > 0) {
nodesError.value[id] = errors
} else {
delete nodesError.value[id]
}
})
</script>
<template>
<Node v-bind="$attrs" icon="el:Tools" color="#ffc107" :node="node">
<el-text>{{ content }}</el-text>
</Node>
</template>
<style scoped lang="scss"></style>

View File

@ -12,8 +12,8 @@ const { nodesError } = inject<{
}>('flowDesign', { nodesError: ref({}) }) }>('flowDesign', { nodesError: ref({}) })
watchEffect(() => { watchEffect(() => {
const errors: ErrorInfo[] = [] const errors: ErrorInfo[] = []
const { id, name, child } = props.node const { id, name, next } = props.node
if (child?.type === 'end') { if (next?.type === 'end') {
errors.push({ id: id, name: name, message: '发起下节点为空' }) errors.push({ id: id, name: name, message: '发起下节点为空' })
} }
// //

View File

@ -7,6 +7,7 @@ import Approval from './ApprovalNode.vue'
import Cc from './CcNode.vue' import Cc from './CcNode.vue'
import Timer from './TimerNode.vue' import Timer from './TimerNode.vue'
import Notify from './NotifyNode.vue' import Notify from './NotifyNode.vue'
import Service from './ServiceNode.vue'
import Exclusive from './ExclusiveNode.vue' import Exclusive from './ExclusiveNode.vue'
import Condition from './ConditionNode.vue' import Condition from './ConditionNode.vue'
@ -19,6 +20,7 @@ const nodes: Recordable<Component> = {
cc: Cc, cc: Cc,
timer: Timer, timer: Timer,
notify: Notify, notify: Notify,
service: Service,
exclusive: Exclusive, exclusive: Exclusive,
condition: Condition, condition: Condition,
end: End end: End
@ -32,7 +34,7 @@ const nodes: Recordable<Component> = {
<slot :name="name" v-bind="scope || {}"></slot> <slot :name="name" v-bind="scope || {}"></slot>
</template> </template>
</component> </component>
<TreeNode v-if="node.child" :node="node.child" v-bind="$attrs" /> <TreeNode v-if="node.next" :node="node.next" v-bind="$attrs" />
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

@ -7,6 +7,7 @@ export type NodeType =
| 'exclusive' | 'exclusive'
| 'timer' | 'timer'
| 'notify' | 'notify'
| 'service'
| 'condition' | 'condition'
| 'end' | 'end'
@ -16,7 +17,7 @@ export interface FlowNode {
name: string name: string
type: NodeType type: NodeType
executionListeners?: NodeListener[] executionListeners?: NodeListener[]
child?: FlowNode next?: FlowNode
} }
export interface NodeListener { export interface NodeListener {
@ -88,6 +89,11 @@ export interface ApprovalNode extends AssigneeNode {
taskListeners?: NodeListener[] taskListeners?: NodeListener[]
} }
export interface ServiceNode extends FlowNode {
implementationType: string
implementation: string
}
export interface TimerNode extends FlowNode { export interface TimerNode extends FlowNode {
waitType: 'duration' | 'date' waitType: 'duration' | 'date'
unit: 'PT%sS' | 'PT%sM' | 'PT%sH' | 'P%sD' | 'P%sW' | 'P%sM' unit: 'PT%sS' | 'PT%sM' | 'PT%sH' | 'P%sD' | 'P%sW' | 'P%sM'
@ -101,11 +107,11 @@ export interface ConditionNode extends FlowNode {
} }
export interface BranchNode extends FlowNode { export interface BranchNode extends FlowNode {
children: FlowNode[] branches: FlowNode[]
} }
export interface ExclusiveNode extends BranchNode { export interface ExclusiveNode extends BranchNode {
children: ConditionNode[] branches: ConditionNode[]
} }
export interface ErrorInfo { export interface ErrorInfo {

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import type { ServiceNode } from '../nodes/type'
defineProps<{
activeData: ServiceNode
}>()
</script>
<template>
<el-form label-position="top">
<el-form-item prop="implementationType" label="执行类型">
<el-select v-model="activeData.implementationType" placeholder="请选择执行类型">
<el-option label="类" value="class" />
<el-option label="表达式" value="expression" />
<el-option label="委托表达式" value="delegateExpression" />
</el-select>
</el-form-item>
<el-form-item prop="implementation" label="执行值">
<template #label>
<div class="flex-items-center gap3px">
<span>执行值</span>
<el-tooltip placement="top-start">
<template #content>
实现 JavaDelegate 接口 <br />
${com.example.delegate.MyServiceDelegate} <br />
表达式: ${myServiceDelegate.execute(execution)} <br />
委托表达式${myServiceDelegate}
</template>
<el-icon>
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model="activeData.implementation" placeholder="请输入执行值" clearable />
</el-form-item>
</el-form>
</template>
<style scoped lang="scss"></style>

View File

@ -6,6 +6,7 @@ import Approval from './ApprovalPanel.vue'
import Cc from './CcPanel.vue' import Cc from './CcPanel.vue'
import Timer from './TimerPanel.vue' import Timer from './TimerPanel.vue'
import Notify from './NotifyPanel.vue' import Notify from './NotifyPanel.vue'
import Service from './ServicePanel.vue'
import Condition from './ConditionPanel.vue' import Condition from './ConditionPanel.vue'
import End from './EndPanel.vue' import End from './EndPanel.vue'
import type { FlowNode } from '../nodes/type' import type { FlowNode } from '../nodes/type'
@ -20,6 +21,7 @@ const panels: Recordable<Component> = {
cc: Cc, cc: Cc,
timer: Timer, timer: Timer,
notify: Notify, notify: Notify,
service: Service,
condition: Condition, condition: Condition,
end: End end: End
} }

View File

@ -12,13 +12,13 @@ const process = ref<FlowNode>({
name: '发起人', name: '发起人',
executionListeners: [], executionListeners: [],
formProperties: [], formProperties: [],
child: { next: {
id: 'end', id: 'end',
pid: 'root', pid: 'root',
type: 'end', type: 'end',
name: '流程结束', name: '流程结束',
executionListeners: [], executionListeners: [],
child: undefined next: undefined
} as EndNode } as EndNode
} as StartNode) } as StartNode)
// //