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
93141792
authored
Apr 27, 2024
by
jason
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
仿钉钉流程设计器-前端重构
parent
2589fbe5
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
1084 additions
and
154 deletions
+1084
-154
src/api/bpm/model/index.ts
+1
-1
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
+70
-0
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
+42
-0
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
+140
-0
src/components/SimpleProcessDesignerV2/src/consts.ts
+73
-0
src/components/SimpleProcessDesignerV2/src/index.ts
+5
-0
src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue
+13
-0
src/components/SimpleProcessDesignerV2/src/nodes/StartEventNode.vue
+35
-0
src/components/SimpleProcessDesignerV2/theme/iconfont.ttf
+0
-0
src/components/SimpleProcessDesignerV2/theme/iconfont.woff
+0
-0
src/components/SimpleProcessDesignerV2/theme/iconfont.woff2
+0
-0
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
+522
-0
src/views/bpm/simpleWorkflow/index.vue
+14
-153
src/views/bpm/simpleWorkflow/index1.vue
+169
-0
No files found.
src/api/bpm/model/index.ts
View file @
93141792
...
...
@@ -29,7 +29,7 @@ export const getModelPage = async (params) => {
return
await
request
.
get
({
url
:
'/bpm/model/page'
,
params
})
}
export
const
getModel
=
async
(
id
:
number
)
=>
{
export
const
getModel
=
async
(
id
:
string
)
=>
{
return
await
request
.
get
({
url
:
'/bpm/model/get?id='
+
id
})
}
...
...
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
0 → 100644
View file @
93141792
<
template
>
<div
class=
"node-handler-wrapper"
>
<div
class=
"node-handler"
v-if=
"props.showAdd"
>
<el-popover
trigger=
"hover"
v-model:visible=
"popoverShow"
placement=
"right-start"
width=
"auto"
>
<div
class=
"handler-item-wrapper"
>
<div
class=
"handler-item"
@
click=
"addNode(NodeType.USER_TASK_NODE)"
>
<div
class=
"approve handler-item-icon"
>
<span
class=
"iconfont icon-approve icon-size"
></span>
</div>
<div
class=
"handler-item-text"
>
审批人
</div>
</div>
<!--
<div
class=
"handler-item"
@
click=
"addNode(NodeType.CONDITION_NODE)"
>
<div
class=
"handler-item-icon"
>
<span
class=
"iconfont icon-size icon-exclusive"
></span>
</div>
<div
class=
"handler-item-text"
>
条件分支
</div>
</div>
-->
</div>
<template
#
reference
>
<div
class=
"add-icon"
><Icon
icon=
"ep:plus"
/></div>
</
template
>
</el-popover>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
SimpleFlowNode
,
NodeType
,
NODE_DEFAULT_NAME
}
from
'./consts'
import
{
generateUUID
}
from
'@/utils'
defineOptions
({
name
:
'NodeHandler'
})
const
popoverShow
=
ref
(
false
)
const
props
=
defineProps
({
childNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
default
:
null
},
showAdd
:
{
// 是否显示添加节点
type
:
Boolean
,
default
:
true
}
})
const
emits
=
defineEmits
([
'update:childNode'
])
const
addNode
=
(
type
:
number
)
=>
{
popoverShow
.
value
=
false
if
(
type
===
NodeType
.
USER_TASK_NODE
)
{
const
data
:
SimpleFlowNode
=
{
id
:
generateUUID
(),
name
:
NODE_DEFAULT_NAME
.
get
(
NodeType
.
USER_TASK_NODE
)
as
string
,
showText
:
''
,
type
:
NodeType
.
USER_TASK_NODE
,
// 审批节点配置
attributes
:
{
approveMethod
:
1
,
candidateStrategy
:
30
,
candidateParam
:
undefined
},
childNode
:
props
.
childNode
}
emits
(
'update:childNode'
,
data
)
}
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
0 → 100644
View file @
93141792
<
template
>
<!-- 开始节点 -->
<StartEventNode
v-if=
"currentNode && currentNode.type === NodeType.START_EVENT_NODE"
:flow-node =
"currentNode"
/>
<!-- 递归显示子节点 -->
<ProcessNodeTree
v-if=
"currentNode && currentNode.childNode"
v-model:flow-node=
"currentNode.childNode"
/>
<!-- 结束节点 -->
<EndEventNode
v-if=
"currentNode && currentNode.type === NodeType.END_EVENT_NODE"
/>
</
template
>
<
script
setup
lang=
'ts'
>
import
StartEventNode
from
'./nodes/StartEventNode.vue'
;
import
EndEventNode
from
'./nodes/EndEventNode.vue'
;
import
{
SimpleFlowNode
,
NodeType
}
from
'./consts'
;
defineOptions
({
name
:
'ProcessNodeTree'
})
const
props
=
defineProps
({
flowNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
default
:
()
=>
null
}
})
const
emits
=
defineEmits
([
'update:flowNode'
])
const
currentNode
=
ref
<
SimpleFlowNode
>
(
props
.
flowNode
);
// 重要:监控节点变化. 能动态新增、删除节点
watch
(()
=>
props
.
flowNode
,
(
newValue
)
=>
{
console
.
log
(
"Flow Nodes changed"
,
newValue
);
currentNode
.
value
=
newValue
;
});
// const handleModelValueUpdate = (updateValue) => {
// console.log('handleModelValueUpdate', updateValue)
// emits('update:flowNode', updateValue);
// }
</
script
>
<
style
lang=
'scss'
scoped
>
</
style
>
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
0 → 100644
View file @
93141792
<
template
>
<div
class=
"simple-flow-canvas"
>
<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>
<Dialog
v-model=
"errorDialogVisible"
title=
"保存失败"
width=
"400"
:fullscreen=
"false"
>
<div
v-for=
"(item, index) in errorNodes"
:key=
"index"
>
{{
item
.
name
}}
</div>
<template
#
footer
>
<el-button
type=
"primary"
@
click=
"errorDialogVisible = false"
>
知道了
</el-button>
</
template
>
</Dialog>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
ProcessNodeTree
from
'./ProcessNodeTree.vue'
;
import
{
saveBpmSimpleModel
,
getBpmSimpleModel
}
from
'@/api/bpm/simple'
import
{
SimpleFlowNode
,
NodeType
}
from
'./consts'
defineOptions
({
name
:
'SimpleProcessDesigner'
})
const
router
=
useRouter
()
// 路由
const
props
=
defineProps
({
modelId
:
String
})
const
message
=
useMessage
()
// 国际化
const
processNodeTree
=
ref
<
SimpleFlowNode
>
({
name
:
'开始'
,
type
:
NodeType
.
START_EVENT_NODE
,
id
:
'StartEvent_1'
,
childNode
:
{
id
:
'EndEvent_1'
,
name
:
'结束'
,
type
:
NodeType
.
END_EVENT_NODE
}
})
const
errorDialogVisible
=
ref
(
false
)
let
errorNodes
:
SimpleFlowNode
[]
=
[]
const
saveSimpleFlowModel
=
async
()
=>
{
console
.
log
(
'processNodeTree===>'
,
processNodeTree
.
value
)
if
(
!
props
.
modelId
)
{
message
.
error
(
'缺少模型 modelId 编号'
)
return
}
errorNodes
=
[]
validateNode
(
processNodeTree
.
value
,
errorNodes
)
console
.
log
(
'errorNodes is '
,
errorNodes
)
if
(
errorNodes
.
length
>
0
)
{
errorDialogVisible
.
value
=
true
}
// const data = {
// modelId: props.modelId,
// simpleModelBody: simpleWorkFlowNodes.value
// }
// console.log('request json data1 is ', data)
// const result = await saveBpmSimpleModel(data)
// console.log('save the result is ', result)
// if (result) {
// message.success('修改成功')
// close()
// } else {
// message.alert('修改失败')
// }
}
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_EVENT_NODE
)
{
validateNode
(
node
.
childNode
,
errorNodes
)
}
if
(
type
===
NodeType
.
USER_TASK_NODE
)
{
if
(
!
showText
)
{
errorNodes
.
push
(
node
)
}
validateNode
(
node
.
childNode
,
errorNodes
)
}
if
(
type
===
NodeType
.
CONDITION_NODE
)
{
if
(
!
showText
)
{
errorNodes
.
push
(
node
)
}
validateNode
(
node
.
childNode
,
errorNodes
)
}
if
(
type
==
NodeType
.
EXCLUSIVE_NODE
)
{
conditionNodes
?.
forEach
((
item
)
=>
{
validateNode
(
item
,
errorNodes
)
})
validateNode
(
node
.
childNode
,
errorNodes
)
}
}
}
const
close
=
()
=>
{
router
.
push
({
path
:
'/bpm/manager/model'
})
}
let
scaleValue
=
ref
(
100
)
const
zoomOut
=
()
=>
{
if
(
scaleValue
.
value
==
300
)
{
return
}
scaleValue
.
value
+=
10
}
const
zoomIn
=
()
=>
{
if
(
scaleValue
.
value
==
50
)
{
return
}
scaleValue
.
value
-=
10
}
onMounted
(
async
()
=>
{
const
result
=
await
getBpmSimpleModel
(
props
.
modelId
)
if
(
result
)
{
console
.
log
(
'get the result is '
,
result
)
processNodeTree
.
value
=
result
}
})
</
script
>
src/components/SimpleProcessDesignerV2/src/consts.ts
0 → 100644
View file @
93141792
// @ts-ignore
import
{
DictDataVO
}
from
'@/api/system/dict/types'
export
enum
NodeType
{
/**
* 发起人节点
*/
START_EVENT_NODE
=
0
,
/**
* 结束节点
*/
END_EVENT_NODE
=
-
2
,
/**
* 审批人节点
*/
USER_TASK_NODE
=
1
,
/**
* 条件节点
*/
CONDITION_NODE
=
3
,
/**
* 条件分支节点
*/
EXCLUSIVE_NODE
=
4
,
/**
* 并行分支分叉节点
*/
PARALLEL_NODE_FORK
=
5
,
/**
* 并行分支聚合
*/
PARALLEL_NODE_JOIN
=
6
,
/**
* 包容分支分叉节点
*/
INCLUSIVE_NODE_FORK
=
7
,
/**
* 包容分支聚合节点
*/
INCLUSIVE_NODE_JOIN
=
8
}
export
type
SimpleFlowNode
=
{
id
:
string
,
type
:
NodeType
,
name
:
string
,
showText
?:
string
,
attributes
?:
any
,
// 孩子节点
childNode
?:
SimpleFlowNode
,
// 条件节点
conditionNodes
?:
SimpleFlowNode
[]
}
export
const
NODE_DEFAULT_TEXT
=
new
Map
<
number
,
string
>
()
NODE_DEFAULT_TEXT
.
set
(
NodeType
.
USER_TASK_NODE
,
'请配置该节点'
)
NODE_DEFAULT_TEXT
.
set
(
NodeType
.
CONDITION_NODE
,
'请设置条件'
)
export
const
NODE_DEFAULT_NAME
=
new
Map
<
number
,
string
>
()
NODE_DEFAULT_NAME
.
set
(
NodeType
.
USER_TASK_NODE
,
'审批人'
)
NODE_DEFAULT_NAME
.
set
(
NodeType
.
CONDITION_NODE
,
'条件'
)
export
const
APPROVE_METHODS
:
DictDataVO
[]
=
[
{
label
:
'单人审批'
,
value
:
1
},
{
label
:
'多人会签(需所有审批人同意)'
,
value
:
2
},
{
label
:
'多人或签(一名审批人同意即可)'
,
value
:
3
},
{
label
:
'依次审批(按顺序依次审批)'
,
value
:
4
}
// TODO 更多的类型
]
\ No newline at end of file
src/components/SimpleProcessDesignerV2/src/index.ts
0 → 100644
View file @
93141792
import
SimpleProcessDesigner
from
'./SimpleProcessDesigner.vue'
import
'../theme/simple-process-designer.scss'
export
{
SimpleProcessDesigner
}
\ No newline at end of file
src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue
0 → 100644
View file @
93141792
<
template
>
<div
class=
"end-node-wrapper"
>
<div
class=
"end-node-box"
>
<span
class=
"node-fixed-name"
title=
"结束"
>
结束
</span>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
defineOptions
({
name
:
'EndEventNode'
})
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/components/SimpleProcessDesignerV2/src/nodes/StartEventNode.vue
0 → 100644
View file @
93141792
<
template
>
<div
class=
"start-node-wrapper"
>
<div
class=
"start-node-container"
>
<div
class=
"start-node-box"
>
<Icon
icon=
"ep:avatar"
/>
<span
class=
"node-fixed-name"
:title=
"currentNode.name"
>
{{
currentNode
.
name
}}
</span>
<Icon
icon=
"ep:arrow-right-bold"
/>
</div>
<!-- 传递子节点给添加节点组件。会在子节点后面添加节点 -->
<node-handler
v-if=
"currentNode"
v-model:child-node=
"currentNode.childNode"
/>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
NodeHandler
from
'../NodeHandler.vue'
import
{
SimpleFlowNode
}
from
'../consts'
;
defineOptions
({
name
:
'StartEventNode'
})
const
props
=
defineProps
({
flowNode
:
{
type
:
Object
as
()
=>
SimpleFlowNode
,
default
:
()
=>
null
}
});
const
currentNode
=
ref
<
SimpleFlowNode
>
(
props
.
flowNode
);
watch
(()
=>
props
.
flowNode
,
(
newValue
)
=>
{
console
.
log
(
'start node value changed=================>'
,
newValue
);
currentNode
.
value
=
newValue
;
});
</
script
>
<
style
lang=
"scss"
scoped
>
</
style
>
src/components/SimpleProcessDesignerV2/theme/iconfont.ttf
0 → 100644
View file @
93141792
File added
src/components/SimpleProcessDesignerV2/theme/iconfont.woff
0 → 100644
View file @
93141792
File added
src/components/SimpleProcessDesignerV2/theme/iconfont.woff2
0 → 100644
View file @
93141792
File added
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
0 → 100644
View file @
93141792
.simple-flow-canvas
{
position
:
absolute
;
inset
:
0
;
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-color: #0089ff;
border-radius
:
8px
;
// border-color: #0089ff;
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
);
&
:hover
{
// border-color: #0089ff;
.node-toolbar
{
opacity
:
1
;
}
}
// 普通节点标题
.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
;
}
.node-title
{
margin-left
:
4px
;
font-size
:
14px
;
font-weight
:
600
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
color
:
#1f1f1f
;
// vertical-align: middle;
}
}
// 条件节点标题
.branch-node-title-container
{
display
:
flex
;
padding
:
4px
;
cursor
:
pointer
;
border-radius
:
4px
4px
0
0
;
align-items
:
center
;
justify-content
:
space-between
;
.branch-title
{
max-width
:
120px
;
font-size
:
13px
;
font-weight
:
600
;
color
:
#f60
;
}
.branch-priority
{
min-width
:
50px
;
font-size
:
13px
;
}
}
.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
;
}
}
.node-toolbar
{
opacity
:
0
;
position
:
absolute
;
top
:
-26px
;
right
:
10px
;
display
:
flex
;
.toolbar-icon
{
text-align
:
center
;
vertical-align
:
middle
;
color
:
#000
;
border-radius
:
4px
;
}
}
}
.node-config-error
{
border-color
:
#ff5219
;
}
// 普通节点包装
.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
;
right
:
0
;
left
:
0
;
// bottom: 5px;
bottom
:
0px
;
z-index
:
0
;
width
:
2px
;
height
:
100%
;
// height: calc(100% - 5px);
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
;
color
:
#222
;
cursor
:
pointer
;
background
:
#fff
;
border
:
2px
solid
#dedede
;
border-radius
:
18px
;
transform
:
translateX
(
-50%
);
transform-origin
:
center
center
;
transition
:
all
0
.3s
cubic-bezier
(
0
.645
,
0
.045
,
0
.355
,
1
);
}
.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: #2c2c2c;
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
;
color
:
#212121
;
// background: #6e6e6e;
background
:
#fafafa
;
border-radius
:
30px
;
box-shadow
:
0
1px
5px
0
rgba
(
10
,
30
,
65
,
0
.08
);
box-sizing
:
border-box
;
}
}
// 可编辑的 title 输入框
.editable-title-input
{
height
:
20px
;
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
,
.2
)
}
}
}
}
}
// 节点连线气泡卡片样式
.handler-item-wrapper
{
display
:
flex
;
cursor
:
pointer
;
.handler-item
{
margin-right
:
8px
;
}
.handler-item-icon
{
width
:
80px
;
height
:
80px
;
background
:
#fff
;
border
:
1px
solid
#e2e2e2
;
border-radius
:
50%
;
transition
:
all
0
.3s
cubic-bezier
(
0
.645
,
0
.045
,
0
.355
,
1
);
user-select
:
none
;
text-align
:
center
;
&
:hover
{
background
:
#3296fa
;
box-shadow
:
0
2px
4px
0
rgba
(
0
,
0
,
0
,
0
.1
);
}
.icon-size
{
font-size
:
35px
;
line-height
:
80px
;
}
}
.approve
{
color
:
#ff943e
;
}
.handler-item-text
{
margin-top
:
4px
;
width
:
80px
;
text-align
:
center
;
}
}
// iconfont 样式
@font-face
{
font-family
:
"iconfont"
;
/* Project id 4495938 */
src
:
url('iconfont.woff2?t=1712392083512')
format
(
'woff2'
)
,
url('iconfont.woff?t=1712392083512')
format
(
'woff'
)
,
url('iconfont.ttf?t=1712392083512')
format
(
'truetype'
);
}
.iconfont
{
font-family
:
"iconfont"
!
important
;
font-size
:
16px
;
font-style
:
normal
;
-webkit-font-smoothing
:
antialiased
;
-moz-osx-font-smoothing
:
grayscale
;
}
.icon-Inclusive
:before
{
content
:
"\e602"
;
}
.icon-copy
:before
{
content
:
"\e7eb"
;
}
.icon-handle
:before
{
content
:
"\e61c"
;
}
.icon-exclusive
:before
{
content
:
"\e717"
;
}
.icon-approve
:before
{
content
:
"\e715"
;
}
.icon-parallel
:before
{
content
:
"\e688"
;
}
\ No newline at end of file
src/views/bpm/simpleWorkflow/index.vue
View file @
93141792
<
template
>
<div>
<section
class=
"dingflow-design"
>
<div
class=
"sticky right-0 top-0 z-10 w-full flex justify-end bg-white p-2 pr-4"
>
<el-button
type=
"primary"
size=
"small"
@
click=
"test"
>
保存(测试中,待完善)
</el-button>
</div>
<div
class=
"box-scale"
>
<div
class=
"start-event-node"
>
<div
class=
"start-event-node-circle"
>
开始
</div>
</div>
<div
class=
"start-event-node-line"
></div>
<nodeWrap
v-model:nodeConfig=
"nodeConfig"
/>
<div
class=
"end-event"
>
<div
class=
"end-event-circle"
>
结束
</div>
</div>
</div>
</section>
</div>
<approverDrawer/>
<copyerDrawer
/>
<SimpleProcessDesigner
:model-id=
"modelId"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
nodeWrap
from
'@/components/SimpleProcessDesigner/src/nodeWrap.vue'
import
approverDrawer
from
'@/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue'
import
copyerDrawer
from
'@/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue'
import
{
WorkFlowNode
}
from
'@/components/SimpleProcessDesigner/src/consts'
import
{
ref
}
from
'vue'
import
{
saveBpmSimpleModel
,
getBpmSimpleModel
}
from
'@/api/bpm/simple'
<
script
setup
lang=
'ts'
>
import
{
SimpleProcessDesigner
}
from
'@/components/SimpleProcessDesignerV2/src/'
;
import
{
getModel
}
from
'@/api/bpm/model'
import
{
getForm
,
FormVO
}
from
'@/api/bpm/form'
defineOptions
({
name
:
'SimpleWorkflowDesignEditor'
})
const
uid
=
getCurrentInstance
().
uid
const
router
=
useRouter
()
// 路由
const
{
query
}
=
useRoute
()
// 路由的查询
const
modelId
=
query
.
modelId
const
message
=
useMessage
()
// 国际化
const
nodeConfig
=
ref
<
WorkFlowNode
>
({
name
:
'发起人'
,
type
:
0
,
id
:
'StartEvent_'
+
uid
,
childNode
:
undefined
,
attributes
:
undefined
,
conditionNodes
:
[]
defineOptions
({
name
:
'SimpleWorkflowDesignEditor'
})
// 默认的表单字段权限
const
defaultFieldsPermission
:
any
[]
=
[]
const
formType
=
ref
(
20
);
provide
(
'defaultFieldsPermission'
,
defaultFieldsPermission
)
const
{
query
}
=
useRoute
()
// 路由的查询
const
modelId
:
string
|
undefined
=
query
.
modelId
as
string
;
const
formFields
=
ref
<
string
[]
>
([])
const
formType
=
ref
(
20
);
provide
(
'formFields'
,
formFields
)
provide
(
'formType'
,
formType
)
const
test
=
async
()
=>
{
if
(
!
modelId
)
{
message
.
error
(
'缺少模型 modelId 编号'
)
return
}
const
test
=
nodeConfig
.
value
console
.
log
(
'test is '
,
test
)
console
.
log
(
'nodeConfig.value '
,
nodeConfig
.
value
)
const
data1
=
{
modelId
:
modelId
,
simpleModelBody
:
toRaw
(
nodeConfig
.
value
)
}
const
data
=
{
modelId
:
modelId
,
simpleModelBody
:
nodeConfig
.
value
}
console
.
log
(
'request json data1 is '
,
data1
)
const
result
=
await
saveBpmSimpleModel
(
data
)
console
.
log
(
'save the result is '
,
result
)
if
(
result
)
{
message
.
success
(
'修改成功'
)
close
()
}
else
{
message
.
alert
(
'修改失败'
)
}
}
const
close
=
()
=>
{
router
.
push
({
path
:
'/bpm/manager/model'
})
}
onMounted
(
async
()
=>
{
const
bpmnModel
=
await
getModel
(
modelId
)
onMounted
(
async
()
=>
{
const
bpmnModel
=
await
getModel
(
modelId
);
if
(
bpmnModel
)
{
formType
.
value
=
bpmnModel
.
formType
if
(
formType
.
value
===
10
)
{
const
bpmnForm
=
await
getForm
(
bpmnModel
.
formId
)
as
unknown
as
FormVO
const
formFields
=
bpmnForm
?.
fields
if
(
formFields
)
{
formFields
.
forEach
((
fieldStr
:
string
)
=>
{
const
{
field
,
title
}
=
JSON
.
parse
(
fieldStr
)
defaultFieldsPermission
.
push
({
field
,
title
,
permission
:
'2'
})
})
}
console
.
log
(
'defaultFieldsPermissions'
,
defaultFieldsPermission
);
formFields
.
value
=
bpmnForm
?.
fields
}
}
console
.
log
(
'the modelId is '
,
modelId
)
const
result
=
await
getBpmSimpleModel
(
modelId
)
if
(
result
)
{
console
.
log
(
'get the result is '
,
result
)
nodeConfig
.
value
=
result
}
})
</
script
>
<
style
>
@import
url('@/components/SimpleProcessDesigner/theme/workflow.css')
;
.end-event
{
display
:
flex
;
direction
:
columns
;
justify-content
:
center
;
align-items
:
center
;
}
.end-event-circle
{
display
:
flex
;
width
:
40px
;
height
:
40px
;
font-size
:
14px
;
color
:
#f8f8fa
;
background-image
:
linear-gradient
(
-30deg
,
#bbbbc4
,
#d5d5de
),
linear-gradient
(
#bcbcc5
,
#bcbcc5
);
border-radius
:
50%
;
justify-content
:
center
;
align-items
:
center
;
}
/* .start-event-node {
color: #191f2566;
text-align: left;
border-radius: 50%;
} */
.start-event-node
{
display
:
flex
;
direction
:
columns
;
justify-content
:
center
;
align-items
:
center
;
}
.start-event-node-circle
{
display
:
flex
;
width
:
40px
;
height
:
40px
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
14px
;
color
:
#f8f8fa
;
background-image
:
linear-gradient
(
90deg
,
#ff6a00
,
#f78b3e
),
linear-gradient
(
#ff6a00
,
#ff6a00
);
border-radius
:
50%
;
}
.start-event-node-line
::before
{
position
:
absolute
;
inset
:
0
;
z-index
:
-1
;
width
:
2px
;
height
:
100%
;
margin
:
auto
;
background-color
:
#cacaca
;
content
:
''
;
}
<
style
lang=
'scss'
scoped
>
.start-event-node-line
{
position
:
relative
;
padding
:
20px
0
32px
;
}
</
style
>
src/views/bpm/simpleWorkflow/index1.vue
0 → 100644
View file @
93141792
<
template
>
<div>
<section
class=
"dingflow-design"
>
<div
class=
"sticky right-0 top-0 z-10 w-full flex justify-end bg-white p-2 pr-4"
>
<el-button
type=
"primary"
size=
"small"
@
click=
"test"
>
保存(测试中,待完善)
</el-button>
</div>
<div
class=
"box-scale"
>
<div
class=
"start-event-node"
>
<div
class=
"start-event-node-circle"
>
开始
</div>
</div>
<div
class=
"start-event-node-line"
></div>
<nodeWrap
v-model:nodeConfig=
"nodeConfig"
/>
<div
class=
"end-event"
>
<div
class=
"end-event-circle"
>
结束
</div>
</div>
</div>
</section>
</div>
<approverDrawer/>
<copyerDrawer
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
nodeWrap
from
'@/components/SimpleProcessDesigner/src/nodeWrap.vue'
import
approverDrawer
from
'@/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue'
import
copyerDrawer
from
'@/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue'
import
{
WorkFlowNode
}
from
'@/components/SimpleProcessDesigner/src/consts'
import
{
ref
}
from
'vue'
import
{
saveBpmSimpleModel
,
getBpmSimpleModel
}
from
'@/api/bpm/simple'
import
{
getModel
}
from
'@/api/bpm/model'
import
{
getForm
,
FormVO
}
from
'@/api/bpm/form'
defineOptions
({
name
:
'SimpleWorkflowDesignEditor2'
})
const
uid
=
getCurrentInstance
().
uid
const
router
=
useRouter
()
// 路由
const
{
query
}
=
useRoute
()
// 路由的查询
const
modelId
=
query
.
modelId
const
message
=
useMessage
()
// 国际化
const
nodeConfig
=
ref
<
WorkFlowNode
>
({
name
:
'发起人'
,
type
:
0
,
id
:
'StartEvent_'
+
uid
,
childNode
:
undefined
,
attributes
:
undefined
,
conditionNodes
:
[]
})
// 默认的表单字段权限
const
defaultFieldsPermission
:
any
[]
=
[]
const
formType
=
ref
(
20
);
provide
(
'defaultFieldsPermission'
,
defaultFieldsPermission
)
provide
(
'formType'
,
formType
)
const
test
=
async
()
=>
{
if
(
!
modelId
)
{
message
.
error
(
'缺少模型 modelId 编号'
)
return
}
const
test
=
nodeConfig
.
value
console
.
log
(
'test is '
,
test
)
console
.
log
(
'nodeConfig.value '
,
nodeConfig
.
value
)
const
data1
=
{
modelId
:
modelId
,
simpleModelBody
:
toRaw
(
nodeConfig
.
value
)
}
const
data
=
{
modelId
:
modelId
,
simpleModelBody
:
nodeConfig
.
value
}
console
.
log
(
'request json data1 is '
,
data1
)
const
result
=
await
saveBpmSimpleModel
(
data
)
console
.
log
(
'save the result is '
,
result
)
if
(
result
)
{
message
.
success
(
'修改成功'
)
close
()
}
else
{
message
.
alert
(
'修改失败'
)
}
}
const
close
=
()
=>
{
router
.
push
({
path
:
'/bpm/manager/model'
})
}
onMounted
(
async
()
=>
{
const
bpmnModel
=
await
getModel
(
modelId
)
if
(
bpmnModel
)
{
formType
.
value
=
bpmnModel
.
formType
if
(
formType
.
value
===
10
)
{
const
bpmnForm
=
await
getForm
(
bpmnModel
.
formId
)
as
unknown
as
FormVO
const
formFields
=
bpmnForm
?.
fields
if
(
formFields
)
{
formFields
.
forEach
((
fieldStr
:
string
)
=>
{
const
{
field
,
title
}
=
JSON
.
parse
(
fieldStr
)
defaultFieldsPermission
.
push
({
field
,
title
,
permission
:
'2'
})
})
}
console
.
log
(
'defaultFieldsPermissions'
,
defaultFieldsPermission
);
}
}
console
.
log
(
'the modelId is '
,
modelId
)
const
result
=
await
getBpmSimpleModel
(
modelId
)
if
(
result
)
{
console
.
log
(
'get the result is '
,
result
)
nodeConfig
.
value
=
result
}
})
</
script
>
<
style
>
@import
url('@/components/SimpleProcessDesigner/theme/workflow.css')
;
.end-event
{
display
:
flex
;
direction
:
columns
;
justify-content
:
center
;
align-items
:
center
;
}
.end-event-circle
{
display
:
flex
;
width
:
40px
;
height
:
40px
;
font-size
:
14px
;
color
:
#f8f8fa
;
background-image
:
linear-gradient
(
-30deg
,
#bbbbc4
,
#d5d5de
),
linear-gradient
(
#bcbcc5
,
#bcbcc5
);
border-radius
:
50%
;
justify-content
:
center
;
align-items
:
center
;
}
/* .start-event-node {
color: #191f2566;
text-align: left;
border-radius: 50%;
} */
.start-event-node
{
display
:
flex
;
direction
:
columns
;
justify-content
:
center
;
align-items
:
center
;
}
.start-event-node-circle
{
display
:
flex
;
width
:
40px
;
height
:
40px
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
14px
;
color
:
#f8f8fa
;
background-image
:
linear-gradient
(
90deg
,
#ff6a00
,
#f78b3e
),
linear-gradient
(
#ff6a00
,
#ff6a00
);
border-radius
:
50%
;
}
.start-event-node-line
::before
{
position
:
absolute
;
inset
:
0
;
z-index
:
-1
;
width
:
2px
;
height
:
100%
;
margin
:
auto
;
background-color
:
#cacaca
;
content
:
''
;
}
.start-event-node-line
{
position
:
relative
;
padding
:
20px
0
32px
;
}
</
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