Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
9a2dcf20
authored
Apr 29, 2024
by
jason
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
仿钉钉流程设计器- 新增条件分支节点
parent
0d8d0432
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
337 additions
and
6 deletions
+337
-6
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
+41
-2
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
+5
-0
src/components/SimpleProcessDesignerV2/src/consts.ts
+5
-1
src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
+126
-0
src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue
+0
-1
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
+147
-0
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
+13
-2
No files found.
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
View file @
9a2dcf20
...
@@ -15,6 +15,12 @@
...
@@ -15,6 +15,12 @@
</div>
</div>
<div
class=
"handler-item-text"
>
抄送
</div>
<div
class=
"handler-item-text"
>
抄送
</div>
</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>
</div>
<template
#
reference
>
<template
#
reference
>
<div
class=
"add-icon"
><Icon
icon=
"ep:plus"
/></div>
<div
class=
"add-icon"
><Icon
icon=
"ep:plus"
/></div>
...
@@ -50,7 +56,7 @@ const addNode = (type: number) => {
...
@@ -50,7 +56,7 @@ const addNode = (type: number) => {
popoverShow
.
value
=
false
popoverShow
.
value
=
false
if
(
type
===
NodeType
.
USER_TASK_NODE
)
{
if
(
type
===
NodeType
.
USER_TASK_NODE
)
{
const
data
:
SimpleFlowNode
=
{
const
data
:
SimpleFlowNode
=
{
id
:
generateUUID
(),
id
:
'Activity_'
+
generateUUID
(),
name
:
NODE_DEFAULT_NAME
.
get
(
NodeType
.
USER_TASK_NODE
)
as
string
,
name
:
NODE_DEFAULT_NAME
.
get
(
NodeType
.
USER_TASK_NODE
)
as
string
,
showText
:
''
,
showText
:
''
,
type
:
NodeType
.
USER_TASK_NODE
,
type
:
NodeType
.
USER_TASK_NODE
,
...
@@ -66,7 +72,7 @@ const addNode = (type: number) => {
...
@@ -66,7 +72,7 @@ const addNode = (type: number) => {
}
}
if
(
type
===
NodeType
.
COPY_TASK_NODE
)
{
if
(
type
===
NodeType
.
COPY_TASK_NODE
)
{
const
data
:
SimpleFlowNode
=
{
const
data
:
SimpleFlowNode
=
{
id
:
generateUUID
(),
id
:
'Activity_'
+
generateUUID
(),
name
:
NODE_DEFAULT_NAME
.
get
(
NodeType
.
COPY_TASK_NODE
)
as
string
,
name
:
NODE_DEFAULT_NAME
.
get
(
NodeType
.
COPY_TASK_NODE
)
as
string
,
showText
:
''
,
showText
:
''
,
type
:
NodeType
.
COPY_TASK_NODE
,
type
:
NodeType
.
COPY_TASK_NODE
,
...
@@ -79,6 +85,39 @@ const addNode = (type: number) => {
...
@@ -79,6 +85,39 @@ const addNode = (type: number) => {
}
}
emits
(
'update:childNode'
,
data
)
emits
(
'update:childNode'
,
data
)
}
}
if
(
type
===
NodeType
.
EXCLUSIVE_NODE
)
{
const
data
:
SimpleFlowNode
=
{
name
:
'条件分支'
,
type
:
NodeType
.
EXCLUSIVE_NODE
,
id
:
'GateWay_'
+
generateUUID
(),
childNode
:
props
.
childNode
,
conditionNodes
:
[
{
id
:
'Flow_'
+
generateUUID
(),
name
:
'条件1'
,
showText
:
''
,
type
:
NodeType
.
CONDITION_NODE
,
childNode
:
undefined
,
attributes
:
{
conditionType
:
1
,
defaultCondition
:
false
}
},
{
id
:
'Flow_'
+
generateUUID
(),
name
:
'其它情况'
,
showText
:
'其它情况进入此流程'
,
type
:
NodeType
.
CONDITION_NODE
,
childNode
:
undefined
,
attributes
:
{
conditionType
:
undefined
,
defaultCondition
:
true
}
}
]
}
emits
(
'update:childNode'
,
data
)
}
}
}
</
script
>
</
script
>
...
...
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
View file @
9a2dcf20
...
@@ -9,6 +9,10 @@
...
@@ -9,6 +9,10 @@
<CopyTaskNode
<CopyTaskNode
v-if=
"currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
v-if=
"currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
:flow-node =
"currentNode"
@
update:model-value=
"handleModelValueUpdate"
/>
:flow-node =
"currentNode"
@
update:model-value=
"handleModelValueUpdate"
/>
<!-- 条件节点 -->
<ExclusiveNode
v-if=
"currentNode && currentNode.type === NodeType.EXCLUSIVE_NODE"
:flow-node =
"currentNode"
@
update:model-value=
"handleModelValueUpdate"
/>
<!-- 递归显示孩子节点 -->
<!-- 递归显示孩子节点 -->
<ProcessNodeTree
v-if=
"currentNode && currentNode.childNode"
v-model:flow-node=
"currentNode.childNode"
/>
<ProcessNodeTree
v-if=
"currentNode && currentNode.childNode"
v-model:flow-node=
"currentNode.childNode"
/>
...
@@ -20,6 +24,7 @@ import StartEventNode from './nodes/StartEventNode.vue';
...
@@ -20,6 +24,7 @@ import StartEventNode from './nodes/StartEventNode.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'
;
import
CopyTaskNode
from
'./nodes/CopyTaskNode.vue'
;
import
CopyTaskNode
from
'./nodes/CopyTaskNode.vue'
;
import
ExclusiveNode
from
'./nodes/ExclusiveNode.vue'
;
import
{
SimpleFlowNode
,
NodeType
}
from
'./consts'
;
import
{
SimpleFlowNode
,
NodeType
}
from
'./consts'
;
defineOptions
({
defineOptions
({
name
:
'ProcessNodeTree'
name
:
'ProcessNodeTree'
...
...
src/components/SimpleProcessDesignerV2/src/consts.ts
View file @
9a2dcf20
...
@@ -112,5 +112,9 @@ export const APPROVE_METHODS: DictDataVO [] = [
...
@@ -112,5 +112,9 @@ export const APPROVE_METHODS: DictDataVO [] = [
{
label
:
'多人会签(需所有审批人同意)'
,
value
:
2
},
{
label
:
'多人会签(需所有审批人同意)'
,
value
:
2
},
{
label
:
'多人或签(一名审批人同意即可)'
,
value
:
3
},
{
label
:
'多人或签(一名审批人同意即可)'
,
value
:
3
},
{
label
:
'依次审批(按顺序依次审批)'
,
value
:
4
}
{
label
:
'依次审批(按顺序依次审批)'
,
value
:
4
}
// TODO 更多的类型
]
export
const
CONDITION_CONFIG_TYPES
:
DictDataVO
[]
=
[
{
label
:
'条件规则'
,
value
:
1
},
{
label
:
'条件表达式'
,
value
:
2
}
]
]
src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
0 → 100644
View file @
9a2dcf20
<
template
>
<el-drawer
:append-to-body=
"true"
v-model=
"settingVisible"
:show-close=
"false"
:size=
"550"
:before-close=
"saveConfig"
>
<template
#
header
>
<div
class=
"w-full flex flex-col"
>
<div
class=
"mb-2 text-size-2xl"
>
{{
currentNode
.
name
}}
</div>
<el-divider
/>
</div>
</
template
>
<div>
<div
class=
"mb-3 text-size-sm"
v-if=
"currentNode.attributes.defaultCondition"
>
其它条件不满足进入此分支(该分支不可编辑和删除)
</div>
<div
v-else
>
<el-form
label-position=
"top"
>
<el-form-item
label=
"配置方式"
prop=
"conditionType"
>
<el-radio-group
v-model=
"currentNode.attributes.conditionType"
@
change=
"changeConditionType"
>
<el-radio
v-for=
"(dict, index) in CONDITION_CONFIG_TYPES"
:key=
"index"
:value=
"dict.value"
:label=
"dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if=
"currentNode.attributes.conditionType === 2"
label=
"条件表达式"
prop=
"conditionExpression"
>
<el-input
type=
"textarea"
v-model=
"currentNode.attributes.conditionExpression"
clearable
style=
"width: 100%"
/>
</el-form-item>
<el-form-item
v-if=
"currentNode.attributes.conditionType === 1"
label=
"条件规则"
prop=
"conditionExpression"
>
<span
class=
"text-red-400"
>
待实现
</span>
</el-form-item>
</el-form>
</div>
</div>
<
template
#
footer
>
<el-divider
/>
<div>
<el-button
type=
"primary"
@
click=
"saveConfig"
>
确 定
</el-button>
<el-button
@
click=
"closeDrawer"
>
取 消
</el-button>
</div>
</
template
>
</el-drawer>
</template>
<
script
setup
lang=
"ts"
>
import
{
SimpleFlowNode
,
CONDITION_CONFIG_TYPES
}
from
'../consts'
defineOptions
({
name
:
'ConditionNode'
})
const
props
=
defineProps
({
conditionNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
required
:
true
}
})
const
settingVisible
=
ref
(
false
)
const
open
=
()
=>
{
settingVisible
.
value
=
true
}
watch
(()
=>
props
.
conditionNode
,
(
newValue
)
=>
{
currentNode
.
value
=
newValue
;
});
const
currentNode
=
ref
<
SimpleFlowNode
>
(
props
.
conditionNode
)
// TODO nodeInfo 测试
defineExpose
({
open
,
nodeInfo
:
currentNode
})
// 提供 open 方法,用于打开弹窗
// 关闭
const
closeDrawer
=
()
=>
{
settingVisible
.
value
=
false
}
// 保存配置
const
saveConfig
=
()
=>
{
if
(
!
currentNode
.
value
.
attributes
.
defaultCondition
)
{
currentNode
.
value
.
showText
=
getShowText
();
}
settingVisible
.
value
=
false
}
const
getShowText
=
()
:
string
=>
{
let
showText
=
''
;
if
(
currentNode
.
value
.
attributes
.
conditionType
===
1
)
{
showText
=
'待实现'
}
if
(
currentNode
.
value
.
attributes
.
conditionType
===
2
)
{
if
(
currentNode
.
value
.
attributes
.
conditionExpression
)
{
showText
=
`表达式:
${
currentNode
.
value
.
attributes
.
conditionExpression
}
`
}
}
return
showText
}
// 改变条件配置方式
const
changeConditionType
=
()
=>
{
}
</
script
>
<
style
lang=
"scss"
scoped
>
::v-deep
(
.el-divider--horizontal
)
{
display
:
block
;
height
:
1px
;
margin
:
0
;
border-top
:
1px
var
(
--el-border-color
)
var
(
--el-border-style
);
}
</
style
>
src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue
View file @
9a2dcf20
...
@@ -5,7 +5,6 @@
...
@@ -5,7 +5,6 @@
:show-close=
"false"
:show-close=
"false"
:size=
"550"
:size=
"550"
:before-close=
"saveConfig"
:before-close=
"saveConfig"
class=
"justify-start"
>
>
<template
#
header
>
<template
#
header
>
<div
class=
"w-full flex flex-col"
>
<div
class=
"w-full flex flex-col"
>
...
...
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
0 → 100644
View file @
9a2dcf20
<
template
>
<div
class=
"branch-node-wrapper"
>
<div
class=
"branch-node-container"
>
<div
class=
"branch-node-add"
@
click=
"addCondition"
>
添加条件
</div>
<div
class=
"branch-node-item"
v-for=
"(item, index) in currentNode.conditionNodes"
:key=
"index"
>
<template
v-if=
"index == 0"
>
<div
class=
"branch-line-first-top"
></div>
<div
class=
"branch-line-first-bottom"
></div>
</
template
>
<
template
v-if=
"index + 1 == currentNode.conditionNodes?.length"
>
<div
class=
"branch-line-last-top"
></div>
<div
class=
"branch-line-last-bottom"
></div>
</
template
>
<div
class=
"node-wrapper"
>
<div
class=
"node-container"
>
<div
class=
"node-box"
:class=
"{'node-config-error': !item.showText}"
>
<div
class=
"branch-node-title-container"
>
<div
class=
"branch-title"
v-if=
"showInputs[index]"
>
<input
type=
"text"
class=
"input-max-width editable-title-input"
@
blur=
"blurEvent(index)"
v-mountedFocus
v-model=
"item.name"
/>
</div>
<div
v-else
class=
"branch-title"
@
click=
"clickEvent(index)"
>
{{ item.name }}
</div>
<div
class=
"branch-priority"
>
优先级{{ index + 1 }}
</div>
</div>
<div
class=
"node-content"
@
click=
"conditionNodeConfig(item.id)"
>
<div
class=
"node-text"
:title=
"item.showText"
v-if =
"item.showText"
>
{{ item.showText }}
</div>
<div
class=
"node-text"
v-else
>
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
</div>
<Icon
icon=
"ep:arrow-right-bold"
/>
</div>
<div
class=
"node-toolbar"
v-if=
"index + 1 !== currentNode.conditionNodes?.length"
>
<div
class=
"toolbar-icon"
><Icon
icon=
"ep:circle-close"
@
click=
"deleteCondition(index)"
/></div>
</div>
</div>
<NodeHandler
v-model:child-node=
"item.childNode"
/>
</div>
</div>
<ConditionNodeConfig
:condition-node=
"item"
:ref=
"item.id"
/>
<!-- 递归显示子节点 -->
<ProcessNodeTree
v-if=
"item && item.childNode"
v-model:flow-node=
"item.childNode"
/>
</div>
</div>
<NodeHandler
v-if=
"currentNode"
v-model:child-node=
"currentNode.childNode"
/>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
NodeHandler
from
'../NodeHandler.vue'
import
ProcessNodeTree
from
'../ProcessNodeTree.vue'
import
{
SimpleFlowNode
,
NodeType
,
NODE_DEFAULT_TEXT
}
from
'../consts'
import
{
generateUUID
}
from
'@/utils'
import
ConditionNodeConfig
from
'../nodes-config/ConditionNodeConfig.vue'
const
{
proxy
}
=
getCurrentInstance
()
as
any
defineOptions
({
name
:
'ExclusiveNode'
})
const
props
=
defineProps
({
flowNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
required
:
true
}
})
// 定义事件,更新父组件
const
emits
=
defineEmits
<
{
'update:modelValue'
:
[
node
:
SimpleFlowNode
|
undefined
]
}
>
()
const
currentNode
=
ref
<
SimpleFlowNode
>
(
props
.
flowNode
)
// const conditionNodes = computed(() => currentNode.value.conditionNodes);
watch
(()
=>
props
.
flowNode
,
(
newValue
)
=>
{
currentNode
.
value
=
newValue
;
});
// TODO 测试后续去掉
// watch(() => conditionNodes, (newValue) => {
// console.log('new conditionNodes is ', newValue);
// },{ deep: true });
const
showInputs
=
ref
<
boolean
[]
>
([])
// 失去焦点
const
blurEvent
=
(
index
:
number
)
=>
{
showInputs
.
value
[
index
]
=
false
const
conditionNode
=
currentNode
.
value
.
conditionNodes
?.
at
(
index
)
as
SimpleFlowNode
;
conditionNode
.
name
=
conditionNode
.
name
||
'条件'
+
index
}
// 点击条件名称
const
clickEvent
=
(
index
:
number
)
=>
{
showInputs
.
value
[
index
]
=
true
}
const
conditionNodeConfig
=
(
nodeId
:
string
)
=>
{
console
.
log
(
'nodeId'
,
nodeId
);
console
.
log
(
"proxy.$refs"
,
proxy
.
$refs
);
// TODO 测试后续去掉
const
conditionNode
=
proxy
.
$refs
[
nodeId
][
0
];
console
.
log
(
"node inf is "
,
conditionNode
.
nodeInfo
);
conditionNode
.
open
()
}
const
addCondition
=
()
=>
{
const
conditionNodes
=
currentNode
.
value
.
conditionNodes
;
if
(
conditionNodes
)
{
const
len
=
conditionNodes
.
length
let
lastIndex
=
len
-
1
const
conditionData
:
SimpleFlowNode
=
{
id
:
generateUUID
(),
name
:
'条件'
+
len
,
showText
:
''
,
type
:
NodeType
.
CONDITION_NODE
,
childNode
:
undefined
,
conditionNodes
:
[],
attributes
:
{
conditionType
:
1
,
defaultCondition
:
false
}
}
conditionNodes
.
splice
(
lastIndex
,
0
,
conditionData
)
}
}
const
deleteCondition
=
(
index
:
number
)
=>
{
const
conditionNodes
=
currentNode
.
value
.
conditionNodes
;
if
(
conditionNodes
)
{
conditionNodes
.
splice
(
index
,
1
)
if
(
conditionNodes
.
length
==
1
)
{
const
childNode
=
currentNode
.
value
.
childNode
// 更新此节点为后续孩子节点
emits
(
'update:modelValue'
,
childNode
)
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
View file @
9a2dcf20
...
@@ -132,16 +132,22 @@
...
@@ -132,16 +132,22 @@
// 条件节点标题
// 条件节点标题
.branch-node-title-container
{
.branch-node-title-container
{
display
:
flex
;
display
:
flex
;
padding
:
4px
;
padding
:
4px
0
;
cursor
:
pointer
;
cursor
:
pointer
;
border-radius
:
4px
4px
0
0
;
border-radius
:
4px
4px
0
0
;
align-items
:
center
;
align-items
:
center
;
justify-content
:
space-between
;
justify-content
:
space-between
;
.input-max-width
{
max-width
:
115px
!
important
;
}
.branch-title
{
.branch-title
{
max-width
:
120px
;
font-size
:
13px
;
font-size
:
13px
;
font-weight
:
600
;
font-weight
:
600
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
color
:
#f60
;
color
:
#f60
;
}
}
...
@@ -425,6 +431,7 @@
...
@@ -425,6 +431,7 @@
// 可编辑的 title 输入框
// 可编辑的 title 输入框
.editable-title-input
{
.editable-title-input
{
height
:
20px
;
height
:
20px
;
max-width
:
145px
;
line-height
:
20px
;
line-height
:
20px
;
font-size
:
12px
;
font-size
:
12px
;
margin-left
:
4px
;
margin-left
:
4px
;
...
@@ -481,6 +488,10 @@
...
@@ -481,6 +488,10 @@
color
:
#3296fa
;
color
:
#3296fa
;
}
}
.condition
{
color
:
#15bc83
;
}
.handler-item-text
{
.handler-item-text
{
margin-top
:
4px
;
margin-top
:
4px
;
width
:
80px
;
width
:
80px
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment