明树Git Lab
Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
J
jt_front
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
jt_front
Commits
330f85bb
Commit
330f85bb
authored
May 13, 2026
by
zhanghan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
初版修改完毕
parent
c3408065
Pipeline
#111244
passed with stage
in 20 seconds
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
386 additions
and
29 deletions
+386
-29
ProcessFlowChart.vue
src/components/common/ProcessFlowChart.vue
+310
-0
routerBack.vue
src/components/common/routerBack.vue
+63
-29
addProject.vue
src/views/projectManage/addProject.vue
+13
-0
No files found.
src/components/common/ProcessFlowChart.vue
0 → 100644
View file @
330f85bb
<
template
>
<div
class=
"flow-chart"
>
<div
class=
"flow-title"
>
{{
flowTitle
}}
</div>
<div
class=
"flow-body"
ref=
"bodyRef"
>
<div
class=
"flow-nodes"
>
<div
v-for=
"(node, index) in activeNodes"
:key=
"index"
class=
"flow-node-wrap"
:ref=
"el => nodeEls[index] = el"
>
<div
class=
"flow-node"
:class=
"[node.type || 'step', getNodeStatus(index)]"
>
<span
class=
"node-label"
>
{{
node
.
label
}}
</span>
</div>
<div
v-if=
"node.annotation"
class=
"flow-annotation"
>
{{
node
.
annotation
}}
</div>
<div
v-if=
"index
<
activeNodes
.
length
-
1
"
class=
"flow-arrow"
>
<div
class=
"arrow-line"
></div>
<div
class=
"arrow-head"
></div>
</div>
</div>
</div>
<svg
v-if=
"returnPaths.length"
class=
"return-svg"
:width=
"svgWidth"
:height=
"svgHeight"
>
<defs>
<marker
id=
"arrowMarker"
markerWidth=
"8"
markerHeight=
"6"
refX=
"8"
refY=
"3"
orient=
"auto"
>
<path
d=
"M0,0 L8,3 L0,6 Z"
fill=
"#909399"
/>
</marker>
</defs>
<g
v-for=
"(path, i) in returnPaths"
:key=
"i"
>
<polyline
:points=
"path.points"
fill=
"none"
stroke=
"#909399"
stroke-width=
"1.5"
stroke-dasharray=
"4,3"
marker-end=
"url(#arrowMarker)"
/>
<text
:x=
"path.textX"
:y=
"path.textY"
font-size=
"11"
fill=
"#909399"
text-anchor=
"middle"
dominant-baseline=
"middle"
:transform=
"`rotate(-90, $
{path.textX}, ${path.textY})`"
>退回修改
</text>
</g>
</svg>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
computed
,
onMounted
,
nextTick
,
watch
}
from
"vue"
;
const
props
=
defineProps
({
flowType
:
{
type
:
String
,
default
:
"complex"
,
},
currentState
:
{
type
:
Number
,
default
:
0
,
},
});
const
complexNodes
=
[
{
label
:
"开始"
,
type
:
"terminal"
},
{
label
:
"所属单位员工发起"
},
{
label
:
"所属单位部门长核准"
,
annotation
:
"提交/退回"
},
{
label
:
"投管部正/副职指定经办人"
},
{
label
:
"投管部经办人初审"
},
{
label
:
"投管部正副职审核"
,
returnTo
:
2
},
{
label
:
"结束"
,
type
:
"terminal"
},
];
const
simpleNodes
=
[
{
label
:
"开始"
,
type
:
"terminal"
},
{
label
:
"投管部员工发起"
},
{
label
:
"投管部正副职审核"
,
annotation
:
"提交/退回"
,
returnTo
:
1
},
{
label
:
"结束"
,
type
:
"terminal"
},
];
const
complexLevelMap
=
{
1
:
2
,
2
:
2
,
7
:
2
,
3
:
4
,
4
:
6
,
8
:
6
,
5
:
7
,
9
:
7
,
13
:
7
,
14
:
7
,
15
:
7
,
};
const
simpleLevelMap
=
{
1
:
2
,
2
:
2
,
3
:
3
,
4
:
3
,
5
:
4
,
};
const
activeNodes
=
computed
(()
=>
{
return
props
.
flowType
===
"simple"
?
simpleNodes
:
complexNodes
;
});
const
highlightLevel
=
computed
(()
=>
{
const
map
=
props
.
flowType
===
"simple"
?
simpleLevelMap
:
complexLevelMap
;
return
map
[
props
.
currentState
]
||
0
;
});
const
flowTitle
=
computed
(()
=>
{
if
(
props
.
flowType
===
"simple"
)
return
"立项审批流程"
;
return
props
.
currentState
>=
7
?
"决策审批流程"
:
"立项审批流程"
;
});
const
getNodeStatus
=
(
index
)
=>
{
const
level
=
highlightLevel
.
value
;
const
total
=
activeNodes
.
value
.
length
;
if
(
level
===
0
||
index
>=
level
)
return
""
;
if
(
level
>=
total
)
return
"completed"
;
if
(
index
===
level
-
1
)
return
"active"
;
return
"completed"
;
};
const
bodyRef
=
ref
(
null
);
const
nodeEls
=
ref
([]);
const
svgWidth
=
ref
(
0
);
const
svgHeight
=
ref
(
0
);
const
returnPaths
=
ref
([]);
const
calcReturnPaths
=
()
=>
{
const
body
=
bodyRef
.
value
;
if
(
!
body
)
return
;
const
bodyRect
=
body
.
getBoundingClientRect
();
svgWidth
.
value
=
bodyRect
.
width
;
svgHeight
.
value
=
bodyRect
.
height
;
const
paths
=
[];
const
nodes
=
activeNodes
.
value
;
let
offsetX
=
0
;
nodes
.
forEach
((
node
,
index
)
=>
{
if
(
node
.
returnTo
===
undefined
)
return
;
const
sourceEl
=
nodeEls
.
value
[
index
];
const
targetEl
=
nodeEls
.
value
[
node
.
returnTo
];
if
(
!
sourceEl
||
!
targetEl
)
return
;
const
sourceNode
=
sourceEl
.
querySelector
(
".flow-node"
);
const
targetNode
=
targetEl
.
querySelector
(
".flow-node"
);
if
(
!
sourceNode
||
!
targetNode
)
return
;
const
sourceRect
=
sourceNode
.
getBoundingClientRect
();
const
targetRect
=
targetNode
.
getBoundingClientRect
();
const
sourceY
=
sourceRect
.
top
+
sourceRect
.
height
/
2
-
bodyRect
.
top
;
const
targetY
=
targetRect
.
top
+
targetRect
.
height
/
2
-
bodyRect
.
top
;
const
nodeLeft
=
sourceRect
.
left
-
bodyRect
.
left
;
offsetX
+=
20
;
const
lineX
=
nodeLeft
-
offsetX
;
paths
.
push
({
points
:
`
${
nodeLeft
}
,
${
sourceY
}
${
lineX
}
,
${
sourceY
}
${
lineX
}
,
${
targetY
}
${
nodeLeft
}
,
${
targetY
}
`
,
textX
:
lineX
-
8
,
textY
:
(
sourceY
+
targetY
)
/
2
,
});
});
returnPaths
.
value
=
paths
;
};
onMounted
(()
=>
{
nextTick
(
calcReturnPaths
);
});
watch
(
()
=>
[
props
.
flowType
,
props
.
currentState
],
()
=>
{
nextTick
(()
=>
nextTick
(
calcReturnPaths
));
}
);
</
script
>
<
style
scoped
lang=
"scss"
>
.flow-chart
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
16px
12px
;
min-width
:
200px
;
}
.flow-title
{
font-size
:
14px
;
font-weight
:
600
;
color
:
#333
;
margin-bottom
:
16px
;
}
.flow-body
{
position
:
relative
;
width
:
100%
;
padding-left
:
90px
;
}
.flow-nodes
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
width
:
100%
;
}
.return-svg
{
position
:
absolute
;
top
:
0
;
left
:
0
;
pointer-events
:
none
;
overflow
:
visible
;
}
.flow-node-wrap
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
position
:
relative
;
width
:
100%
;
}
.flow-node
{
min-width
:
200px
;
max-width
:
280px
;
padding
:
10px
24px
;
text-align
:
center
;
border
:
2px
solid
#dcdfe6
;
background
:
#fff
;
font-size
:
13px
;
color
:
#606266
;
transition
:
all
0
.3s
;
&
.terminal
{
border-radius
:
20px
;
background
:
#f5f7fa
;
border-color
:
#c0c4cc
;
color
:
#909399
;
}
&
.step
{
border-radius
:
4px
;
}
&
.active
{
border-color
:
#409eff
;
background
:
#409eff
;
color
:
#fff
;
font-weight
:
600
;
box-shadow
:
0
2px
8px
rgba
(
64
,
158
,
255
,
0
.4
);
}
&
.completed
{
border-color
:
#409eff
;
background
:
#ecf5ff
;
color
:
#409eff
;
}
&
.terminal.completed
{
background
:
#ecf5ff
;
border-color
:
#409eff
;
color
:
#409eff
;
}
&
.terminal.active
{
background
:
#409eff
;
border-color
:
#409eff
;
color
:
#fff
;
}
}
.node-label
{
white-space
:
nowrap
;
}
.flow-annotation
{
font-size
:
11px
;
color
:
#909399
;
margin-top
:
2px
;
}
.flow-arrow
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
height
:
28px
;
.arrow-line
{
width
:
2px
;
flex
:
1
;
background
:
#c0c4cc
;
}
.arrow-head
{
width
:
0
;
height
:
0
;
border-left
:
5px
solid
transparent
;
border-right
:
5px
solid
transparent
;
border-top
:
6px
solid
#c0c4cc
;
}
}
</
style
>
src/components/common/routerBack.vue
View file @
330f85bb
...
@@ -48,36 +48,41 @@
...
@@ -48,36 +48,41 @@
:close-on-click-modal=
"false"
:close-on-click-modal=
"false"
:close-on-press-escape=
"true"
:close-on-press-escape=
"true"
>
>
<div
class=
"process-dialog-content"
>
<div
class=
"process-dialog-content process-layout"
>
<div
class=
"process-project-name"
v-if=
"processInfo.projectName"
>
<div
class=
"process-left"
>
项目名称:
<span
class=
"name"
>
{{
processInfo
.
projectName
}}
</span>
<div
class=
"process-project-name"
v-if=
"processInfo.projectName"
>
项目名称:
<span
class=
"name"
>
{{
processInfo
.
projectName
}}
</span>
</div>
<el-table
:data=
"processData"
border
style=
"width: 100%"
class=
"process-table"
:header-cell-style=
"
{
background: '#f5f7fa',
color: '#333',
textAlign: 'center',
fontWeight: 600,
}"
:cell-style="{ textAlign: 'center' }"
>
<el-table-column
type=
"index"
label=
"序号"
width=
"60"
/>
<el-table-column
prop=
"actionName"
label=
"操作"
min-width=
"120"
/>
<el-table-column
label=
"操作人"
min-width=
"100"
>
<template
#
default=
"
{ row }">
{{
row
.
creator
?.
name
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"actionName"
label=
"项目步骤"
min-width=
"120"
/>
<el-table-column
prop=
"approvalMessage"
label=
"审核意见"
min-width=
"120"
/>
<el-table-column
prop=
"createdAt"
label=
"时间"
min-width=
"160"
/>
</el-table>
</div>
<div
class=
"process-right"
>
<ProcessFlowChart
:flow-type=
"flowType"
:current-state=
"currentState"
/>
</div>
</div>
<el-table
:data=
"processData"
border
style=
"width: 100%"
class=
"process-table"
:header-cell-style=
"
{
background: '#f5f7fa',
color: '#333',
textAlign: 'center',
fontWeight: 600,
}"
:cell-style="{ textAlign: 'center' }"
>
<el-table-column
type=
"index"
label=
"序号"
width=
"80"
/>
<el-table-column
prop=
"actionName"
label=
"操作"
min-width=
"150"
/>
<el-table-column
label=
"操作人"
min-width=
"120"
>
<template
#
default=
"
{ row }">
{{
row
.
creator
?.
name
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"actionName"
label=
"项目步骤"
min-width=
"150"
/>
<el-table-column
prop=
"approvalMessage"
label=
"审核意见"
min-width=
"150"
/>
<el-table-column
prop=
"createdAt"
label=
"时间"
min-width=
"180"
/>
</el-table>
</div>
</div>
<
template
#
footer
>
<
template
#
footer
>
<el-button
@
click=
"processDialogVisible = false"
>
关闭
</el-button>
<el-button
@
click=
"processDialogVisible = false"
>
关闭
</el-button>
...
@@ -88,6 +93,7 @@
...
@@ -88,6 +93,7 @@
<
script
setup
>
<
script
setup
>
import
{
ref
}
from
"vue"
;
import
{
ref
}
from
"vue"
;
import
{
useRouter
,
useRoute
}
from
"vue-router"
;
import
{
useRouter
,
useRoute
}
from
"vue-router"
;
import
ProcessFlowChart
from
"./ProcessFlowChart.vue"
;
const
props
=
defineProps
({
const
props
=
defineProps
({
showSave
:
{
showSave
:
{
...
@@ -126,6 +132,14 @@ const props = defineProps({
...
@@ -126,6 +132,14 @@ const props = defineProps({
type
:
Array
,
type
:
Array
,
default
:
()
=>
[],
default
:
()
=>
[],
},
},
flowType
:
{
type
:
String
,
default
:
"complex"
,
},
currentState
:
{
type
:
Number
,
default
:
0
,
},
});
});
const
emit
=
defineEmits
([
"save"
,
"back"
,
"export"
,
"process"
]);
const
emit
=
defineEmits
([
"save"
,
"back"
,
"export"
,
"process"
]);
...
@@ -187,6 +201,26 @@ const handleProcess = () => {
...
@@ -187,6 +201,26 @@ const handleProcess = () => {
}
}
}
}
.process-layout
{
display
:
flex
;
gap
:
16px
;
}
.process-left
{
flex
:
1
;
min-width
:
0
;
overflow
:
auto
;
}
.process-right
{
width
:
420px
;
flex-shrink
:
0
;
border-left
:
1px
solid
#ebeef5
;
display
:
flex
;
align-items
:
flex-start
;
justify-content
:
center
;
}
.process-project-name
{
.process-project-name
{
font-size
:
14px
;
font-size
:
14px
;
margin-bottom
:
12px
;
margin-bottom
:
12px
;
...
...
src/views/projectManage/addProject.vue
View file @
330f85bb
...
@@ -12,6 +12,8 @@
...
@@ -12,6 +12,8 @@
:show-process=
"!!projectId"
:show-process=
"!!projectId"
:process-info=
"processInfo"
:process-info=
"processInfo"
:process-data=
"processTableData"
:process-data=
"processTableData"
:flow-type=
"flowType"
:current-state=
"currentFlowState"
@
save=
"() => saveClick('save')"
@
save=
"() => saveClick('save')"
@
process=
"getProcessData"
@
process=
"getProcessData"
></routerBack>
></routerBack>
...
@@ -219,6 +221,17 @@ userInfo.roles.map((item) => {
...
@@ -219,6 +221,17 @@ userInfo.roles.map((item) => {
}
}
});
});
const
isTouGuanBu
=
userInfo
.
departs
?.
some
(
d
=>
d
.
name
===
'投资管理部门'
);
const
currentFlowState
=
computed
(()
=>
{
const
lxState
=
Number
(
formData
.
projectLzType
)
||
0
;
const
jcState
=
Number
(
jcFormData
.
projectLzType
)
||
0
;
return
jcState
>=
7
?
jcState
:
lxState
;
});
const
flowType
=
computed
(()
=>
{
if
(
isTouGuanBu
&&
currentFlowState
.
value
>=
1
&&
currentFlowState
.
value
<=
6
)
return
'simple'
;
return
'complex'
;
});
// tab相关
// tab相关
const
pageActiveName
=
ref
(
"立项填报"
);
const
pageActiveName
=
ref
(
"立项填报"
);
const
lxTabActiveName
=
ref
(
"基本信息"
);
const
lxTabActiveName
=
ref
(
"基本信息"
);
...
...
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