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
c9b12c8b
authored
Oct 29, 2024
by
jason
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【功能优化】仿钉钉流程设计优化
parent
a43bca57
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1317 additions
and
717 deletions
+1317
-717
src/assets/svgs/bpm/simple-process-bg.svg
+2
-0
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
+4
-1
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
+33
-66
src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
+141
-0
src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue
+9
-47
src/components/SimpleProcessDesignerV2/src/index.ts
+1
-1
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
+1095
-570
src/router/modules/remaining.ts
+3
-3
src/views/bpm/model/CategoryDraggableModel.vue
+4
-2
src/views/bpm/model/index_old.vue
+1
-1
src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue
+9
-19
src/views/bpm/processInstance/detail/index.vue
+7
-2
src/views/bpm/simple/SimpleModelDesign.vue
+8
-5
No files found.
src/assets/svgs/bpm/simple-process-bg.svg
0 → 100644
View file @
c9b12c8b
<svg
width=
"22"
height=
"22"
xmlns=
"http://www.w3.org/2000/svg"
><g
fill=
"none"
fill-rule=
"evenodd"
><path
fill=
"#FAFAFA"
d=
"M0 0h22v22H0z"
/><circle
fill=
"#919BAE"
cx=
"1"
cy=
"1"
r=
"1"
/></g></svg>
\ No newline at end of file
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
View file @
c9b12c8b
...
...
@@ -47,7 +47,10 @@
/>
<!-- 结束节点 -->
<EndEventNode
v-if=
"currentNode && currentNode.type === NodeType.END_EVENT_NODE"
:flow-node=
"currentNode"
/>
<EndEventNode
v-if=
"currentNode && currentNode.type === NodeType.END_EVENT_NODE"
:flow-node=
"currentNode"
/>
</
template
>
<
script
setup
lang=
"ts"
>
import
StartUserNode
from
'./nodes/StartUserNode.vue'
...
...
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
View file @
c9b12c8b
<
template
>
<div
class=
"simple-flow-canvas"
v-loading=
"loading"
>
<div
class=
"simple-flow-container"
>
<div
class=
"top-area-container"
>
<div
class=
"top-actions"
>
<div
class=
"canvas-control"
>
<span
class=
"control-scale-group"
>
<span
class=
"control-scale-button"
>
<Icon
icon=
"ep:plus"
@
click=
"zoomOut()"
/></span>
<span
class=
"control-scale-label"
>
{{
scaleValue
}}
%
</span>
<span
class=
"control-scale-button"
><Icon
icon=
"ep:minus"
@
click=
"zoomIn()"
/></span>
</span>
</div>
<el-button
type=
"primary"
@
click=
"saveSimpleFlowModel"
>
保存
</el-button>
<!--
<el-button
type=
"primary"
>
全局设置
</el-button>
-->
</div>
</div>
<div
class=
"scale-container"
:style=
"`transform: scale($
{scaleValue / 100});`">
<ProcessNodeTree
v-if=
"processNodeTree"
v-model:flow-node=
"processNodeTree"
/>
</div>
</div>
<div
v-loading=
"loading"
>
<SimpleProcessModel
v-if=
"processNodeTree"
:flow-node=
"processNodeTree"
:readonly=
"false"
@
save=
"saveSimpleFlowModel"
/>
<Dialog
v-model=
"errorDialogVisible"
title=
"保存失败"
width=
"400"
:fullscreen=
"false"
>
<div
class=
"mb-2"
>
以下节点内容不完善,请修改后保存
</div>
<div
...
...
@@ -35,7 +23,7 @@
</template>
<
script
setup
lang=
"ts"
>
import
ProcessNodeTree
from
'./ProcessNodeTree
.vue'
import
SimpleProcessModel
from
'./SimpleProcessModel
.vue'
import
{
updateBpmSimpleModel
,
getBpmSimpleModel
}
from
'@/api/bpm/simple'
import
{
SimpleFlowNode
,
NodeType
,
NodeId
,
NODE_DEFAULT_TEXT
}
from
'./consts'
import
{
getModel
}
from
'@/api/bpm/model'
...
...
@@ -50,13 +38,15 @@ import * as UserGroupApi from '@/api/bpm/userGroup'
defineOptions
({
name
:
'SimpleProcessDesigner'
})
const
router
=
useRouter
()
// 路由
const
emits
=
defineEmits
([
'success'
])
// 保存成功事件
const
props
=
defineProps
({
modelId
:
{
type
:
String
,
required
:
true
}
})
const
loading
=
ref
(
false
)
const
formFields
=
ref
<
string
[]
>
([])
const
formType
=
ref
(
20
)
...
...
@@ -66,7 +56,6 @@ 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
)
...
...
@@ -80,28 +69,26 @@ const message = useMessage() // 国际化
const
processNodeTree
=
ref
<
SimpleFlowNode
|
undefined
>
()
const
errorDialogVisible
=
ref
(
false
)
let
errorNodes
:
SimpleFlowNode
[]
=
[]
const
saveSimpleFlowModel
=
async
()
=>
{
if
(
!
props
.
modelId
)
{
message
.
error
(
'
缺少模型 modelId 编号
'
)
const
saveSimpleFlowModel
=
async
(
simpleModelNode
:
SimpleFlowNode
)
=>
{
if
(
!
simpleModelNode
)
{
message
.
error
(
'
模型数据为空
'
)
return
}
errorNodes
=
[]
validateNode
(
processNodeTree
.
value
,
errorNodes
)
if
(
errorNodes
.
length
>
0
)
{
errorDialogVisible
.
value
=
true
return
}
const
data
=
{
id
:
props
.
modelId
,
simpleModel
:
processNodeTree
.
value
}
const
result
=
await
updateBpmSimpleModel
(
data
)
if
(
result
)
{
message
.
success
(
'修改成功'
)
close
()
}
else
{
message
.
alert
(
'修改失败'
)
try
{
loading
.
value
=
true
const
data
=
{
id
:
props
.
modelId
,
simpleModel
:
simpleModelNode
}
const
result
=
await
updateBpmSimpleModel
(
data
)
if
(
result
)
{
message
.
success
(
'修改成功'
)
emits
(
'success'
)
}
else
{
message
.
alert
(
'修改失败'
)
}
}
finally
{
loading
.
value
=
false
}
}
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
...
...
@@ -126,12 +113,13 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
}
validateNode
(
node
.
childNode
,
errorNodes
)
}
if
(
type
==
NodeType
.
CONDITION_BRANCH_NODE
||
type
==
NodeType
.
PARALLEL_BRANCH_NODE
||
type
==
NodeType
.
INCLUSIVE_BRANCH_NODE
)
{
// 分支节点
)
{
// 分支节点
// 1. 先校验各个分支
conditionNodes
?.
forEach
((
item
)
=>
{
validateNode
(
item
,
errorNodes
)
...
...
@@ -142,27 +130,6 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
}
}
const
close
=
()
=>
{
router
.
push
({
path
:
'/bpm/manager/model'
})
}
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
...
...
@@ -188,7 +155,7 @@ onMounted(async () => {
// 获取用户组列表
userGroupOptions
.
value
=
await
UserGroupApi
.
getUserGroupSimpleList
()
//
获取 SIMPLE 设计器模型
//获取 SIMPLE 设计器模型
const
result
=
await
getBpmSimpleModel
(
props
.
modelId
)
if
(
result
)
{
processNodeTree
.
value
=
result
...
...
src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
0 → 100644
View file @
c9b12c8b
<
template
>
<div
class=
"simple-process-model-container position-relative"
:style=
"`transform: scale($
{scaleValue / 100});`"
>
<div
class=
"position-absolute top-0px right-0px bg-#fff"
>
<el-row
type=
"flex"
justify=
"end"
>
<el-button-group
key=
"scale-control"
size=
"default"
>
<el-button
size=
"default"
:icon=
"ScaleToOriginal"
@
click=
"processReZoom()"
/>
<el-button
size=
"default"
:plain=
"true"
:icon=
"ZoomOut"
@
click=
"zoomOut()"
/>
<el-button
size=
"default"
class=
"w-80px"
>
{{
scaleValue
}}
%
</el-button>
<el-button
size=
"default"
:plain=
"true"
:icon=
"ZoomIn"
@
click=
"zoomIn()"
/>
</el-button-group>
<el-button
v-if=
"!readonly"
size=
"default"
class=
"ml-4px"
type=
"primary"
:icon=
"Select"
@
click=
"saveSimpleFlowModel"
>
保存模型
</el-button
>
</el-row>
</div>
<ProcessNodeTree
v-if=
"processNodeTree"
v-model:flow-node=
"processNodeTree"
/>
</div>
<Dialog
v-model=
"errorDialogVisible"
title=
"保存失败"
width=
"400"
:fullscreen=
"false"
>
<div
class=
"mb-2"
>
以下节点内容不完善,请修改后保存
</div>
<div
class=
"mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
v-for=
"(item, index) in errorNodes"
:key=
"index"
>
{{
item
.
name
}}
:
{{
NODE_DEFAULT_TEXT
.
get
(
item
.
type
)
}}
</div>
<template
#
footer
>
<el-button
type=
"primary"
@
click=
"errorDialogVisible = false"
>
知道了
</el-button>
</
template
>
</Dialog>
</template>
<
script
setup
lang=
"ts"
>
import
ProcessNodeTree
from
'./ProcessNodeTree.vue'
import
{
SimpleFlowNode
,
NodeType
,
NODE_DEFAULT_TEXT
}
from
'./consts'
import
{
useWatchNode
}
from
'./node'
import
{
Select
,
ZoomOut
,
ZoomIn
,
ScaleToOriginal
}
from
'@element-plus/icons-vue'
defineOptions
({
name
:
'SimpleProcessModel'
})
const
props
=
defineProps
({
flowNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
required
:
true
},
readonly
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
}
})
const
emits
=
defineEmits
<
{
'save'
:
[
node
:
SimpleFlowNode
|
undefined
]
}
>
()
const
processNodeTree
=
useWatchNode
(
props
)
provide
(
'readonly'
,
props
.
readonly
)
let
scaleValue
=
ref
(
100
)
const
MAX_SCALE_VALUE
=
200
const
MIN_SCALE_VALUE
=
50
// 放大
const
zoomIn
=
()
=>
{
if
(
scaleValue
.
value
==
MAX_SCALE_VALUE
)
{
return
}
scaleValue
.
value
+=
10
}
// 缩小
const
zoomOut
=
()
=>
{
if
(
scaleValue
.
value
==
MIN_SCALE_VALUE
)
{
return
}
scaleValue
.
value
-=
10
}
const
processReZoom
=
()
=>
{
scaleValue
.
value
=
100
}
const
errorDialogVisible
=
ref
(
false
)
let
errorNodes
:
SimpleFlowNode
[]
=
[]
const
saveSimpleFlowModel
=
async
()
=>
{
errorNodes
=
[]
validateNode
(
processNodeTree
.
value
,
errorNodes
)
if
(
errorNodes
.
length
>
0
)
{
errorDialogVisible
.
value
=
true
return
}
emits
(
'save'
,
processNodeTree
.
value
)
}
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
const
validateNode
=
(
node
:
SimpleFlowNode
|
undefined
,
errorNodes
:
SimpleFlowNode
[])
=>
{
if
(
node
)
{
const
{
type
,
showText
,
conditionNodes
}
=
node
if
(
type
==
NodeType
.
END_EVENT_NODE
)
{
return
}
if
(
type
==
NodeType
.
START_USER_NODE
)
{
// 发起人节点暂时不用校验,直接校验孩子节点
validateNode
(
node
.
childNode
,
errorNodes
)
}
if
(
type
===
NodeType
.
USER_TASK_NODE
||
type
===
NodeType
.
COPY_TASK_NODE
||
type
===
NodeType
.
CONDITION_NODE
)
{
if
(
!
showText
)
{
errorNodes
.
push
(
node
)
}
validateNode
(
node
.
childNode
,
errorNodes
)
}
if
(
type
==
NodeType
.
CONDITION_BRANCH_NODE
||
type
==
NodeType
.
PARALLEL_BRANCH_NODE
||
type
==
NodeType
.
INCLUSIVE_BRANCH_NODE
)
{
// 分支节点
// 1. 先校验各个分支
conditionNodes
?.
forEach
((
item
)
=>
{
validateNode
(
item
,
errorNodes
)
})
// 2. 校验孩子节点
validateNode
(
node
.
childNode
,
errorNodes
)
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue
View file @
c9b12c8b
<
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>
<SimpleProcessModel
:flow-node=
"simpleModel"
:readonly=
"true"
/>
</
template
>
<
script
setup
lang=
"ts"
>
import
ProcessNodeTree
from
'./ProcessNodeTree.vu
e'
import
{
useWatchNode
}
from
'./nod
e'
import
{
SimpleFlowNode
}
from
'./consts'
defineOptions
({
name
:
'SimpleProcess
Rend
er'
name
:
'SimpleProcess
View
er'
})
const
props
=
defineProps
({
flowNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
required
:
true
},
// 流程任务
tasks
:
{
type
:
Array
,
default
:
()
=>
[]
as
any
[]
}
})
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
// }
// })
const
simpleModel
=
useWatchNode
(
props
)
</
script
>
src/components/SimpleProcessDesignerV2/src/index.ts
View file @
c9b12c8b
...
...
@@ -2,4 +2,4 @@ import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
import
SimpleProcessViewer
from
'./SimpleProcessViewer.vue'
import
'../theme/simple-process-designer.scss'
export
{
SimpleProcessDesigner
,
SimpleProcessViewer
}
export
{
SimpleProcessDesigner
,
SimpleProcessViewer
}
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
View file @
c9b12c8b
.simple-flow-canvas
{
z-index
:
1
;
overflow
:
auto
;
background-color
:
#fafafa
;
// user-select: none;
.simple-flow-container
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
.top-area-container
{
position
:
sticky
;
inset
:
0
;
display
:
flex
;
width
:
100%
;
height
:
42px
;
z-index
:
1
;
// padding: 4px 0;
background-color
:
#fff
;
justify-content
:
flex-end
;
align-items
:
center
;
.top-actions
{
display
:
flex
;
margin
:
4px
;
margin-right
:
8px
;
align-items
:
center
;
.canvas-control
{
font-size
:
16px
;
.control-scale-group
{
display
:
inline-flex
;
align-items
:
center
;
margin-right
:
8px
;
.control-scale-button
{
display
:
inline-flex
;
width
:
28px
;
height
:
28px
;
padding
:
2px
;
text-align
:
center
;
cursor
:
pointer
;
justify-content
:
center
;
align-items
:
center
;
}
.control-scale-label
{
margin
:
0
4px
;
font-size
:
14px
;
}
}
}
}
}
.scale-container
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
margin-top
:
16px
;
background-color
:
#fafafa
;
transform-origin
:
50%
0
0
;
transform
:
scale
(
1
);
transition
:
transform
0
.3s
cubic-bezier
(
0
.645
,
0
.045
,
0
.355
,
1
);
// 节点容器 定义节点宽度
.node-container
{
width
:
200px
;
}
// 节点
.node-box
{
position
:
relative
;
display
:
flex
;
min-height
:
70px
;
padding
:
5px
10px
8px
;
cursor
:
pointer
;
background-color
:
#fff
;
flex-direction
:
column
;
border
:
2px
solid
transparent
;
border-radius
:
8px
;
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
{
opacity
:
1
;
}
.branch-node-move
{
display
:
flex
;
}
}
// 普通节点标题
.node-title-container
{
display
:
flex
;
padding
:
4px
;
cursor
:
pointer
;
border-radius
:
4px
4px
0
0
;
align-items
:
center
;
.node-title-icon
{
display
:
flex
;
align-items
:
center
;
&
.user-task
{
color
:
#ff943e
;
}
&
.copy-task
{
color
:
#3296fa
;
}
&
.start-user
{
color
:
#676565
;
}
}
.node-title
{
margin-left
:
4px
;
font-size
:
14px
;
font-weight
:
600
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
color
:
#1f1f1f
;
line-height
:
18px
;
&
:hover
{
border-bottom
:
1px
dashed
#f60
;
}
}
}
// 条件节点标题
.branch-node-title-container
{
display
:
flex
;
padding
:
4px
0
;
cursor
:
pointer
;
border-radius
:
4px
4px
0
0
;
align-items
:
center
;
justify-content
:
space-between
;
.input-max-width
{
max-width
:
115px
!
important
;
}
.branch-title
{
font-size
:
13px
;
font-weight
:
600
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
color
:
#f60
;
&
:hover
{
border-bottom
:
1px
dashed
#000
;
}
}
.branch-priority
{
min-width
:
50px
;
font-size
:
12px
;
}
}
.node-content
{
display
:
flex
;
min-height
:
32px
;
padding
:
4px
8px
;
margin-top
:
4px
;
line-height
:
32px
;
justify-content
:
space-between
;
align-items
:
center
;
color
:
#111f2c
;
background
:
rgba
(
0
,
0
,
0
,
0
.03
);
border-radius
:
4px
;
.node-text
{
display
:
-
webkit-box
;
overflow
:
hidden
;
font-size
:
14px
;
line-height
:
24px
;
text-overflow
:
ellipsis
;
word-break
:
break-all
;
-webkit-line-clamp
:
2
;
/* 这将限制文本显示为两行 */
-webkit-box-orient
:
vertical
;
}
}
//条件节点内容
.branch-node-content
{
display
:
flex
;
min-height
:
32px
;
padding
:
4px
0
;
margin-top
:
4px
;
line-height
:
32px
;
align-items
:
center
;
color
:
#111f2c
;
border-radius
:
4px
;
.branch-node-text
{
overflow
:
hidden
;
font-size
:
12px
;
line-height
:
24px
;
text-overflow
:
ellipsis
;
word-break
:
break-all
;
-webkit-line-clamp
:
2
;
/* 这将限制文本显示为两行 */
-webkit-box-orient
:
vertical
;
}
}
// 节点操作 :删除
.node-toolbar
{
opacity
:
0
;
position
:
absolute
;
top
:
-20px
;
right
:
0px
;
display
:
flex
;
.toolbar-icon
{
text-align
:
center
;
vertical-align
:
middle
;
}
}
// 条件节点左右移动
.branch-node-move
{
position
:
absolute
;
width
:
10px
;
cursor
:
pointer
;
display
:
none
;
align-items
:
center
;
height
:
100%
;
justify-content
:
center
;
}
.move-node-left
{
left
:
-2px
;
top
:
0px
;
background
:
rgba
(
126
,
134
,
142
,
0
.08
);
border-top-left-radius
:
8px
;
border-bottom-left-radius
:
8px
;
}
.move-node-right
{
right
:
-2px
;
top
:
0px
;
background
:
rgba
(
126
,
134
,
142
,
0
.08
);
border-top-right-radius
:
6px
;
border-bottom-right-radius
:
6px
;
}
}
.node-config-error
{
border-color
:
#ff5219
!
important
;
}
// 普通节点包装
.node-wrapper
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
}
// 节点连线处理
.node-handler-wrapper
{
position
:
relative
;
display
:
flex
;
height
:
70px
;
align-items
:
center
;
user-select
:
none
;
justify-content
:
center
;
flex-direction
:
column
;
&
:
:
before
{
position
:
absolute
;
top
:
0
;
z-index
:
0
;
width
:
2px
;
height
:
100%
;
margin
:
auto
;
background-color
:
#dedede
;
content
:
''
;
}
.node-handler
{
.add-icon
{
position
:
relative
;
top
:
-5px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
width
:
25px
;
height
:
25px
;
color
:
#fff
;
background-color
:
#0089ff
;
border-radius
:
50%
;
&
:hover
{
transform
:
scale
(
1
.1
);
}
}
}
.node-handler-arrow
{
position
:
absolute
;
bottom
:
0
;
left
:
50%
;
display
:
flex
;
transform
:
translateX
(
-50%
);
}
}
// 条件节点包装
.branch-node-wrapper
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
margin-top
:
16px
;
.branch-node-container
{
position
:
relative
;
display
:
flex
;
&
:
:
before
{
position
:
absolute
;
height
:
100%
;
width
:
4px
;
background-color
:
#fafafa
;
content
:
''
;
left
:
50%
;
transform
:
translate
(
-50%
);
}
.branch-node-add
{
position
:
absolute
;
top
:
-18px
;
left
:
50%
;
z-index
:
1
;
height
:
36px
;
padding
:
0
10px
;
font-size
:
12px
;
line-height
:
36px
;
border
:
2px
solid
#dedede
;
border-radius
:
18px
;
transform
:
translateX
(
-50%
);
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
;
flex-direction
:
column
;
align-items
:
center
;
min-width
:
280px
;
padding
:
40px
40px
0
;
background
:
transparent
;
border-top
:
2px
solid
#dedede
;
border-bottom
:
2px
solid
#dedede
;
&
:
:
before
{
position
:
absolute
;
width
:
2px
;
height
:
100%
;
margin
:
auto
;
inset
:
0
;
background-color
:
#dedede
;
content
:
''
;
}
}
// 覆盖条件节点第一个节点左上角的线
.branch-line-first-top
{
position
:
absolute
;
top
:
-5px
;
left
:
-1px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
// 覆盖条件节点第一个节点左下角的线
.branch-line-first-bottom
{
position
:
absolute
;
bottom
:
-5px
;
left
:
-1px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
// 覆盖条件节点最后一个节点右上角的线
.branch-line-last-top
{
position
:
absolute
;
top
:
-5px
;
right
:
-1px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
// 覆盖条件节点最后一个节点右下角的线
.branch-line-last-bottom
{
position
:
absolute
;
right
:
-1px
;
bottom
:
-5px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
}
}
.node-fixed-name
{
display
:
inline-block
;
width
:
auto
;
padding
:
0
4px
;
overflow
:
hidden
;
text-align
:
center
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
// 开始节点包装
.start-node-wrapper
{
position
:
relative
;
margin-top
:
16px
;
.start-node-container
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
.start-node-box
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
width
:
90px
;
height
:
36px
;
padding
:
3px
4px
;
color
:
#212121
;
cursor
:
pointer
;
background
:
#fafafa
;
border-radius
:
30px
;
box-shadow
:
0
1px
5px
0
rgba
(
10
,
30
,
65
,
0
.08
);
box-sizing
:
border-box
;
}
}
}
// 结束节点包装
.end-node-wrapper
{
margin-bottom
:
16px
;
.end-node-box
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
width
:
80px
;
height
:
36px
;
border
:
2px
solid
#fafafa
;
color
:
#212121
;
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
;
}
}
}
// 可编辑的 title 输入框
.editable-title-input
{
height
:
20px
;
max-width
:
145px
;
line-height
:
20px
;
font-size
:
12px
;
margin-left
:
4px
;
border
:
1px
solid
#d9d9d9
;
border-radius
:
4px
;
transition
:
all
0
.3s
;
&
:focus
{
border-color
:
#40a9ff
;
outline
:
0
;
box-shadow
:
0
0
0
2px
rgba
(
24
,
144
,
255
,
0
.2
);
}
}
}
}
}
//
.simple-flow-canvas {
//
z-index: 1;
//
overflow: auto;
//
background-color: #fafafa;
//
// user-select: none;
//
.simple-flow-container {
//
position: relative;
//
display: flex;
//
flex-direction: column;
//
justify-content: center;
//
align-items: center;
//
.top-area-container {
//
position: sticky;
//
inset: 0;
//
display: flex;
//
width: 100%;
//
height: 42px;
//
z-index: 1;
//
// padding: 4px 0;
//
background-color: #fff;
//
justify-content: flex-end;
//
align-items: center;
//
.top-actions {
//
display: flex;
//
margin: 4px;
//
margin-right: 8px;
//
align-items: center;
//
.canvas-control {
//
font-size: 16px;
//
.control-scale-group {
//
display: inline-flex;
//
align-items: center;
//
margin-right: 8px;
//
.control-scale-button {
//
display: inline-flex;
//
width: 28px;
//
height: 28px;
//
padding: 2px;
//
text-align: center;
//
cursor: pointer;
//
justify-content: center;
//
align-items: center;
//
}
//
.control-scale-label {
//
margin: 0 4px;
//
font-size: 14px;
//
}
//
}
//
}
//
}
//
}
//
.scale-container {
//
display: flex;
//
flex-direction: column;
//
justify-content: center;
//
align-items: center;
//
margin-top: 16px;
//
background-color: #fafafa;
//
transform-origin: 50% 0 0;
//
transform: scale(1);
//
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
//
// 节点容器 定义节点宽度
//
.node-container {
//
width: 200px;
//
}
//
// 节点
//
.node-box {
//
position: relative;
//
display: flex;
//
min-height: 70px;
//
padding: 5px 10px 8px;
//
cursor: pointer;
//
background-color: #fff;
//
flex-direction: column;
//
border: 2px solid transparent;
//
border-radius: 8px;
//
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 {
//
opacity: 1;
//
}
//
.branch-node-move {
//
display: flex;
//
}
//
}
//
// 普通节点标题
//
.node-title-container {
//
display: flex;
//
padding: 4px;
//
cursor: pointer;
//
border-radius: 4px 4px 0 0;
//
align-items: center;
//
.node-title-icon {
//
display: flex;
//
align-items: center;
//
&.user-task {
//
color: #ff943e;
//
}
//
&.copy-task {
//
color: #3296fa;
//
}
//
&.start-user {
//
color: #676565;
//
}
//
}
//
.node-title {
//
margin-left: 4px;
//
font-size: 14px;
//
font-weight: 600;
//
white-space: nowrap;
//
overflow: hidden;
//
text-overflow: ellipsis;
//
color: #1f1f1f;
//
line-height: 18px;
//
&:hover {
//
border-bottom: 1px dashed #f60;
//
}
//
}
//
}
//
// 条件节点标题
//
.branch-node-title-container {
//
display: flex;
//
padding: 4px 0;
//
cursor: pointer;
//
border-radius: 4px 4px 0 0;
//
align-items: center;
//
justify-content: space-between;
//
.input-max-width {
//
max-width: 115px !important;
//
}
//
.branch-title {
//
font-size: 13px;
//
font-weight: 600;
//
white-space: nowrap;
//
overflow: hidden;
//
text-overflow: ellipsis;
//
color: #f60;
//
&:hover {
//
border-bottom: 1px dashed #000;
//
}
//
}
//
.branch-priority {
//
min-width: 50px;
//
font-size: 12px;
//
}
//
}
//
.node-content {
//
display: flex;
//
min-height: 32px;
//
padding: 4px 8px;
//
margin-top: 4px;
//
line-height: 32px;
//
justify-content: space-between;
//
align-items: center;
//
color: #111f2c;
//
background: rgba(0, 0, 0, 0.03);
//
border-radius: 4px;
//
.node-text {
//
display: -webkit-box;
//
overflow: hidden;
//
font-size: 14px;
//
line-height: 24px;
//
text-overflow: ellipsis;
//
word-break: break-all;
//
-webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
//
-webkit-box-orient: vertical;
//
}
//
}
//
//条件节点内容
//
.branch-node-content {
//
display: flex;
//
min-height: 32px;
//
padding: 4px 0;
//
margin-top: 4px;
//
line-height: 32px;
//
align-items: center;
//
color: #111f2c;
//
border-radius: 4px;
//
.branch-node-text {
//
overflow: hidden;
//
font-size: 12px;
//
line-height: 24px;
//
text-overflow: ellipsis;
//
word-break: break-all;
//
-webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
//
-webkit-box-orient: vertical;
//
}
//
}
//
// 节点操作 :删除
//
.node-toolbar {
//
opacity: 0;
//
position: absolute;
//
top: -20px;
//
right: 0px;
//
display: flex;
//
.toolbar-icon {
//
text-align: center;
//
vertical-align: middle;
//
}
//
}
//
// 条件节点左右移动
//
.branch-node-move {
//
position: absolute;
//
width: 10px;
//
cursor: pointer;
//
display: none;
//
align-items: center;
//
height: 100%;
//
justify-content: center;
//
}
//
.move-node-left {
//
left: -2px;
//
top: 0px;
//
background: rgba(126, 134, 142, 0.08);
//
border-top-left-radius: 8px;
//
border-bottom-left-radius: 8px;
//
}
//
.move-node-right {
//
right: -2px;
//
top: 0px;
//
background: rgba(126, 134, 142, 0.08);
//
border-top-right-radius: 6px;
//
border-bottom-right-radius: 6px;
//
}
//
}
//
.node-config-error {
//
border-color: #ff5219 !important;
//
}
//
// 普通节点包装
//
.node-wrapper {
//
display: flex;
//
flex-direction: column;
//
justify-content: center;
//
align-items: center;
//
}
//
// 节点连线处理
//
.node-handler-wrapper {
//
position: relative;
//
display: flex;
//
height: 70px;
//
align-items: center;
//
user-select: none;
//
justify-content: center;
//
flex-direction: column;
//
&::before {
//
position: absolute;
// top:
0;
//
z-index: 0;
//
width: 2px;
//
height: 100%;
//
margin: auto;
//
background-color: #dedede;
//
content: '';
//
}
//
.node-handler {
//
.add-icon {
//
position: relative;
//
top: -5px;
//
display: flex;
//
align-items: center;
//
justify-content: center;
//
cursor: pointer;
//
width: 25px;
//
height: 25px;
//
color: #fff;
//
background-color: #0089ff;
//
border-radius: 50%;
//
&:hover {
//
transform: scale(1.1);
//
}
//
}
//
}
//
.node-handler-arrow {
//
position: absolute;
//
bottom: 0;
//
left: 50%;
//
display: flex;
//
transform: translateX(-50%);
//
}
//
}
//
// 条件节点包装
//
.branch-node-wrapper {
//
position: relative;
//
display: flex;
//
flex-direction: column;
//
justify-content: center;
//
align-items: center;
//
margin-top: 16px;
//
.branch-node-container {
//
position: relative;
//
display: flex;
//
&::before {
//
position: absolute;
//
height: 100%;
//
width: 4px;
//
background-color: #fafafa;
//
content: '';
//
left: 50%;
//
transform: translate(-50%);
//
}
//
.branch-node-add {
//
position: absolute;
//
top: -18px;
//
left: 50%;
//
z-index: 1;
//
height: 36px;
//
padding: 0 10px;
//
font-size: 12px;
//
line-height: 36px;
//
border: 2px solid #dedede;
//
border-radius: 18px;
//
transform: translateX(-50%);
//
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;
//
flex-direction: column;
//
align-items: center;
//
min-width: 280px;
//
padding: 40px 40px 0;
//
background: transparent;
//
border-top: 2px solid #dedede;
//
border-bottom: 2px solid #dedede;
//
&::before {
//
position: absolute;
//
width: 2px;
//
height: 100%;
//
margin: auto;
//
inset: 0;
//
background-color: #dedede;
//
content: '';
//
}
//
}
//
// 覆盖条件节点第一个节点左上角的线
//
.branch-line-first-top {
//
position: absolute;
//
top: -5px;
//
left: -1px;
//
width: 50%;
//
height: 7px;
//
background-color: #fafafa;
//
content: '';
//
}
//
// 覆盖条件节点第一个节点左下角的线
//
.branch-line-first-bottom {
//
position: absolute;
//
bottom: -5px;
//
left: -1px;
//
width: 50%;
//
height: 7px;
//
background-color: #fafafa;
//
content: '';
//
}
//
// 覆盖条件节点最后一个节点右上角的线
//
.branch-line-last-top {
//
position: absolute;
//
top: -5px;
//
right: -1px;
//
width: 50%;
//
height: 7px;
//
background-color: #fafafa;
//
content: '';
//
}
//
// 覆盖条件节点最后一个节点右下角的线
//
.branch-line-last-bottom {
//
position: absolute;
//
right: -1px;
//
bottom: -5px;
//
width: 50%;
//
height: 7px;
//
background-color: #fafafa;
//
content: '';
//
}
//
}
//
}
//
.node-fixed-name {
//
display: inline-block;
//
width: auto;
//
padding: 0 4px;
//
overflow: hidden;
//
text-align: center;
//
text-overflow: ellipsis;
//
white-space: nowrap;
//
}
//
// 开始节点包装
//
.start-node-wrapper {
//
position: relative;
//
margin-top: 16px;
//
.start-node-container {
//
display: flex;
//
flex-direction: column;
//
justify-content: center;
//
align-items: center;
//
.start-node-box {
//
display: flex;
//
justify-content: center;
//
align-items: center;
//
width: 90px;
//
height: 36px;
//
padding: 3px 4px;
//
color: #212121;
//
cursor: pointer;
//
background: #fafafa;
//
border-radius: 30px;
//
box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
//
box-sizing: border-box;
//
}
//
}
//
}
//
// 结束节点包装
//
.end-node-wrapper {
//
margin-bottom: 16px;
//
.end-node-box {
//
display: flex;
//
justify-content: center;
//
align-items: center;
//
width: 80px;
//
height: 36px;
//
border: 2px solid #fafafa;
//
color: #212121;
//
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;
//
}
//
}
//
}
//
// 可编辑的 title 输入框
//
.editable-title-input {
//
height: 20px;
//
max-width: 145px;
//
line-height: 20px;
//
font-size: 12px;
//
margin-left: 4px;
//
border: 1px solid #d9d9d9;
//
border-radius: 4px;
//
transition: all 0.3s;
//
&:focus {
//
border-color: #40a9ff;
//
outline: 0;
//
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
//
}
//
}
//
}
//
}
//
}
// 配置节点头部
.config-header
{
...
...
@@ -740,6 +740,531 @@
}
}
.simple-process-model-container
{
height
:
100%
;
overflow
:
auto
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
padding-top
:
16px
;
background-color
:
#fafafa
;
transform-origin
:
50%
0
0
;
transform
:
scale
(
1
);
transition
:
transform
0
.3s
cubic-bezier
(
0
.645
,
0
.045
,
0
.355
,
1
);
background
:
url(@/assets/svgs/bpm/simple-process-bg.svg)
0
0
repeat
;
// 节点容器 定义节点宽度
.node-container
{
width
:
200px
;
}
// 节点
.node-box
{
position
:
relative
;
display
:
flex
;
min-height
:
70px
;
padding
:
5px
10px
8px
;
cursor
:
pointer
;
background-color
:
#fff
;
flex-direction
:
column
;
border
:
2px
solid
transparent
;
border-radius
:
8px
;
box-shadow
:
0
1px
4px
0
rgb
(
10
30
65
/
16%
);
transition
:
all
0
.1s
cubic-bezier
(
0
.645
,
0
.045
,
0
.355
,
1
);
&
.status-pass
{
background-color
:
#a9da90
;
border-color
:
#67c23a
;
}
&
.status-pass
:hover
{
border-color
:
#67c23a
;
}
&
.status-running
{
background-color
:
#e7f0fe
;
border-color
:
#5a9cf8
;
}
&
.status-running
:hover
{
border-color
:
#5a9cf8
;
}
&
.status-reject
{
background-color
:
#f6e5e5
;
border-color
:
#e47470
;
}
&
.status-reject
:hover
{
border-color
:
#e47470
;
}
&
:hover
{
border-color
:
#0089ff
;
.node-toolbar
{
opacity
:
1
;
}
.branch-node-move
{
display
:
flex
;
}
}
// 普通节点标题
.node-title-container
{
display
:
flex
;
padding
:
4px
;
cursor
:
pointer
;
border-radius
:
4px
4px
0
0
;
align-items
:
center
;
.node-title-icon
{
display
:
flex
;
align-items
:
center
;
&
.user-task
{
color
:
#ff943e
;
}
&
.copy-task
{
color
:
#3296fa
;
}
&
.start-user
{
color
:
#676565
;
}
}
.node-title
{
margin-left
:
4px
;
overflow
:
hidden
;
font-size
:
14px
;
font-weight
:
600
;
line-height
:
18px
;
color
:
#1f1f1f
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
&
:hover
{
border-bottom
:
1px
dashed
#f60
;
}
}
}
// 条件节点标题
.branch-node-title-container
{
display
:
flex
;
padding
:
4px
0
;
cursor
:
pointer
;
border-radius
:
4px
4px
0
0
;
align-items
:
center
;
justify-content
:
space-between
;
.input-max-width
{
max-width
:
115px
!
important
;
}
.branch-title
{
overflow
:
hidden
;
font-size
:
13px
;
font-weight
:
600
;
color
:
#f60
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
&
:hover
{
border-bottom
:
1px
dashed
#000
;
}
}
.branch-priority
{
min-width
:
50px
;
font-size
:
12px
;
}
}
.node-content
{
display
:
flex
;
min-height
:
32px
;
padding
:
4px
8px
;
margin-top
:
4px
;
line-height
:
32px
;
justify-content
:
space-between
;
align-items
:
center
;
color
:
#111f2c
;
background
:
rgb
(
0
0
0
/
3%
);
border-radius
:
4px
;
.node-text
{
display
:
-
webkit-box
;
overflow
:
hidden
;
font-size
:
14px
;
line-height
:
24px
;
text-overflow
:
ellipsis
;
word-break
:
break-all
;
-webkit-line-clamp
:
2
;
/* 这将限制文本显示为两行 */
-webkit-box-orient
:
vertical
;
}
}
//条件节点内容
.branch-node-content
{
display
:
flex
;
min-height
:
32px
;
padding
:
4px
0
;
margin-top
:
4px
;
line-height
:
32px
;
align-items
:
center
;
color
:
#111f2c
;
border-radius
:
4px
;
.branch-node-text
{
overflow
:
hidden
;
font-size
:
12px
;
line-height
:
24px
;
text-overflow
:
ellipsis
;
word-break
:
break-all
;
-webkit-line-clamp
:
2
;
/* 这将限制文本显示为两行 */
-webkit-box-orient
:
vertical
;
}
}
// 节点操作 :删除
.node-toolbar
{
position
:
absolute
;
top
:
-20px
;
right
:
0
;
display
:
flex
;
opacity
:
0
;
.toolbar-icon
{
text-align
:
center
;
vertical-align
:
middle
;
}
}
// 条件节点左右移动
.branch-node-move
{
position
:
absolute
;
display
:
none
;
width
:
10px
;
height
:
100%
;
cursor
:
pointer
;
align-items
:
center
;
justify-content
:
center
;
}
.move-node-left
{
top
:
0
;
left
:
-2px
;
background
:
rgb
(
126
134
142
/
8%
);
border-bottom-left-radius
:
8px
;
border-top-left-radius
:
8px
;
}
.move-node-right
{
top
:
0
;
right
:
-2px
;
background
:
rgb
(
126
134
142
/
8%
);
border-top-right-radius
:
6px
;
border-bottom-right-radius
:
6px
;
}
}
.node-config-error
{
border-color
:
#ff5219
!
important
;
}
// 普通节点包装
.node-wrapper
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
}
// 节点连线处理
.node-handler-wrapper
{
position
:
relative
;
display
:
flex
;
height
:
70px
;
align-items
:
center
;
user-select
:
none
;
justify-content
:
center
;
flex-direction
:
column
;
&
:
:
before
{
position
:
absolute
;
top
:
0
;
z-index
:
0
;
width
:
2px
;
height
:
100%
;
margin
:
auto
;
background-color
:
#dedede
;
content
:
''
;
}
.node-handler
{
.add-icon
{
position
:
relative
;
top
:
-5px
;
display
:
flex
;
width
:
25px
;
height
:
25px
;
color
:
#fff
;
cursor
:
pointer
;
background-color
:
#0089ff
;
border-radius
:
50%
;
align-items
:
center
;
justify-content
:
center
;
&
:hover
{
transform
:
scale
(
1
.1
);
}
}
}
.node-handler-arrow
{
position
:
absolute
;
bottom
:
0
;
left
:
50%
;
display
:
flex
;
transform
:
translateX
(
-50%
);
}
}
// 条件节点包装
.branch-node-wrapper
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
margin-top
:
16px
;
.branch-node-container
{
position
:
relative
;
display
:
flex
;
&
:
:
before
{
position
:
absolute
;
left
:
50%
;
width
:
4px
;
height
:
100%
;
background-color
:
#fafafa
;
content
:
''
;
transform
:
translate
(
-50%
);
}
.branch-node-add
{
position
:
absolute
;
top
:
-18px
;
left
:
50%
;
z-index
:
1
;
height
:
36px
;
padding
:
0
10px
;
font-size
:
12px
;
line-height
:
36px
;
border
:
2px
solid
#dedede
;
border-radius
:
18px
;
transform
:
translateX
(
-50%
);
transform-origin
:
center
center
;
}
.branch-node-readonly
{
position
:
absolute
;
top
:
-18px
;
left
:
50%
;
z-index
:
1
;
display
:
flex
;
width
:
36px
;
height
:
36px
;
background-color
:
#fff
;
border
:
2px
solid
#dedede
;
border-radius
:
50%
;
transform
:
translateX
(
-50%
);
align-items
:
center
;
justify-content
:
center
;
transform-origin
:
center
center
;
&
.status-pass
{
background-color
:
#e9f4e2
;
border-color
:
#6bb63c
;
}
&
.status-pass
:hover
{
border-color
:
#6bb63c
;
}
.icon-size
{
font-size
:
22px
;
color
:
#67c23a
;
}
}
.branch-node-item
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
min-width
:
280px
;
padding
:
40px
40px
0
;
background
:
transparent
;
border-top
:
2px
solid
#dedede
;
border-bottom
:
2px
solid
#dedede
;
&
:
:
before
{
position
:
absolute
;
width
:
2px
;
height
:
100%
;
margin
:
auto
;
inset
:
0
;
background-color
:
#dedede
;
content
:
''
;
}
}
// 覆盖条件节点第一个节点左上角的线
.branch-line-first-top
{
position
:
absolute
;
top
:
-5px
;
left
:
-1px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
// 覆盖条件节点第一个节点左下角的线
.branch-line-first-bottom
{
position
:
absolute
;
bottom
:
-5px
;
left
:
-1px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
// 覆盖条件节点最后一个节点右上角的线
.branch-line-last-top
{
position
:
absolute
;
top
:
-5px
;
right
:
-1px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
// 覆盖条件节点最后一个节点右下角的线
.branch-line-last-bottom
{
position
:
absolute
;
right
:
-1px
;
bottom
:
-5px
;
width
:
50%
;
height
:
7px
;
background-color
:
#fafafa
;
content
:
''
;
}
}
}
.node-fixed-name
{
display
:
inline-block
;
width
:
auto
;
padding
:
0
4px
;
overflow
:
hidden
;
text-align
:
center
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
// 开始节点包装
.start-node-wrapper
{
position
:
relative
;
margin-top
:
16px
;
.start-node-container
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
.start-node-box
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
width
:
90px
;
height
:
36px
;
padding
:
3px
4px
;
color
:
#212121
;
cursor
:
pointer
;
background
:
#fafafa
;
border-radius
:
30px
;
box-shadow
:
0
1px
5px
0
rgb
(
10
30
65
/
8%
);
box-sizing
:
border-box
;
}
}
}
// 结束节点包装
.end-node-wrapper
{
margin-bottom
:
16px
;
.end-node-box
{
display
:
flex
;
width
:
80px
;
height
:
36px
;
color
:
#212121
;
border
:
2px
solid
#fafafa
;
border-radius
:
30px
;
box-shadow
:
0
1px
5px
0
rgb
(
10
30
65
/
8%
);
box-sizing
:
border-box
;
justify-content
:
center
;
align-items
:
center
;
&
.status-pass
{
background-color
:
#a9da90
;
border-color
:
#6bb63c
;
}
&
.status-pass
:hover
{
border-color
:
#6bb63c
;
}
&
.status-reject
{
background-color
:
#f6e5e5
;
border-color
:
#e47470
;
}
&
.status-reject
:hover
{
border-color
:
#e47470
;
}
&
.status-cancel
{
background-color
:
#eaeaeb
;
border-color
:
#919398
;
}
&
.status-cancel
:hover
{
border-color
:
#919398
;
}
}
}
// 可编辑的 title 输入框
.editable-title-input
{
height
:
20px
;
max-width
:
145px
;
margin-left
:
4px
;
font-size
:
12px
;
line-height
:
20px
;
border
:
1px
solid
#d9d9d9
;
border-radius
:
4px
;
transition
:
all
0
.3s
;
&
:focus
{
border-color
:
#40a9ff
;
outline
:
0
;
box-shadow
:
0
0
0
2px
rgb
(
24
144
255
/
20%
);
}
}
}
// iconfont 样式
@font-face
{
font-family
:
'iconfont'
;
/* Project id 4495938 */
...
...
src/router/modules/remaining.ts
View file @
c9b12c8b
...
...
@@ -267,9 +267,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
path
:
'manager/simple/
workflow/model/edit
'
,
component
:
()
=>
import
(
'@/views/bpm/simple
Workflow/index
.vue'
),
name
:
'Simple
WorkflowDesignEditor
'
,
path
:
'manager/simple/
model
'
,
component
:
()
=>
import
(
'@/views/bpm/simple
/SimpleModelDesign
.vue'
),
name
:
'Simple
ModelDesign
'
,
meta
:
{
noCache
:
true
,
hidden
:
true
,
...
...
src/views/bpm/model/CategoryDraggableModel.vue
View file @
c9b12c8b
...
...
@@ -342,7 +342,7 @@ const handleDesign = (row: any) => {
})
}
else
{
push
({
name
:
'Simple
WorkflowDesignEditor
'
,
name
:
'Simple
ModelDesign
'
,
query
:
{
modelId
:
row
.
id
}
...
...
@@ -492,9 +492,11 @@ watch(
<
style
lang=
"scss"
>
.rename-dialog.el-dialog
{
padding
:
0
!important
;
.el-dialog__header
{
border-bottom
:
none
;
}
.el-dialog__footer
{
border-top
:
none
!important
;
}
...
...
@@ -503,8 +505,8 @@ watch(
<
style
lang=
"scss"
scoped
>
:deep
()
{
.el-table__cell
{
border-bottom
:
none
!important
;
overflow
:
hidden
;
border-bottom
:
none
!important
;
}
}
</
style
>
src/views/bpm/model/index_old.vue
View file @
c9b12c8b
...
...
@@ -338,7 +338,7 @@ const handleDesign = (row: any) => {
})
}
else
{
push
({
name
:
'Simple
WorkflowDesignEditor
'
,
name
:
'Simple
ModelDesign
'
,
query
:
{
modelId
:
row
.
id
}
...
...
src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue
View file @
c9b12c8b
<
template
>
<
el-card
v-loading=
"loading"
class=
"box-card
"
>
<SimpleProcessViewer
:flow-node=
"simpleModel"
/>
</
el-card
>
<
div
v-loading=
"loading"
class=
"mb-20px
"
>
<SimpleProcessViewer
:flow-node=
"simpleModel"
:tasks=
"tasks"
/>
</
div
>
</
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'
...
...
@@ -18,10 +17,7 @@ const props = defineProps({
id
:
propTypes
.
string
// 流程实例的编号
})
// const view = ref({
// simpleModel: undefined
// }) // simple 模型数据
const
tasks
=
ref
([])
const
simpleModel
=
ref
()
/** 只有 loading 完成时,才去加载流程列表 */
...
...
@@ -31,6 +27,7 @@ watch(
if
(
value
&&
props
.
id
)
{
const
modelView
=
await
ProcessInstanceApi
.
getProcessInstanceBpmnModelView
(
props
.
id
)
if
(
modelView
)
{
tasks
.
value
=
modelView
.
tasks
;
// 已经拒绝的活动节点编号集合,只包括 UserTask
const
rejectedTaskActivityIds
:
string
[]
=
modelView
.
rejectedTaskActivityIds
// 进行中的活动节点编号集合, 只包括 UserTask
...
...
@@ -47,7 +44,7 @@ watch(
finishedActivityIds
,
finishedSequenceFlowActivityIds
)
console
.
log
(
"modelView.simpleModel==>"
,
modelView
.
simpleModel
)
console
.
log
(
'modelView.simpleModel==>'
,
modelView
.
simpleModel
)
simpleModel
.
value
=
modelView
.
simpleModel
}
}
...
...
@@ -60,8 +57,7 @@ const setSimpleModelNodeTaskStatus = (
rejectedTaskActivityIds
:
string
[],
unfinishedTaskActivityIds
:
string
[],
finishedActivityIds
:
string
[],
finishedSequenceFlowActivityIds
:
string
[],
finishedSequenceFlowActivityIds
:
string
[]
)
=>
{
if
(
!
simpleModel
)
{
return
...
...
@@ -75,7 +71,7 @@ const setSimpleModelNodeTaskStatus = (
}
return
}
// 审批节点
if
(
simpleModel
.
type
===
NodeType
.
START_USER_NODE
||
...
...
@@ -84,7 +80,7 @@ const setSimpleModelNodeTaskStatus = (
simpleModel
.
activityStatus
=
TaskStatusEnum
.
NOT_START
if
(
rejectedTaskActivityIds
.
includes
(
simpleModel
.
id
))
{
simpleModel
.
activityStatus
=
TaskStatusEnum
.
REJECT
}
else
if
(
unfinishedTaskActivityIds
.
includes
(
simpleModel
.
id
))
{
}
else
if
(
unfinishedTaskActivityIds
.
includes
(
simpleModel
.
id
))
{
simpleModel
.
activityStatus
=
TaskStatusEnum
.
RUNNING
}
else
if
(
finishedActivityIds
.
includes
(
simpleModel
.
id
))
{
simpleModel
.
activityStatus
=
TaskStatusEnum
.
APPROVE
...
...
@@ -152,9 +148,3 @@ const setSimpleModelNodeTaskStatus = (
// }
// )
</
script
>
<
style
>
.box-card
{
width
:
100%
;
margin-bottom
:
20px
;
}
</
style
>
src/views/bpm/processInstance/detail/index.vue
View file @
c9b12c8b
...
...
@@ -75,11 +75,14 @@
<!-- 流程图 -->
<el-tab-pane
label=
"流程图"
name=
"diagram"
>
<div
class=
"form-scroll-area"
>
<ProcessInstanceBpmnViewer
<ProcessInstanceSimpleViewer
v-show=
"processDefinition.modelType && processDefinition.modelType === BpmModelType.SIMPLE"
:id=
"`$
{id}`"
:loading="processInstanceLoading"
:show-header="false"
/>
<ProcessInstanceBpmnViewer
v-show=
"processDefinition.modelType && processDefinition.modelType === BpmModelType.BPMN"
:id=
"`$
{id}`" :loading="processInstanceLoading" />
</div>
</el-tab-pane>
...
...
@@ -122,11 +125,13 @@
<
script
lang=
"ts"
setup
>
import
{
formatDate
}
from
'@/utils/formatTime'
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
{
BpmModelType
}
from
'@/utils/constants'
import
{
setConfAndFields2
}
from
'@/utils/formCreate'
import
type
{
ApiAttrs
}
from
'@form-create/element-ui/types/config'
import
*
as
ProcessInstanceApi
from
'@/api/bpm/processInstance'
import
*
as
TaskApi
from
'@/api/bpm/task'
import
ProcessInstanceBpmnViewer
from
'./ProcessInstanceBpmnViewer.vue'
import
ProcessInstanceSimpleViewer
from
'./ProcessInstanceSimpleViewer.vue'
import
ProcessInstanceTaskList
from
'./ProcessInstanceTaskList.vue'
import
ProcessInstanceOperationButton
from
'./ProcessInstanceOperationButton.vue'
import
ProcessInstanceTimeline
from
'./ProcessInstanceTimeline.vue'
...
...
src/views/bpm/simple
Workflow/index
.vue
→
src/views/bpm/simple
/SimpleModelDesign
.vue
View file @
c9b12c8b
<
template
>
<ContentWrap
:bodyStyle=
"
{ padding: '0px 0px' }" class="position-relative">
<SimpleProcessDesigner
:model-id=
"modelId"
/>
</ContentWrap>
<ContentWrap
:bodyStyle=
"
{ padding: '20px 16px' }">
<SimpleProcessDesigner
:model-id=
"modelId"
@
success=
"close"
/>
</ContentWrap>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
SimpleProcessDesigner
}
from
'@/components/SimpleProcessDesignerV2/src/'
defineOptions
({
name
:
'Simple
WorkflowDesignEditor
'
name
:
'Simple
ModelDesign
'
})
const
router
=
useRouter
()
// 路由
const
{
query
}
=
useRoute
()
// 路由的查询
const
modelId
=
query
.
modelId
as
string
const
close
=
()
=>
{
router
.
push
({
path
:
'/bpm/manager/model'
})
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
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