Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
zrch-risk-39
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Administrator
zrch-risk-39
Commits
78599ce5
提交
78599ce5
authored
3月 29, 2026
作者:
kxjia
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
问题模块
上级
ca1f544f
隐藏空白字符变更
内嵌
并排
正在显示
22 个修改的文件
包含
3699 行增加
和
1104 行删除
+3699
-1104
definition.js
zrch-risk-client-39/src/components/Process/api/definition.js
+0
-1
ApprovalPanel.vue
zrch-risk-client-39/src/views/common/ApprovalPanel.vue
+654
-0
CurrentFormPanel.vue
zrch-risk-client-39/src/views/common/CurrentFormPanel.vue
+334
-0
HistoryPanel.vue
zrch-risk-client-39/src/views/common/HistoryPanel.vue
+386
-0
TaskAssigneeDrawer.vue
zrch-risk-client-39/src/views/common/TaskAssigneeDrawer.vue
+257
-0
TaskAssigneeSelector.vue
...-risk-client-39/src/views/common/TaskAssigneeSelector.vue
+593
-0
WorkFlowForm.vue
zrch-risk-client-39/src/views/common/WorkFlowForm.vue
+0
-302
WorkFlowFormApprovalDrawer.vue
...client-39/src/views/common/WorkFlowFormApprovalDrawer.vue
+425
-0
WorkFlowFormDrawer.vue
zrch-risk-client-39/src/views/common/WorkFlowFormDrawer.vue
+307
-327
ShowFormModal.vue
...t-39/src/views/flowable/task/components/ShowFormModal.vue
+576
-362
TodoIndex.vue
...-39/src/views/flowable/task/todo/components/TodoIndex.vue
+1
-1
StProblemArchiveList.vue
...9/src/views/project/problemCheck/StProblemArchiveList.vue
+17
-8
StProblemCheck.data.ts
...-39/src/views/project/problemCheck/StProblemCheck.data.ts
+2
-1
StProblemCheckList.vue
...-39/src/views/project/problemCheck/StProblemCheckList.vue
+16
-40
StProblemExecApprovalList.vue
.../views/project/problemCheck/StProblemExecApprovalList.vue
+3
-6
StProblemExecList.vue
...t-39/src/views/project/problemCheck/StProblemExecList.vue
+4
-6
StProblemIndex.vue
...ient-39/src/views/project/problemCheck/StProblemIndex.vue
+97
-25
StProblemPlanApprovalList.vue
.../views/project/problemCheck/StProblemPlanApprovalList.vue
+3
-6
StProblemPlanList.vue
...t-39/src/views/project/problemCheck/StProblemPlanList.vue
+12
-2
StProblemCheckForm.vue
...ws/project/problemCheck/components/StProblemCheckForm.vue
+4
-5
StProblemCheckModal.vue
...s/project/problemCheck/components/StProblemCheckModal.vue
+2
-2
StProblemCheckPlanForm.vue
...roject/problemCheck/components/StProblemCheckPlanForm.vue
+6
-10
没有找到文件。
zrch-risk-client-39/src/components/Process/api/definition.js
浏览文件 @
78599ce5
...
...
@@ -14,7 +14,6 @@ export const listDefinition = async (params) => {
// 部署流程实例
export
function
definitionStart
(
procDefId
,
data
)
{
alert
(
JSON
.
stringify
(
procDefId
))
return
defHttp
.
post
({
url
:
'/flowable/definition/startByProcDefId'
,
data
:
{
...
...
zrch-risk-client-39/src/views/common/ApprovalPanel.vue
0 → 100644
浏览文件 @
78599ce5
<
template
>
<div
class=
"approval-panel"
>
<div
class=
"approval-header"
>
<span
class=
"approval-title"
>
{{
title
}}
</span>
<a-tag
:color=
"tagColor"
v-if=
"showTag"
>
{{
tagText
}}
</a-tag>
</div>
<div
class=
"approval-content"
>
<a-form
:model=
"approvalForm"
layout=
"vertical"
>
<a-form-item
label=
"审核结果"
required
>
<a-radio-group
v-model:value=
"approvalForm.result"
class=
"result-group"
>
<a-radio
v-for=
"option in resultOptions"
:key=
"option.value"
:value=
"option.value"
:class=
"option.className"
>
<div
class=
"radio-content"
>
<component
:is=
"option.icon"
class=
"radio-icon"
/>
<span
class=
"radio-label"
>
{{
option
.
label
}}
</span>
</div>
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
:label=
"commentLabel"
:required=
"isCommentRequired"
:extra=
"commentExtra"
>
<a-textarea
v-model:value=
"approvalForm.comment"
:rows=
"commentRows"
:placeholder=
"commentPlaceholder"
:maxlength=
"commentMaxLength"
show-count
/>
</a-form-item>
<!-- 退回节点选择(仅当支持退回且结果选择退回时显示) -->
<a-form-item
v-if=
"showRejectNodeSelect && approvalForm.result === 'rejected'"
label=
"退回节点"
required
>
<a-select
v-model:value=
"approvalForm.rejectNode"
placeholder=
"请选择退回节点"
:options=
"rejectNodeOptions"
:disabled=
"!canSelectRejectNode"
>
<template
v-if=
"rejectNodeOptions.length === 0"
>
<a-select-option
value=
""
disabled
>
无可退回的节点
</a-select-option>
</
template
>
</a-select>
<div
class=
"form-tip"
v-if=
"rejectNodeOptions.length === 0"
>
{{ emptyRejectNodesTip }}
</div>
</a-form-item>
<!-- 自定义扩展区域 -->
<slot
name=
"extra-fields"
></slot>
<!-- 附加说明 -->
<div
v-if=
"showAdditionalInfo && approvalForm.result === 'rejected'"
class=
"additional-info"
>
<div
class=
"info-tip"
>
<info-circle-outlined
/>
<span>
{{ additionalInfoText }}
</span>
</div>
</div>
<a-divider
v-if=
"showSummary"
/>
<!-- 信息摘要区域 -->
<div
class=
"approval-summary"
v-if=
"showSummary"
>
<div
v-for=
"item in summaryItems"
:key=
"item.label"
class=
"summary-item"
>
<span
class=
"summary-label"
>
{{ item.label }}
</span>
<span
class=
"summary-value"
>
{{ item.value || '--' }}
</span>
</div>
</div>
</a-form>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
watch
,
onUnmounted
}
from
'vue'
import
{
CheckCircleOutlined
,
CloseCircleOutlined
,
InfoCircleOutlined
,
QuestionCircleOutlined
}
from
'@ant-design/icons-vue'
import
dayjs
from
'dayjs'
// 审核结果选项接口
interface
ResultOption
{
value
:
'approved'
|
'rejected'
|
string
label
:
string
icon
:
any
className
:
string
color
?:
string
}
// 摘要项接口
interface
SummaryItem
{
label
:
string
value
:
string
key
?:
string
}
// 工作流节点接口
interface
WorkflowNode
{
id
:
string
name
:
string
processor
?:
string
[
key
:
string
]:
any
}
const
props
=
defineProps
({
// 基础配置
title
:
{
type
:
String
,
default
:
'审核意见'
},
showTag
:
{
type
:
Boolean
,
default
:
true
},
tagColor
:
{
type
:
String
,
default
:
'orange'
},
tagText
:
{
type
:
String
,
default
:
'审核节点'
},
// 审核结果配置
resultOptions
:
{
type
:
Array
as
()
=>
ResultOption
[],
default
:
()
=>
[
{
value
:
'approved'
,
label
:
'通过'
,
icon
:
CheckCircleOutlined
,
className
:
'approved'
,
color
:
'#52c41a'
},
{
value
:
'rejected'
,
label
:
'退回'
,
icon
:
CloseCircleOutlined
,
className
:
'rejected'
,
color
:
'#ff4d4f'
}
]
},
defaultResult
:
{
type
:
String
,
default
:
'approved'
},
// 评论配置
commentLabel
:
{
type
:
String
,
default
:
'审核意见'
},
commentPlaceholder
:
{
type
:
String
,
default
:
'请输入审核意见...'
},
commentRows
:
{
type
:
Number
,
default
:
4
},
commentMaxLength
:
{
type
:
Number
,
default
:
500
},
isCommentRequired
:
{
type
:
Boolean
,
default
:
false
},
// 退回时是否强制要求评论
requireCommentOnReject
:
{
type
:
Boolean
,
default
:
true
},
// 退回节点配置
showRejectNodeSelect
:
{
type
:
Boolean
,
default
:
true
},
rejectNodes
:
{
type
:
Array
as
()
=>
WorkflowNode
[],
default
:
()
=>
[]
},
canSelectRejectNode
:
{
type
:
Boolean
,
default
:
true
},
emptyRejectNodesTip
:
{
type
:
String
,
default
:
'当前流程无可退回的历史节点'
},
// 附加信息配置
showAdditionalInfo
:
{
type
:
Boolean
,
default
:
true
},
additionalInfoText
:
{
type
:
String
,
default
:
'退回后,申请人需重新提交申请'
},
// 摘要配置
showSummary
:
{
type
:
Boolean
,
default
:
true
},
summaryItems
:
{
type
:
Array
as
()
=>
SummaryItem
[],
default
:
()
=>
[]
},
// 当前节点信息(用于默认摘要)
currentNode
:
{
type
:
Object
as
()
=>
WorkflowNode
|
null
,
default
:
null
},
currentUser
:
{
type
:
String
,
default
:
''
},
showCurrentTime
:
{
type
:
Boolean
,
default
:
true
},
// 初始数据
initialData
:
{
type
:
Object
as
()
=>
{
result
?:
string
comment
?:
string
rejectNode
?:
string
[
key
:
string
]:
any
},
default
:
()
=>
({})
},
// 其他配置
disabled
:
{
type
:
Boolean
,
default
:
false
},
readonly
:
{
type
:
Boolean
,
default
:
false
}
})
const
emit
=
defineEmits
([
'update:approval-data'
,
'change'
])
// 表单数据
const
approvalForm
=
ref
({
result
:
props
.
defaultResult
as
string
,
comment
:
''
,
rejectNode
:
''
})
// 当前时间
const
currentTime
=
ref
(
new
Date
())
// 退回节点选项
const
rejectNodeOptions
=
computed
(()
=>
{
if
(
!
props
.
rejectNodes
||
props
.
rejectNodes
.
length
===
0
)
{
return
[]
}
return
props
.
rejectNodes
.
map
(
node
=>
({
label
:
node
.
name
,
value
:
node
.
id
}))
})
// 评论是否必填
const
isCommentRequiredComputed
=
computed
(()
=>
{
if
(
props
.
isCommentRequired
)
return
true
if
(
props
.
requireCommentOnReject
&&
approvalForm
.
value
.
result
===
'rejected'
)
return
true
return
false
})
// 评论额外提示
const
commentExtra
=
computed
(()
=>
{
if
(
isCommentRequiredComputed
.
value
)
{
return
'此项为必填项'
}
return
''
})
// 默认摘要项
const
defaultSummaryItems
=
computed
<
SummaryItem
[]
>
(()
=>
{
const
items
:
SummaryItem
[]
=
[]
if
(
props
.
currentNode
)
{
items
.
push
({
label
:
'当前节点'
,
value
:
props
.
currentNode
.
name
})
}
if
(
props
.
currentUser
)
{
items
.
push
({
label
:
'处理人'
,
value
:
props
.
currentUser
})
}
if
(
props
.
showCurrentTime
)
{
items
.
push
({
label
:
'待处理时间'
,
value
:
dayjs
(
currentTime
.
value
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
})
}
return
items
})
// 最终的摘要项(合并默认和自定义)
const
finalSummaryItems
=
computed
(()
=>
{
if
(
props
.
summaryItems
.
length
>
0
)
{
return
props
.
summaryItems
}
return
defaultSummaryItems
.
value
})
// 获取审核数据
function
getApprovalData
()
{
const
data
:
any
=
{
result
:
approvalForm
.
value
.
result
,
comment
:
approvalForm
.
value
.
comment
}
// 如果是退回且需要退回节点信息
if
(
approvalForm
.
value
.
result
===
'rejected'
&&
props
.
showRejectNodeSelect
)
{
const
selectedNode
=
props
.
rejectNodes
.
find
(
node
=>
node
.
id
===
approvalForm
.
value
.
rejectNode
)
data
.
rejectNodeId
=
approvalForm
.
value
.
rejectNode
data
.
rejectNodeName
=
selectedNode
?.
name
||
''
}
// 添加时间戳
data
.
approvalTime
=
new
Date
().
toISOString
()
return
data
}
// 验证表单
async
function
validate
():
Promise
<
{
valid
:
boolean
;
errors
:
string
[]
}
>
{
const
errors
:
string
[]
=
[]
if
(
!
approvalForm
.
value
.
result
)
{
errors
.
push
(
'请选择审核结果'
)
}
if
(
isCommentRequiredComputed
.
value
&&
!
approvalForm
.
value
.
comment
?.
trim
())
{
errors
.
push
(
'请填写审核意见'
)
}
if
(
approvalForm
.
value
.
result
===
'rejected'
&&
props
.
showRejectNodeSelect
)
{
if
(
props
.
canSelectRejectNode
&&
!
approvalForm
.
value
.
rejectNode
)
{
errors
.
push
(
'请选择退回节点'
)
}
}
return
{
valid
:
errors
.
length
===
0
,
errors
}
}
// 重置表单
function
resetForm
()
{
approvalForm
.
value
=
{
result
:
props
.
defaultResult
,
comment
:
''
,
rejectNode
:
props
.
rejectNodes
[
0
]?.
id
||
''
}
// 如果有初始数据,则填充
if
(
props
.
initialData
.
result
)
{
approvalForm
.
value
.
result
=
props
.
initialData
.
result
approvalForm
.
value
.
comment
=
props
.
initialData
.
comment
||
''
approvalForm
.
value
.
rejectNode
=
props
.
initialData
.
rejectNode
||
''
}
}
// 设置表单数据
function
setFormData
(
data
:
any
)
{
if
(
data
.
result
!==
undefined
)
{
approvalForm
.
value
.
result
=
data
.
result
}
if
(
data
.
comment
!==
undefined
)
{
approvalForm
.
value
.
comment
=
data
.
comment
}
if
(
data
.
rejectNode
!==
undefined
)
{
approvalForm
.
value
.
rejectNode
=
data
.
rejectNode
}
}
// 监听表单变化
watch
(
approvalForm
,
(
newVal
)
=>
{
const
approvalData
=
{
result
:
newVal
.
result
,
comment
:
newVal
.
comment
,
rejectNode
:
newVal
.
rejectNode
}
emit
(
'update:approval-data'
,
approvalData
)
emit
(
'change'
,
approvalData
)
},
{
deep
:
true
}
)
// 监听初始数据变化
watch
(
()
=>
props
.
initialData
,
(
newData
)
=>
{
if
(
newData
&&
Object
.
keys
(
newData
).
length
>
0
)
{
setFormData
(
newData
)
}
},
{
deep
:
true
,
immediate
:
true
}
)
// 监听可退回节点变化,设置默认退回节点
watch
(
()
=>
props
.
rejectNodes
,
(
nodes
)
=>
{
if
(
nodes
&&
nodes
.
length
>
0
&&
!
approvalForm
.
value
.
rejectNode
)
{
approvalForm
.
value
.
rejectNode
=
nodes
[
0
]?.
id
||
''
}
},
{
immediate
:
true
}
)
// 启动定时器更新时间
let
timer
:
ReturnType
<
typeof
setInterval
>
|
null
=
null
if
(
props
.
showCurrentTime
)
{
timer
=
setInterval
(()
=>
{
currentTime
.
value
=
new
Date
()
},
1000
)
}
// 组件卸载时清除定时器
onUnmounted
(()
=>
{
if
(
timer
)
{
clearInterval
(
timer
)
}
})
// 暴露方法给父组件
defineExpose
({
getApprovalData
,
validate
,
resetForm
,
setFormData
,
getFormRef
:
()
=>
approvalForm
})
</
script
>
<
style
scoped
lang=
"scss"
>
.approval-panel
{
background-color
:
#fff
;
border-left
:
1px
solid
#e8eef2
;
display
:
flex
;
flex-direction
:
column
;
overflow
:
hidden
;
height
:
100%
;
.approval-header
{
padding
:
16px
20px
;
border-bottom
:
1px
solid
#e8eef2
;
background-color
:
#fafbfc
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
flex-shrink
:
0
;
.approval-title
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#1f2f3d
;
}
}
.approval-content
{
flex
:
1
;
overflow-y
:
auto
;
padding
:
20px
;
&
:
:-
webkit-scrollbar
{
width
:
4px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
border-radius
:
2px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
2px
;
&
:hover
{
background
:
#a8a8a8
;
}
}
}
.result-group
{
display
:
flex
;
gap
:
16px
;
width
:
100%
;
.ant-radio-wrapper
{
flex
:
1
;
:deep
(
.ant-radio
)
{
display
:
none
;
}
:deep
(
.ant-radio
+
*)
{
padding
:
0
;
}
.radio-content
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
16px
;
border
:
1px
solid
#d9d9d9
;
border-radius
:
8px
;
transition
:
all
0
.3s
;
cursor
:
pointer
;
.radio-icon
{
font-size
:
28px
;
margin-bottom
:
8px
;
}
.radio-label
{
font-size
:
14px
;
font-weight
:
500
;
}
}
&
.approved
.radio-content
{
&
:hover
{
border-color
:
#52c41a
;
background-color
:
#f6ffed
;
}
}
&
.rejected
.radio-content
{
&
:hover
{
border-color
:
#ff4d4f
;
background-color
:
#fff2f0
;
}
}
}
:deep
(
.ant-radio-wrapper-checked
)
{
.approved
.radio-content
{
border-color
:
#52c41a
;
background-color
:
#f6ffed
;
.radio-icon
{
color
:
#52c41a
;
}
}
.rejected
.radio-content
{
border-color
:
#ff4d4f
;
background-color
:
#fff2f0
;
.radio-icon
{
color
:
#ff4d4f
;
}
}
}
}
.form-tip
{
font-size
:
12px
;
color
:
#ff4d4f
;
margin-top
:
4px
;
}
.additional-info
{
margin-top
:
16px
;
.info-tip
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
padding
:
8px
12px
;
background-color
:
#fff7e6
;
border-radius
:
4px
;
font-size
:
12px
;
color
:
#d46b00
;
.anticon
{
font-size
:
14px
;
}
}
}
.approval-summary
{
background-color
:
#fafbfc
;
border-radius
:
8px
;
padding
:
12px
;
.summary-item
{
display
:
flex
;
justify-content
:
space-between
;
margin-bottom
:
8px
;
font-size
:
13px
;
&
:last-child
{
margin-bottom
:
0
;
}
.summary-label
{
color
:
#8c8c8c
;
}
.summary-value
{
color
:
#262626
;
font-weight
:
500
;
}
}
}
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/CurrentFormPanel.vue
0 → 100644
浏览文件 @
78599ce5
<!-- CurrentFormPanel.vue -->
<
template
>
<div
class=
"form-panel"
>
<div
class=
"form-header"
>
<span
class=
"form-title"
>
当前待办
</span>
<a-tag
color=
"green"
v-if=
"editableNode"
>
可编辑
</a-tag>
</div>
<div
class=
"form-content"
v-if=
"editableNode"
>
<a-card
:title=
"editableNode.name"
:bordered=
"false"
class=
"current-form-card"
>
<template
#
extra
>
<a-tag
color=
"processing"
>
待处理
</a-tag>
</
template
>
<component
:is=
"getComponent(editableNode.formUrl || editableNode.formListUrl)"
:ref=
"(el) => setFormRef(el, editableNode.id)"
:disabled=
"false"
:readonly=
"false"
:form-data=
"formData"
:current-flow-node=
"editableNode"
@
update:form-data=
"handleFormDataUpdate"
/>
</a-card>
</div>
<div
v-else
class=
"empty-form-state"
>
<a-empty
description=
"未找到可编辑的表单节点"
/>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
defineAsyncComponent
,
h
,
watch
,
ComponentPublicInstance
,
nextTick
}
from
'vue'
interface
WorkflowNode
{
id
:
string
name
:
string
formUrl
?:
string
formListUrl
?:
string
[
key
:
string
]:
any
}
// 表单组件实例类型
interface
FormComponentInstance
extends
ComponentPublicInstance
{
validate
?:
()
=>
Promise
<
any
>
getFormData
?:
()
=>
any
initFormData
?:
(
dataId
:
string
)
=>
Promise
<
void
>
|
void
formData
?:
any
[
key
:
string
]:
any
}
const
props
=
defineProps
({
editableNode
:
{
type
:
Object
as
()
=>
WorkflowNode
|
null
,
default
:
null
},
dataId
:
{
type
:
String
,
default
:
''
},
externalFormData
:
{
type
:
Object
as
()
=>
Record
<
string
,
any
>
,
default
:
()
=>
({})
}
})
const
emit
=
defineEmits
([
'update:form-data'
,
'form-mounted'
])
// 组件缓存
const
componentCache
=
new
Map
()
const
modules
=
import
.
meta
.
glob
(
'@/views/**/*.vue'
)
// 表单组件实例
const
formComponentRef
=
ref
<
FormComponentInstance
|
null
>
(
null
)
const
formData
=
ref
<
any
>
({})
// 设置表单组件 ref
function
setFormRef
(
el
:
any
,
nodeId
:
string
)
{
if
(
el
)
{
formComponentRef
.
value
=
el
emit
(
'form-mounted'
,
{
nodeId
,
instance
:
el
})
callInitFormData
(
el
)
}
}
// 调用 initFormData
async
function
callInitFormData
(
formComponent
:
FormComponentInstance
)
{
if
(
!
props
.
dataId
)
return
if
(
!
formComponent
)
return
if
(
typeof
formComponent
.
initFormData
===
'function'
)
{
try
{
await
formComponent
.
initFormData
(
props
.
dataId
)
}
catch
(
error
)
{
console
.
error
(
'initFormData 调用失败:'
,
error
)
}
}
}
// 处理表单数据更新
function
handleFormDataUpdate
(
data
:
any
)
{
formData
.
value
=
{
...
formData
.
value
,
...
data
}
emit
(
'update:form-data'
,
formData
.
value
)
}
// 获取当前表单实例
function
getFormInstance
():
FormComponentInstance
|
null
{
return
formComponentRef
.
value
}
// 获取表单数据
async
function
getFormData
():
Promise
<
any
>
{
const
formComponent
=
formComponentRef
.
value
if
(
!
formComponent
)
{
return
formData
.
value
}
if
(
typeof
formComponent
.
getFormData
===
'function'
)
{
try
{
const
data
=
await
formComponent
.
getFormData
()
return
data
}
catch
(
error
)
{
console
.
error
(
'调用 getFormData 失败:'
,
error
)
}
}
if
(
formComponent
.
formData
!==
undefined
)
{
return
formComponent
.
formData
}
return
formData
.
value
}
// 验证表单
async
function
validateForm
():
Promise
<
boolean
>
{
const
formComponent
=
formComponentRef
.
value
if
(
!
formComponent
)
{
return
true
}
if
(
typeof
formComponent
.
validate
===
'function'
)
{
try
{
await
formComponent
.
validate
()
return
true
}
catch
(
error
)
{
return
false
}
}
return
true
}
// 重置表单数据
function
resetFormData
()
{
if
(
props
.
editableNode
&&
props
.
externalFormData
[
props
.
editableNode
.
id
])
{
formData
.
value
=
{
...
props
.
externalFormData
[
props
.
editableNode
.
id
]
}
}
else
{
formData
.
value
=
{}
}
}
// 重新加载表单数据
async
function
reloadFormData
()
{
if
(
formComponentRef
.
value
&&
typeof
formComponentRef
.
value
.
initFormData
===
'function'
)
{
await
formComponentRef
.
value
.
initFormData
(
props
.
dataId
)
}
}
// 监听 dataId 变化
watch
(()
=>
props
.
dataId
,
async
()
=>
{
if
(
formComponentRef
.
value
)
{
await
nextTick
()
await
reloadFormData
()
}
})
// 监听外部数据变化
watch
(()
=>
props
.
externalFormData
,
(
newData
)
=>
{
if
(
newData
&&
props
.
editableNode
&&
newData
[
props
.
editableNode
.
id
])
{
formData
.
value
=
newData
[
props
.
editableNode
.
id
]
}
},
{
deep
:
true
})
// 监听可编辑节点变化
watch
(()
=>
props
.
editableNode
,
()
=>
{
resetFormData
()
})
function
getComponent
(
url
:
string
)
{
if
(
!
url
)
{
return
createEmptyComponent
()
}
if
(
componentCache
.
has
(
url
))
{
return
componentCache
.
get
(
url
)
}
let
componentPath
=
''
if
(
url
.
includes
(
'/views'
))
{
componentPath
=
`/src
${
url
}
`
}
else
{
componentPath
=
`/src/views
${
url
}
`
}
if
(
!
componentPath
.
match
(
/
\.(
vue|js|ts|jsx|tsx
)
$/
))
{
componentPath
+=
'.vue'
}
const
loader
=
modules
[
componentPath
]
if
(
!
loader
)
{
console
.
error
(
'未找到组件:'
,
componentPath
)
const
ErrorComponent
=
createErrorComponent
(
`组件未找到:
${
componentPath
}
`
)
componentCache
.
set
(
url
,
ErrorComponent
)
return
ErrorComponent
}
const
AsyncComponent
=
defineAsyncComponent
({
loader
:
()
=>
loader
()
as
Promise
<
{
default
:
any
}
>
,
loadingComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'text-align: center; padding: 20px;'
},
'加载中...'
)
},
errorComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
'组件加载失败'
)
},
delay
:
200
,
timeout
:
3000
})
componentCache
.
set
(
url
,
AsyncComponent
)
return
AsyncComponent
}
function
createEmptyComponent
()
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: #999; padding: 20px; text-align: center;'
},
'该节点未配置表单'
)
}
}
function
createErrorComponent
(
msg
:
string
)
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
msg
)
}
}
// 暴露方法给父组件
defineExpose
({
getFormData
,
validateForm
,
getFormInstance
,
resetFormData
,
reloadFormData
})
</
script
>
<
style
scoped
lang=
"scss"
>
.form-panel
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
overflow
:
hidden
;
background-color
:
#fff
;
.form-header
{
padding
:
16px
24px
;
border-bottom
:
1px
solid
#e8eef2
;
background-color
:
#fff
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
flex-shrink
:
0
;
.form-title
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#1f2f3d
;
}
}
.form-content
{
flex
:
1
;
overflow-y
:
auto
;
padding
:
24px
;
&
:
:-
webkit-scrollbar
{
width
:
6px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
border-radius
:
3px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
3px
;
&
:hover
{
background
:
#a8a8a8
;
}
}
}
.current-form-card
{
border-radius
:
8px
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.06
);
border
:
1px
solid
#e8eef2
;
:deep
(
.ant-card-head
)
{
background-color
:
#fafbfc
;
border-bottom
:
1px
solid
#e8eef2
;
padding
:
12px
20px
;
.ant-card-head-title
{
font-size
:
15px
;
font-weight
:
500
;
color
:
#096dd9
;
}
}
:deep
(
.ant-card-body
)
{
padding
:
24px
;
}
}
.empty-form-state
{
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/HistoryPanel.vue
0 → 100644
浏览文件 @
78599ce5
<!-- HistoryPanel.vue -->
<
template
>
<div
class=
"history-panel"
:class=
"
{ 'empty-history': readonlyNodes.length === 0 }">
<div
class=
"history-header"
>
<span
class=
"history-title"
>
历史节点
</span>
<a-tag
color=
"blue"
v-if=
"readonlyNodes.length > 0"
>
{{
readonlyNodes
.
length
}}
个节点
</a-tag>
</div>
<div
class=
"history-content"
:class=
"
{ 'has-scroll': readonlyNodes.length > 3 }">
<div
v-if=
"readonlyNodes.length === 0"
class=
"empty-history-state"
>
<a-empty
description=
"暂无历史节点"
:image=
"simpleImage"
/>
</div>
<a-timeline
v-else
>
<a-timeline-item
v-for=
"(node, index) in readonlyNodes"
:key=
"node.id"
:color=
"index === readonlyNodes.length - 1 ? 'blue' : 'gray'"
>
<template
#
dot
>
<div
class=
"timeline-dot"
>
<check-circle-outlined
v-if=
"index
<
readonlyNodes
.
length
-
1
"
/>
<clock-circle-outlined
v-else
/>
</div>
</
template
>
<a-card
:title=
"node.name"
:bordered=
"false"
size=
"small"
class=
"history-card"
:class=
"{ 'last-card': index === readonlyNodes.length - 1 }"
>
<
template
#
extra
>
<a-tag
:color=
"index === readonlyNodes.length - 1 ? 'orange' : 'green'"
size=
"small"
>
{{
index
===
readonlyNodes
.
length
-
1
?
'已通过'
:
'已完成'
}}
</a-tag>
</
template
>
<div
class=
"history-card-content"
>
<div
class=
"node-info"
>
<span
class=
"node-label"
>
表单名称:
</span>
<span
class=
"node-value"
>
{{ node.formName || node.name }}
</span>
</div>
<div
class=
"node-info"
>
<span
class=
"node-label"
>
处理时间:
</span>
<span
class=
"node-value"
>
{{ node.processTime || '--' }}
</span>
</div>
<div
class=
"node-info"
>
<span
class=
"node-label"
>
处理人:
</span>
<span
class=
"node-value"
>
{{ node.processor || '--' }}
</span>
</div>
<div
class=
"node-info"
>
<span
class=
"node-label"
>
审批意见:
</span>
<span
class=
"node-value"
>
{{ node.comment || '无' }}
</span>
</div>
</div>
<!-- 如果需要显示历史表单数据,可以展开 -->
<
template
v-if=
"showHistoryFormData"
>
<a-divider
style=
"margin: 12px 0"
/>
<div
class=
"history-form-preview"
>
<div
class=
"preview-header"
>
<span>
表单数据预览
</span>
<a-button
type=
"link"
size=
"small"
@
click=
"togglePreview(node.id)"
>
{{
expandedPreviewId
===
node
.
id
?
'收起'
:
'展开'
}}
</a-button>
</div>
<a-collapse
v-model:activeKey=
"expandedPreviewId"
:bordered=
"false"
ghost
>
<a-collapse-panel
:key=
"node.id"
:showArrow=
"false"
>
<component
:is=
"getComponent(node.formUrl)"
:disabled=
"true"
:readonly=
"true"
:form-data=
"
{ dataId: dataId }"
:formId="Number(dataId) || 2035539989"
:current-flow-node="node"
class="history-form-component"
/>
</a-collapse-panel>
</a-collapse>
</div>
</
template
>
</a-card>
</a-timeline-item>
</a-timeline>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
defineAsyncComponent
,
h
}
from
'vue'
import
{
CheckCircleOutlined
,
ClockCircleOutlined
}
from
'@ant-design/icons-vue'
import
{
Empty
}
from
'ant-design-vue'
interface
WorkflowNode
{
id
:
string
name
:
string
formName
?:
string
formUrl
?:
string
formListUrl
?:
string
procDefId
?:
string
dataId
?:
string
processTime
?:
string
processor
?:
string
comment
?:
string
[
key
:
string
]:
any
}
const
props
=
defineProps
({
readonlyNodes
:
{
type
:
Array
as
()
=>
WorkflowNode
[],
required
:
true
,
default
:
()
=>
[]
},
dataId
:
{
type
:
String
,
default
:
''
},
showHistoryFormData
:
{
type
:
Boolean
,
default
:
false
}
})
// 简单空状态图片
const
simpleImage
=
Empty
.
PRESENTED_IMAGE_SIMPLE
// 组件缓存
const
componentCache
=
new
Map
()
const
modules
=
import
.
meta
.
glob
(
'@/views/**/*.vue'
)
// 历史表单预览展开状态
const
expandedPreviewId
=
ref
<
string
>
(
''
)
// 切换预览展开/收起
function
togglePreview
(
nodeId
:
string
)
{
expandedPreviewId
.
value
=
expandedPreviewId
.
value
===
nodeId
?
''
:
nodeId
}
function
getComponent
(
url
:
string
)
{
if
(
!
url
)
{
return
createEmptyComponent
()
}
if
(
componentCache
.
has
(
url
))
{
return
componentCache
.
get
(
url
)
}
let
componentPath
=
''
if
(
url
.
includes
(
'/views'
))
{
componentPath
=
`/src
${
url
}
`
}
else
{
componentPath
=
`/src/views
${
url
}
`
}
if
(
!
componentPath
.
match
(
/
\.(
vue|js|ts|jsx|tsx
)
$/
))
{
componentPath
+=
'.vue'
}
const
loader
=
modules
[
componentPath
]
if
(
!
loader
)
{
console
.
error
(
'未找到组件:'
,
componentPath
)
const
ErrorComponent
=
createErrorComponent
(
`组件未找到:
${
componentPath
}
`
)
componentCache
.
set
(
url
,
ErrorComponent
)
return
ErrorComponent
}
const
AsyncComponent
=
defineAsyncComponent
({
loader
:
()
=>
loader
()
as
Promise
<
{
default
:
any
}
>
,
loadingComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'text-align: center; padding: 20px;'
},
'加载中...'
)
},
errorComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
'组件加载失败'
)
},
delay
:
200
,
timeout
:
3000
})
componentCache
.
set
(
url
,
AsyncComponent
)
return
AsyncComponent
}
function
createEmptyComponent
()
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: #999; padding: 20px; text-align: center;'
},
'该节点未配置表单'
)
}
}
function
createErrorComponent
(
msg
:
string
)
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
msg
)
}
}
</
script
>
<
style
scoped
lang=
"scss"
>
.history-panel
{
width
:
50%
;
background-color
:
#f5f7fa
;
border-right
:
1px
solid
#e8eef2
;
display
:
flex
;
flex-direction
:
column
;
overflow
:
hidden
;
&
.empty-history
{
.history-content
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
}
.history-header
{
padding
:
16px
20px
;
border-bottom
:
1px
solid
#e8eef2
;
background-color
:
#fff
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
flex-shrink
:
0
;
.history-title
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#1f2f3d
;
}
}
.history-content
{
flex
:
1
;
overflow-y
:
auto
;
padding
:
16px
12px
;
&
.has-scroll
{
padding-right
:
8px
;
}
&
:
:-
webkit-scrollbar
{
width
:
4px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#e8eef2
;
border-radius
:
2px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
2px
;
&
:hover
{
background
:
#a8a8a8
;
}
}
}
.empty-history-state
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
height
:
100%
;
}
:deep
(
.ant-timeline
)
{
.ant-timeline-item
{
padding-bottom
:
20px
;
}
.ant-timeline-item-tail
{
border-left-color
:
#d9d9d9
;
}
}
.timeline-dot
{
width
:
24px
;
height
:
24px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background-color
:
#fff
;
border-radius
:
50%
;
font-size
:
14px
;
.anticon
{
font-size
:
14px
;
}
}
.history-card
{
background-color
:
#fff
;
border-radius
:
8px
;
margin-bottom
:
8px
;
box-shadow
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0
.03
);
transition
:
box-shadow
0
.2s
;
&
:hover
{
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.08
);
}
&
.last-card
{
border-left
:
3px
solid
#1890ff
;
}
:deep
(
.ant-card-head
)
{
padding
:
12px
16px
;
min-height
:
auto
;
border-bottom
:
1px
solid
#f0f0f0
;
.ant-card-head-title
{
font-size
:
14px
;
font-weight
:
500
;
padding
:
0
;
}
.ant-card-extra
{
padding
:
0
;
}
}
:deep
(
.ant-card-body
)
{
padding
:
12px
16px
;
}
}
.history-card-content
{
.node-info
{
display
:
flex
;
margin-bottom
:
8px
;
font-size
:
13px
;
&
:last-child
{
margin-bottom
:
0
;
}
.node-label
{
width
:
70px
;
color
:
#8c8c8c
;
flex-shrink
:
0
;
}
.node-value
{
color
:
#262626
;
flex
:
1
;
word-break
:
break-all
;
}
}
}
.history-form-preview
{
margin-top
:
8px
;
.preview-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
font-size
:
12px
;
color
:
#8c8c8c
;
}
:deep
(
.ant-collapse
)
{
background
:
transparent
;
.ant-collapse-item
{
border
:
none
;
}
.ant-collapse-content
{
border
:
none
;
background
:
#fafafa
;
border-radius
:
4px
;
margin-top
:
8px
;
}
.ant-collapse-content-box
{
padding
:
12px
;
font-size
:
12px
;
}
}
}
.history-form-component
{
:deep
(*)
{
font-size
:
12px
;
}
}
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/TaskAssigneeDrawer.vue
0 → 100644
浏览文件 @
78599ce5
<!-- TaskAssigneeDrawer.vue -->
<
template
>
<a-drawer
:title=
"title"
:visible=
"visible"
:width=
"width"
:closable=
"true"
:mask-closable=
"maskClosable"
:destroy-on-close=
"destroyOnClose"
:footer=
"null"
@
close=
"handleClose"
class=
"task-assignee-drawer"
>
<div
class=
"drawer-content"
>
<!-- 任务分配组件 -->
<div
class=
"assignee-section"
>
<TaskAssigneeSelector
ref=
"assigneeSelectorRef"
:title=
"assigneeTitle"
:proc-def-id=
"procDefId"
:task-id=
"taskId"
:form-data=
"formData"
:initial-assignee=
"initialAssignee"
:user-type-options=
"userTypeOptions"
:required=
"required"
:next-api=
"customNextApi"
@
confirm=
"handleAssigneeConfirm"
@
success=
"handleTaskSuccess"
@
error=
"handleTaskError"
@
cancel=
"handleCancel"
/>
</div>
</div>
</a-drawer>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
watch
}
from
'vue'
import
{
message
}
from
'ant-design-vue'
import
TaskAssigneeSelector
from
'./TaskAssigneeSelector.vue'
interface
AssigneeData
{
userType
:
'user'
|
'role'
assignee
:
string
name
?:
string
remark
?:
string
}
const
props
=
defineProps
({
// 抽屉基础配置
visible
:
{
type
:
Boolean
,
default
:
false
},
title
:
{
type
:
String
,
default
:
'任务分配'
},
width
:
{
type
:
[
Number
,
String
],
default
:
600
},
maskClosable
:
{
type
:
Boolean
,
default
:
false
},
destroyOnClose
:
{
type
:
Boolean
,
default
:
true
},
// 任务信息
taskId
:
{
type
:
String
,
required
:
true
},
procDefId
:
{
type
:
String
,
required
:
true
},
// 表单数据
formData
:
{
type
:
Object
,
default
:
()
=>
({})
},
// 初始分配数据
initialAssignee
:
{
type
:
Object
as
()
=>
AssigneeData
|
null
,
default
:
null
},
// 分配组件配置
assigneeTitle
:
{
type
:
String
,
default
:
'选择处理人'
},
userTypeOptions
:
{
type
:
Array
as
()
=>
Array
<
{
value
:
string
;
label
:
string
}
>
,
default
:
()
=>
[
{
value
:
'user'
,
label
:
'用户'
},
{
value
:
'role'
,
label
:
'角色'
}
]
},
required
:
{
type
:
Boolean
,
default
:
true
},
// 自定义API
customNextApi
:
{
type
:
Function
,
default
:
null
}
})
const
emit
=
defineEmits
([
'update:visible'
,
'success'
,
'error'
,
'close'
,
'assignee-confirm'
])
// 状态
const
assigneeSelectorRef
=
ref
<
InstanceType
<
typeof
TaskAssigneeSelector
>
|
null
>
(
null
)
// 处理分配确认
function
handleAssigneeConfirm
(
assigneeData
:
AssigneeData
)
{
emit
(
'assignee-confirm'
,
assigneeData
)
}
// 处理任务成功
function
handleTaskSuccess
(
response
:
any
)
{
emit
(
'success'
,
response
)
// 成功后自动关闭抽屉
setTimeout
(()
=>
{
handleClose
()
},
500
)
}
// 处理任务失败
function
handleTaskError
(
error
:
any
)
{
emit
(
'error'
,
error
)
}
// 处理取消
function
handleCancel
()
{
handleClose
()
}
// 关闭抽屉
function
handleClose
()
{
emit
(
'update:visible'
,
false
)
emit
(
'close'
)
}
// 重置状态
function
resetDrawer
()
{
if
(
assigneeSelectorRef
.
value
)
{
assigneeSelectorRef
.
value
.
resetForm
()
}
}
// 监听抽屉打开/关闭
watch
(()
=>
props
.
visible
,
(
newVal
)
=>
{
if
(
!
newVal
)
{
resetDrawer
()
}
})
// 暴露方法
defineExpose
({
resetDrawer
,
getAssigneeData
:
()
=>
assigneeSelectorRef
.
value
?.
getAssigneeData
(),
submit
:
()
=>
assigneeSelectorRef
.
value
?.
handleConfirm
()
})
</
script
>
<
style
scoped
lang=
"scss"
>
.task-assignee-drawer
{
:deep
(
.ant-drawer-body
)
{
padding
:
0
;
height
:
100%
;
overflow
:
hidden
;
}
:deep
(
.ant-drawer-header
)
{
background-color
:
#f5f7fa
;
border-bottom
:
1px
solid
#e8eef2
;
padding
:
16px
24px
;
.ant-drawer-title
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#1f2f3d
;
}
.ant-drawer-close
{
color
:
#8c8c8c
;
&
:hover
{
color
:
#1f2f3d
;
}
}
}
}
.drawer-content
{
height
:
100%
;
overflow-y
:
auto
;
padding
:
16px
20px
;
&
:
:-
webkit-scrollbar
{
width
:
4px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
border-radius
:
2px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
2px
;
&
:hover
{
background
:
#a8a8a8
;
}
}
}
// 分配区域
.assignee-section
{
height
:
100%
;
:deep
(
.task-assignee-selector
)
{
height
:
100%
;
.assignee-card
{
height
:
100%
;
box-shadow
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0
.03
);
:deep
(
.ant-card-head
)
{
background-color
:
#fff
;
border-bottom
:
1px
solid
#f0f0f0
;
}
:deep
(
.ant-card-body
)
{
height
:
calc
(
100%
-
57px
);
}
}
}
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/TaskAssigneeSelector.vue
0 → 100644
浏览文件 @
78599ce5
<!-- TaskAssigneeSelector.vue -->
<
template
>
<div
class=
"task-assignee-selector"
>
<a-card
:title=
"title"
:bordered=
"false"
class=
"assignee-card"
>
<template
#
extra
>
<a-tag
:color=
"assigneeStatus.color"
>
{{
assigneeStatus
.
text
}}
</a-tag>
</
template
>
<a-form
:model=
"formData"
layout=
"vertical"
>
<!-- 分配类型选择 -->
<a-form-item
label=
"分配类型"
required
>
<a-radio-group
v-model:value=
"formData.userType"
@
change=
"handleUserTypeChange"
>
<a-radio
value=
"user"
>
<user-outlined
/>
用户
</a-radio>
<a-radio
value=
"role"
>
<team-outlined
/>
角色
</a-radio>
</a-radio-group>
</a-form-item>
<!-- 用户选择区域 -->
<div
v-if=
"formData.userType === 'user'"
class=
"assignee-section"
>
<a-form-item
label=
"选择用户"
:required=
"true"
:validate-status=
"userValidateStatus"
:help=
"userValidateHelp"
>
<div
class=
"assignee-selector"
>
<a-input
v-model:value=
"formData.userName"
:placeholder=
"userPlaceholder"
:readonly=
"true"
class=
"assignee-input"
>
<
template
#
suffix
>
<a-button
type=
"link"
size=
"small"
@
click=
"handleSelectUser"
class=
"select-btn"
>
选择
</a-button>
</
template
>
</a-input>
<a-button
v-if=
"formData.assignee"
type=
"text"
size=
"small"
@
click=
"clearAssignee"
class=
"clear-btn"
>
<close-circle-outlined
/>
</a-button>
</div>
</a-form-item>
<!-- 用户信息展示 -->
<div
v-if=
"formData.assignee"
class=
"assignee-info"
>
<a-descriptions
:column=
"1"
size=
"small"
bordered
>
<a-descriptions-item
label=
"用户ID"
>
{{ formData.assignee }}
</a-descriptions-item>
<a-descriptions-item
label=
"用户名称"
>
{{ formData.userName }}
</a-descriptions-item>
<a-descriptions-item
label=
"用户类型"
>
<a-tag
color=
"blue"
>
系统用户
</a-tag>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
<!-- 角色选择区域 -->
<div
v-if=
"formData.userType === 'role'"
class=
"assignee-section"
>
<a-form-item
label=
"选择角色"
:required=
"true"
:validate-status=
"roleValidateStatus"
:help=
"roleValidateHelp"
>
<div
class=
"assignee-selector"
>
<a-input
v-model:value=
"formData.roleName"
:placeholder=
"rolePlaceholder"
:readonly=
"true"
class=
"assignee-input"
>
<
template
#
suffix
>
<a-button
type=
"link"
size=
"small"
@
click=
"handleSelectRole"
class=
"select-btn"
>
选择
</a-button>
</
template
>
</a-input>
<a-button
v-if=
"formData.assignee"
type=
"text"
size=
"small"
@
click=
"clearAssignee"
class=
"clear-btn"
>
<close-circle-outlined
/>
</a-button>
</div>
</a-form-item>
<!-- 角色信息展示 -->
<div
v-if=
"formData.assignee"
class=
"assignee-info"
>
<a-descriptions
:column=
"1"
size=
"small"
bordered
>
<a-descriptions-item
label=
"角色ID"
>
{{ formData.assignee }}
</a-descriptions-item>
<a-descriptions-item
label=
"角色名称"
>
{{ formData.roleName }}
</a-descriptions-item>
<a-descriptions-item
label=
"角色类型"
>
<a-tag
color=
"purple"
>
系统角色
</a-tag>
</a-descriptions-item>
<a-descriptions-item
v-if=
"roleMembers.length"
label=
"角色成员"
>
<a-tooltip
:title=
"roleMembers.join(', ')"
>
<span>
{{ roleMembers.length }}人
</span>
</a-tooltip>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
<!-- 备注信息 -->
<a-form-item
label=
"备注"
>
<a-textarea
v-model:value=
"formData.remark"
placeholder=
"请输入备注信息(选填)"
:rows=
"3"
:maxlength=
"200"
show-count
/>
</a-form-item>
</a-form>
<!-- 操作按钮 -->
<div
class=
"assignee-actions"
>
<a-space>
<a-button
@
click=
"handleCancel"
>
取消
</a-button>
<a-button
type=
"primary"
:loading=
"confirmLoading"
@
click=
"handleConfirm"
:disabled=
"!isValid"
>
确认
</a-button>
</a-space>
</div>
</a-card>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
watch
}
from
'vue'
import
{
message
}
from
'ant-design-vue'
import
{
UserOutlined
,
TeamOutlined
,
CloseCircleOutlined
}
from
'@ant-design/icons-vue'
//import { nextWorkflowNode } from '@/api/workflow'
interface
AssigneeData
{
userType
:
'user'
|
'role'
assignee
:
string
userName
?:
string
roleName
?:
string
remark
?:
string
[
key
:
string
]:
any
}
interface
WorkflowParams
{
procDefId
:
string
taskId
:
string
formData
:
any
assignee
?:
AssigneeData
[
key
:
string
]:
any
}
const
props
=
defineProps
({
// 标题
title
:
{
type
:
String
,
default
:
'任务分配'
},
// 流程定义ID
procDefId
:
{
type
:
String
,
required
:
true
},
// 任务ID
taskId
:
{
type
:
String
,
required
:
true
},
// 表单数据
formData
:
{
type
:
Object
,
default
:
()
=>
({})
},
// 初始分配数据
initialAssignee
:
{
type
:
Object
as
()
=>
AssigneeData
|
null
,
default
:
null
},
// 是否必须分配
required
:
{
type
:
Boolean
,
default
:
true
},
// 工作流下一步接口配置
nextApi
:
{
type
:
Function
,
default
:
null
}
})
const
emit
=
defineEmits
([
'confirm'
,
'cancel'
,
'success'
,
'error'
,
'select-user'
,
// 选择用户事件
'select-role'
// 选择角色事件
])
// 表单数据
const
formData
=
ref
<
AssigneeData
>
({
userType
:
'user'
,
assignee
:
''
,
userName
:
''
,
roleName
:
''
,
remark
:
''
})
// 状态
const
confirmLoading
=
ref
(
false
)
// 验证状态
const
userValidateStatus
=
computed
(()
=>
{
if
(
!
props
.
required
)
return
''
if
(
formData
.
value
.
userType
===
'user'
&&
!
formData
.
value
.
assignee
)
{
return
'error'
}
return
''
})
const
userValidateHelp
=
computed
(()
=>
{
if
(
userValidateStatus
.
value
===
'error'
)
{
return
'请选择用户'
}
return
''
})
const
roleValidateStatus
=
computed
(()
=>
{
if
(
!
props
.
required
)
return
''
if
(
formData
.
value
.
userType
===
'role'
&&
!
formData
.
value
.
assignee
)
{
return
'error'
}
return
''
})
const
roleValidateHelp
=
computed
(()
=>
{
if
(
roleValidateStatus
.
value
===
'error'
)
{
return
'请选择角色'
}
return
''
})
// 占位符
const
userPlaceholder
=
computed
(()
=>
{
if
(
formData
.
value
.
assignee
)
{
return
formData
.
value
.
userName
||
'已选择用户'
}
return
'点击"选择"按钮选择用户'
})
const
rolePlaceholder
=
computed
(()
=>
{
if
(
formData
.
value
.
assignee
)
{
return
formData
.
value
.
roleName
||
'已选择角色'
}
return
'点击"选择"按钮选择角色'
})
// 分配状态
const
assigneeStatus
=
computed
(()
=>
{
if
(
!
formData
.
value
.
assignee
)
{
return
{
text
:
'未分配'
,
color
:
'default'
}
}
if
(
formData
.
value
.
userType
===
'user'
)
{
return
{
text
:
`已分配用户:
${
formData
.
value
.
userName
}
`
,
color
:
'green'
}
}
return
{
text
:
`已分配角色:
${
formData
.
value
.
roleName
}
`
,
color
:
'blue'
}
})
// 角色成员(示例数据,实际应从接口获取)
const
roleMembers
=
ref
<
string
[]
>
([])
// 是否有效
const
isValid
=
computed
(()
=>
{
if
(
!
props
.
required
)
return
true
if
(
!
formData
.
value
.
assignee
)
return
false
return
true
})
// 处理用户类型切换
function
handleUserTypeChange
()
{
clearAssignee
()
}
// 处理选择用户(触发父组件事件)
function
handleSelectUser
()
{
emit
(
'select-user'
,
{
currentAssignee
:
formData
.
value
.
assignee
,
onSelect
:
(
user
:
any
)
=>
{
setUserData
(
user
)
}
})
}
// 处理选择角色(触发父组件事件)
function
handleSelectRole
()
{
emit
(
'select-role'
,
{
currentAssignee
:
formData
.
value
.
assignee
,
onSelect
:
(
role
:
any
)
=>
{
setRoleData
(
role
)
}
})
}
// 设置用户数据(供父组件调用)
function
setUserData
(
user
:
any
)
{
formData
.
value
.
assignee
=
user
.
id
formData
.
value
.
userName
=
user
.
name
||
user
.
realName
||
user
.
username
message
.
success
(
`已选择用户:
${
formData
.
value
.
userName
}
`
)
}
// 设置角色数据(供父组件调用)
async
function
setRoleData
(
role
:
any
)
{
formData
.
value
.
assignee
=
role
.
id
formData
.
value
.
roleName
=
role
.
name
||
role
.
roleName
// 获取角色成员(可选)
try
{
// const members = await getRoleMembers(role.id)
// roleMembers.value = members
roleMembers
.
value
=
[]
// 示例数据
}
catch
(
error
)
{
console
.
error
(
'获取角色成员失败:'
,
error
)
}
message
.
success
(
`已选择角色:
${
formData
.
value
.
roleName
}
`
)
}
// 清空分配
function
clearAssignee
()
{
formData
.
value
.
assignee
=
''
formData
.
value
.
userName
=
''
formData
.
value
.
roleName
=
''
roleMembers
.
value
=
[]
}
// 取消
function
handleCancel
()
{
emit
(
'cancel'
)
}
// 确认并调用工作流下一步
async
function
handleConfirm
()
{
if
(
props
.
required
&&
!
formData
.
value
.
assignee
)
{
message
.
error
(
formData
.
value
.
userType
===
'user'
?
'请选择用户'
:
'请选择角色'
)
return
}
confirmLoading
.
value
=
true
try
{
// 构建分配参数
const
assigneeData
=
{
userType
:
formData
.
value
.
userType
,
assignee
:
formData
.
value
.
assignee
,
name
:
formData
.
value
.
userType
===
'user'
?
formData
.
value
.
userName
:
formData
.
value
.
roleName
,
remark
:
formData
.
value
.
remark
}
// 构建工作流参数
const
workflowParams
:
WorkflowParams
=
{
procDefId
:
props
.
procDefId
,
taskId
:
props
.
taskId
,
formData
:
props
.
formData
,
assignee
:
assigneeData
,
completeTime
:
new
Date
().
toISOString
()
}
// 触发确认事件
emit
(
'confirm'
,
assigneeData
)
// 调用工作流下一步接口
let
response
if
(
props
.
nextApi
)
{
response
=
await
props
.
nextApi
(
workflowParams
)
}
else
{
//response = await nextWorkflowNode(workflowParams)
}
if
(
response
&&
response
.
success
!==
false
)
{
message
.
success
(
'任务提交成功'
)
emit
(
'success'
,
response
)
return
response
}
else
{
throw
new
Error
(
response
?.
message
||
'提交失败'
)
}
}
catch
(
error
:
any
)
{
console
.
error
(
'提交失败:'
,
error
)
message
.
error
(
error
?.
message
||
'提交失败,请重试'
)
emit
(
'error'
,
error
)
throw
error
}
finally
{
confirmLoading
.
value
=
false
}
}
// 获取分配数据
function
getAssigneeData
()
{
return
{
userType
:
formData
.
value
.
userType
,
assignee
:
formData
.
value
.
assignee
,
name
:
formData
.
value
.
userType
===
'user'
?
formData
.
value
.
userName
:
formData
.
value
.
roleName
,
remark
:
formData
.
value
.
remark
}
}
// 设置分配数据
function
setAssigneeData
(
data
:
AssigneeData
)
{
if
(
data
)
{
formData
.
value
=
{
userType
:
data
.
userType
||
'user'
,
assignee
:
data
.
assignee
||
''
,
userName
:
data
.
userName
||
''
,
roleName
:
data
.
roleName
||
''
,
remark
:
data
.
remark
||
''
}
}
}
// 重置表单
function
resetForm
()
{
formData
.
value
=
{
userType
:
'user'
,
assignee
:
''
,
userName
:
''
,
roleName
:
''
,
remark
:
''
}
roleMembers
.
value
=
[]
}
// 监听初始数据
watch
(()
=>
props
.
initialAssignee
,
(
newVal
)
=>
{
if
(
newVal
)
{
setAssigneeData
(
newVal
)
}
},
{
immediate
:
true
})
// 暴露方法
defineExpose
({
getAssigneeData
,
setAssigneeData
,
resetForm
,
setUserData
,
// 暴露设置用户数据的方法
setRoleData
,
// 暴露设置角色数据的方法
validate
:
()
=>
isValid
.
value
})
</
script
>
<
style
scoped
lang=
"scss"
>
.task-assignee-selector
{
height
:
100%
;
.assignee-card
{
height
:
100%
;
border-radius
:
8px
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.06
);
:deep
(
.ant-card-head
)
{
background-color
:
#fafbfc
;
border-bottom
:
1px
solid
#e8eef2
;
.ant-card-head-title
{
font-size
:
16px
;
font-weight
:
500
;
}
}
:deep
(
.ant-card-body
)
{
padding
:
24px
;
height
:
calc
(
100%
-
57px
);
overflow-y
:
auto
;
display
:
flex
;
flex-direction
:
column
;
}
}
.assignee-section
{
margin-bottom
:
16px
;
}
.assignee-selector
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
.assignee-input
{
flex
:
1
;
:deep
(
.ant-input
)
{
background-color
:
#fafafa
;
cursor
:
pointer
;
&
:hover
{
background-color
:
#fff
;
}
}
.select-btn
{
padding
:
0
8px
;
color
:
#1890ff
;
}
}
.clear-btn
{
color
:
#999
;
font-size
:
14px
;
&
:hover
{
color
:
#ff4d4f
;
}
}
}
.assignee-info
{
margin-top
:
12px
;
:deep
(
.ant-descriptions
)
{
.ant-descriptions-item-label
{
background-color
:
#fafbfc
;
width
:
80px
;
}
.ant-descriptions-item-content
{
background-color
:
#fff
;
}
}
}
.assignee-actions
{
margin-top
:
auto
;
padding-top
:
24px
;
text-align
:
center
;
border-top
:
1px
solid
#f0f0f0
;
}
:deep
(
.ant-form-item
)
{
margin-bottom
:
20px
;
&
:last-child
{
margin-bottom
:
0
;
}
}
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/WorkFlowForm.vue
deleted
100644 → 0
浏览文件 @
ca1f544f
<
template
>
<div
class=
"multi-form-preview"
>
<!-- 只读表单列表:前 N-1 个 -->
<div
v-for=
"(node, idx) in previousNodes"
:key=
"`readonly-$
{idx}`" class="form-readonly-item">
<div
class=
"form-header"
>
<span
class=
"form-name"
>
{{
node
.
name
}}
</span>
<a-tag
color=
"blue"
class=
"readonly-tag"
>
只读
</a-tag>
</div>
<div
class=
"form-content readonly"
>
<component
:is=
"loadComponent(node.formUrl)"
:disabled=
"true"
:readonly=
"true"
:form-data=
"formDataMap[node.id]"
:current-flow-node=
"node"
/>
</div>
</div>
<!-- 可编辑表单:第 N 个(当前节点) -->
<div
v-if=
"currentNode"
class=
"form-editable-item"
>
<div
class=
"form-header"
>
<span
class=
"form-name"
>
{{
currentNode
.
name
}}
</span>
<a-tag
color=
"green"
>
可编辑
</a-tag>
</div>
<div
class=
"form-content editable"
>
<component
:is=
"loadComponent(currentNode.formUrl)"
:disabled=
"false"
:readonly=
"false"
:form-data=
"currentFormData"
:current-flow-node=
"currentNode"
@
update:form-data=
"handleFormDataUpdate"
@
submit=
"handleSubmit"
/>
</div>
</div>
<!-- 加载错误提示 -->
<a-empty
v-if=
"!currentNode && previousNodes.length === 0"
description=
"未找到有效表单节点"
/>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
onMounted
,
defineAsyncComponent
,
h
,
watch
}
from
'vue'
;
import
{
message
}
from
'ant-design-vue'
;
// 定义组件属性
const
props
=
defineProps
({
// 当前节点在 workflowNodes 中的索引(从 0 开始)
currentNodeIndex
:
{
type
:
Number
,
required
:
true
,
default
:
2
// 默认第三个(索引2)
},
// 工作流节点列表(从父组件传入)
workflowNodes
:
{
type
:
Array
as
()
=>
Array
<
any
>
,
required
:
true
,
default
:
()
=>
[]
},
// 外部传入的表单数据(用于回显)
externalFormData
:
{
type
:
Object
,
default
:
()
=>
({})
}
});
// 定义事件
const
emit
=
defineEmits
([
'form-data-update'
,
'submit'
]);
// 组件缓存
const
componentCache
=
new
Map
();
// 使用 import.meta.glob 预加载所有可能的组件
const
modules
=
import
.
meta
.
glob
(
'@/views/**/*.vue'
);
// 当前节点的表单数据(可编辑)
const
currentFormData
=
ref
<
any
>
({});
// 只读节点的表单数据映射(按节点id存储)
const
formDataMap
=
ref
<
Record
<
string
,
any
>>
({});
// 获取前 N 个节点(只读部分)
const
previousNodes
=
computed
(()
=>
{
if
(
!
props
.
workflowNodes
||
props
.
workflowNodes
.
length
===
0
)
return
[];
const
idx
=
props
.
currentNodeIndex
;
// 取索引小于 idx 的节点(即当前节点之前的所有节点)
return
props
.
workflowNodes
.
slice
(
0
,
idx
);
});
// 获取当前节点(第 N 个,可编辑)
const
currentNode
=
computed
(()
=>
{
if
(
!
props
.
workflowNodes
||
props
.
workflowNodes
.
length
===
0
)
return
null
;
const
idx
=
props
.
currentNodeIndex
;
if
(
idx
<
0
||
idx
>=
props
.
workflowNodes
.
length
)
return
null
;
return
props
.
workflowNodes
[
idx
];
});
// 将 URL 转换为正确的导入路径并动态加载组件
function
loadComponent
(
url
:
string
)
{
if
(
!
url
)
{
console
.
warn
(
'formUrl 为空'
);
return
createEmptyComponent
();
}
if
(
componentCache
.
has
(
url
))
{
return
componentCache
.
get
(
url
);
}
// 构建组件路径(使用 /@/ 格式)
let
componentPath
=
''
;
if
(
url
.
includes
(
'/views'
))
{
componentPath
=
`/src
${
url
}
`
;
}
else
{
componentPath
=
`/src/views
${
url
}
`
;
}
// 确保有文件后缀
if
(
!
componentPath
.
match
(
/
\.(
vue|js|ts|jsx|tsx
)
$/
))
{
componentPath
+=
'.vue'
;
}
console
.
log
(
'加载组件路径:'
,
componentPath
);
// 从预加载的 modules 中获取加载器
let
loader
=
modules
[
componentPath
];
if
(
!
loader
)
{
console
.
error
(
'未找到组件:'
,
componentPath
);
console
.
log
(
'可用路径示例:'
,
Object
.
keys
(
modules
).
slice
(
0
,
5
));
const
ErrorComponent
=
createErrorComponent
(
`组件未找到:
${
componentPath
}
`
);
componentCache
.
set
(
url
,
ErrorComponent
);
return
ErrorComponent
;
}
// 创建异步组件,并注入额外的 props(disabled/readonly 等)
const
AsyncComponent
=
defineAsyncComponent
({
loader
:
()
=>
loader
()
as
Promise
<
{
default
:
any
}
>
,
loadingComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'text-align: center; padding: 20px;'
},
'加载中...'
)
},
errorComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
'组件加载失败,请刷新页面重试'
)
},
delay
:
200
,
timeout
:
3000
,
onError
(
error
)
{
console
.
error
(
'异步组件加载错误:'
,
error
);
}
});
componentCache
.
set
(
url
,
AsyncComponent
);
return
AsyncComponent
;
}
// 创建空组件(当 formUrl 为空时使用)
function
createEmptyComponent
()
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: #999; padding: 20px; text-align: center;'
},
'该节点未配置表单'
)
};
}
// 创建错误组件
function
createErrorComponent
(
msg
:
string
)
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
msg
)
};
}
// 处理表单数据更新(来自可编辑组件)
function
handleFormDataUpdate
(
data
:
any
)
{
currentFormData
.
value
=
{
...
currentFormData
.
value
,
...
data
};
emit
(
'form-data-update'
,
currentFormData
.
value
);
}
// 处理提交事件
function
handleSubmit
(
data
:
any
)
{
emit
(
'submit'
,
{
nodeId
:
currentNode
.
value
?.
id
,
nodeName
:
currentNode
.
value
?.
name
,
formData
:
data
||
currentFormData
.
value
});
}
// 初始化只读节点的表单数据(从外部传入或模拟)
function
initReadonlyFormData
()
{
// 如果外部传入了表单数据,则按节点id映射
if
(
props
.
externalFormData
&&
Object
.
keys
(
props
.
externalFormData
).
length
>
0
)
{
// 假设 externalFormData 的结构为 { nodeId: formData, ... }
previousNodes
.
value
.
forEach
(
node
=>
{
if
(
props
.
externalFormData
[
node
.
id
])
{
formDataMap
.
value
[
node
.
id
]
=
props
.
externalFormData
[
node
.
id
];
}
else
{
// 如果没有传入,设置空对象
formDataMap
.
value
[
node
.
id
]
=
{};
}
});
}
else
{
// 初始化空数据
previousNodes
.
value
.
forEach
(
node
=>
{
formDataMap
.
value
[
node
.
id
]
=
{};
});
}
}
// 监听外部表单数据变化
watch
(()
=>
props
.
externalFormData
,
(
newData
)
=>
{
if
(
newData
&&
Object
.
keys
(
newData
).
length
>
0
)
{
previousNodes
.
value
.
forEach
(
node
=>
{
if
(
newData
[
node
.
id
])
{
formDataMap
.
value
[
node
.
id
]
=
newData
[
node
.
id
];
}
});
// 如果当前节点有数据,也更新
if
(
currentNode
.
value
&&
newData
[
currentNode
.
value
.
id
])
{
currentFormData
.
value
=
newData
[
currentNode
.
value
.
id
];
}
}
},
{
deep
:
true
,
immediate
:
true
});
// 组件挂载时初始化
onMounted
(()
=>
{
initReadonlyFormData
();
console
.
log
(
'MultiFormPreview 组件已挂载'
);
console
.
log
(
'当前节点索引:'
,
props
.
currentNodeIndex
);
console
.
log
(
'只读节点数量:'
,
previousNodes
.
value
.
length
);
console
.
log
(
'当前节点:'
,
currentNode
.
value
?.
name
);
});
</
script
>
<
style
scoped
lang=
"scss"
>
.multi-form-preview
{
width
:
100%
;
padding
:
16px
;
background-color
:
#f5f7fa
;
border-radius
:
8px
;
.form-readonly-item
,
.form-editable-item
{
margin-bottom
:
24px
;
background-color
:
#fff
;
border-radius
:
8px
;
overflow
:
hidden
;
box-shadow
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0
.05
);
transition
:
box-shadow
0
.2s
;
&
:hover
{
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.1
);
}
.form-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
12px
16px
;
background-color
:
#fafbfc
;
border-bottom
:
1px
solid
#e8eef2
;
.form-name
{
font-size
:
15px
;
font-weight
:
500
;
color
:
#1f2f3d
;
}
.readonly-tag
,
.ant-tag
{
font-size
:
12px
;
}
}
.form-content
{
padding
:
20px
;
&
.readonly
{
background-color
:
#fefefe
;
// 只读模式下添加半透明遮罩效果,但仍保留交互(如果子组件支持disabled)
opacity
:
0
.95
;
}
&
.editable
{
background-color
:
#fff
;
}
}
}
.form-editable-item
{
border
:
1px
solid
#d9ecff
;
box-shadow
:
0
2px
8px
rgba
(
24
,
144
,
255
,
0
.08
);
.form-header
{
background-color
:
#e6f7ff
;
border-bottom-color
:
#bae7ff
;
.form-name
{
color
:
#096dd9
;
}
}
}
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/WorkFlowFormApprovalDrawer.vue
0 → 100644
浏览文件 @
78599ce5
<
template
>
<a-drawer
:title=
"drawerTitle"
:visible=
"visible"
:width=
"drawerWidth"
:closable=
"true"
:mask-closable=
"maskClosable"
:header-style=
"
{ backgroundColor: '#f5f7fa', borderBottom: '1px solid #e8eef2' }"
@close="handleClose"
class="workflow-form-drawer"
style="padding: 0px;"
>
<div
class=
"drawer-layout"
:class=
"
{ 'three-columns': isApprovalNode }">
<!-- 左侧:历史节点区域 -->
<HistoryPanel
:readonly-nodes=
"readonlyNodes"
:data-id=
"dataId"
:show-history-form-data=
"showHistoryFormData"
/>
<!-- 中间:当前节点表单区域 -->
<div
class=
"form-wrapper"
>
<CurrentFormPanel
ref=
"currentFormPanelRef"
:editable-node=
"editableNode"
:data-id=
"dataId"
:external-form-data=
"externalFormData"
@
update:form-data=
"handleFormDataUpdate"
@
form-mounted=
"handleFormMounted"
/>
</div>
<!-- 右侧:审核意见区域(仅审核节点显示) -->
<ApprovalPanel
v-if=
"isApprovalNode"
ref=
"approvalPanelRef"
:current-node=
"editableNode"
:is-approval-node=
"isApprovalNode"
:reject-nodes=
"rejectNodes"
:can-select-reject-node=
"canSelectRejectNode"
:current-user=
"currentUser"
:initial-data=
"initialApprovalData"
@
update:approval-data=
"handleApprovalDataUpdate"
/>
</div>
<template
#
footer
>
<div
class=
"drawer-footer"
>
<a-space>
<a-button
@
click=
"handleClose"
>
取消
</a-button>
<a-button
type=
"primary"
:loading=
"submitLoading"
@
click=
"handleSubmit"
>
{{
isApprovalNode
?
'提交审核'
:
'提交'
}}
</a-button>
</a-space>
</div>
</
template
>
</a-drawer>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
onMounted
,
watch
,
nextTick
}
from
'vue'
import
{
message
}
from
'ant-design-vue'
import
HistoryPanel
from
'./HistoryPanel.vue'
import
CurrentFormPanel
from
'./CurrentFormPanel.vue'
import
ApprovalPanel
from
'./ApprovalPanel.vue'
interface
WorkflowNode
{
id
:
string
name
:
string
formName
?:
string
formUrl
?:
string
formListUrl
?:
string
procDefId
?:
string
dataId
?:
string
processTime
?:
string
processor
?:
string
comment
?:
string
nodeType
?:
string
// 'form' | 'approval'
[
key
:
string
]:
any
}
const
props
=
defineProps
({
visible
:
{
type
:
Boolean
,
default
:
false
},
title
:
{
type
:
String
,
default
:
'表单处理'
},
width
:
{
type
:
[
Number
,
String
],
default
:
"90%"
},
maskClosable
:
{
type
:
Boolean
,
default
:
false
},
// 当前节点索引(从0开始),这个索引对应的节点是可编辑的
currentNodeIndex
:
{
type
:
Number
,
required
:
true
,
default
:
2
},
workflowNodes
:
{
type
:
Array
as
()
=>
WorkflowNode
[],
required
:
true
,
default
:
()
=>
[]
},
externalFormData
:
{
type
:
Object
as
()
=>
Record
<
string
,
any
>
,
default
:
()
=>
({})
},
procDefId
:
{
type
:
String
,
default
:
''
},
dataId
:
{
type
:
String
,
default
:
''
},
// 是否显示历史节点的表单数据
showHistoryFormData
:
{
type
:
Boolean
,
default
:
false
},
// 当前节点是否为审核节点(如果是审核节点,显示三栏布局)
isApprovalNode
:
{
type
:
Boolean
,
default
:
false
},
// 可退回的节点列表
rejectNodes
:
{
type
:
Array
as
()
=>
WorkflowNode
[],
default
:
()
=>
[]
},
// 是否可以手动选择退回节点
canSelectRejectNode
:
{
type
:
Boolean
,
default
:
true
},
// 当前用户
currentUser
:
{
type
:
String
,
default
:
''
},
// 初始审核数据
initialApprovalData
:
{
type
:
Object
as
()
=>
{
result
?:
string
comment
?:
string
rejectNode
?:
string
},
default
:
()
=>
({})
}
})
const
emit
=
defineEmits
([
'update:visible'
,
'submit'
,
'close'
,
'form-data-update'
])
// 状态
const
submitLoading
=
ref
(
false
)
const
currentFormData
=
ref
<
any
>
({})
const
approvalData
=
ref
<
any
>
({})
const
currentFormPanelRef
=
ref
<
InstanceType
<
typeof
CurrentFormPanel
>
|
null
>
(
null
)
const
approvalPanelRef
=
ref
<
InstanceType
<
typeof
ApprovalPanel
>
|
null
>
(
null
)
// 计算属性
const
drawerTitle
=
computed
(()
=>
props
.
title
)
const
drawerWidth
=
computed
(()
=>
props
.
width
)
// 只读节点:索引小于 currentNodeIndex 的节点
const
readonlyNodes
=
computed
(()
=>
{
if
(
!
props
.
workflowNodes
||
props
.
workflowNodes
.
length
===
0
)
{
return
[]
}
const
idx
=
props
.
currentNodeIndex
if
(
idx
<=
0
)
return
[]
return
props
.
workflowNodes
.
slice
(
0
,
idx
)
})
// 可编辑节点:索引等于 currentNodeIndex 的节点
const
editableNode
=
computed
(()
=>
{
if
(
!
props
.
workflowNodes
||
props
.
workflowNodes
.
length
===
0
)
{
return
null
}
const
idx
=
props
.
currentNodeIndex
if
(
idx
<
0
||
idx
>=
props
.
workflowNodes
.
length
)
{
return
null
}
return
props
.
workflowNodes
[
idx
]
})
// 处理表单数据更新
function
handleFormDataUpdate
(
data
:
any
)
{
currentFormData
.
value
=
data
emit
(
'form-data-update'
,
data
)
}
// 处理审核数据更新
function
handleApprovalDataUpdate
(
data
:
any
)
{
approvalData
.
value
=
data
}
// 处理表单组件挂载
function
handleFormMounted
({
nodeId
,
instance
}:
{
nodeId
:
string
;
instance
:
any
})
{
console
.
log
(
'表单组件已挂载 - 节点:'
,
nodeId
)
}
// 提交处理
async
function
handleSubmit
()
{
if
(
!
editableNode
.
value
)
{
message
.
warning
(
'没有可编辑的表单'
)
return
}
if
(
!
currentFormPanelRef
.
value
)
{
message
.
warning
(
'表单组件未加载'
)
return
}
// 如果是审核节点,验证审核意见
if
(
props
.
isApprovalNode
&&
approvalPanelRef
.
value
)
{
const
isValidApproval
=
await
approvalPanelRef
.
value
.
validate
()
if
(
!
isValidApproval
)
{
message
.
error
(
'请填写完整的审核信息'
)
return
}
}
submitLoading
.
value
=
true
try
{
// 1. 先进行表单验证
const
isValid
=
await
currentFormPanelRef
.
value
.
validateForm
()
if
(
!
isValid
)
{
message
.
error
(
'请完善表单信息'
)
return
}
// 2. 获取表单数据
const
submitData
=
await
currentFormPanelRef
.
value
.
getFormData
()
// 3. 构建提交数据
const
finalSubmitData
:
any
=
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
submitData
,
procDefId
:
props
.
procDefId
,
formComponent
:
currentFormPanelRef
.
value
.
getFormInstance
()
}
// 如果是审核节点,添加审核数据
if
(
props
.
isApprovalNode
&&
approvalPanelRef
.
value
)
{
const
approval
=
await
approvalPanelRef
.
value
.
getApprovalData
()
finalSubmitData
.
approval
=
approval
}
console
.
log
(
'最终提交数据:'
,
finalSubmitData
)
// 4. 触发提交事件
emit
(
'submit'
,
finalSubmitData
)
message
.
success
(
props
.
isApprovalNode
?
'审核提交成功'
:
'提交成功'
)
handleClose
()
}
catch
(
error
:
any
)
{
console
.
error
(
'提交失败:'
,
error
)
message
.
error
(
error
?.
message
||
'提交失败,请重试'
)
}
finally
{
submitLoading
.
value
=
false
}
}
// 关闭抽屉
function
handleClose
()
{
emit
(
'update:visible'
,
false
)
emit
(
'close'
)
}
// 重置数据
function
resetFormData
()
{
if
(
currentFormPanelRef
.
value
)
{
currentFormPanelRef
.
value
.
resetFormData
()
}
if
(
approvalPanelRef
.
value
)
{
approvalPanelRef
.
value
.
resetForm
()
}
currentFormData
.
value
=
{}
approvalData
.
value
=
{}
}
// 监听抽屉打开
watch
(()
=>
props
.
visible
,
async
(
newVal
)
=>
{
if
(
newVal
)
{
resetFormData
()
// 等待子组件渲染完成后重新加载数据
await
nextTick
()
if
(
currentFormPanelRef
.
value
)
{
await
currentFormPanelRef
.
value
.
reloadFormData
()
}
}
})
// 监听 dataId 变化
watch
(()
=>
props
.
dataId
,
async
()
=>
{
if
(
props
.
visible
&&
currentFormPanelRef
.
value
)
{
await
nextTick
()
await
currentFormPanelRef
.
value
.
reloadFormData
()
}
})
// 监听外部数据变化
watch
(()
=>
props
.
externalFormData
,
(
newData
)
=>
{
if
(
newData
&&
editableNode
.
value
&&
newData
[
editableNode
.
value
.
id
])
{
currentFormData
.
value
=
newData
[
editableNode
.
value
.
id
]
}
},
{
deep
:
true
})
onMounted
(()
=>
{
resetFormData
()
})
defineExpose
({
resetFormData
,
getFormData
:
()
=>
currentFormData
.
value
,
getApprovalData
:
()
=>
approvalData
.
value
,
getCurrentFormData
:
async
()
=>
{
if
(
currentFormPanelRef
.
value
)
{
return
await
currentFormPanelRef
.
value
.
getFormData
()
}
return
currentFormData
.
value
},
validate
:
async
()
=>
{
if
(
currentFormPanelRef
.
value
)
{
const
formValid
=
await
currentFormPanelRef
.
value
.
validateForm
()
if
(
props
.
isApprovalNode
&&
approvalPanelRef
.
value
)
{
const
approvalValid
=
await
approvalPanelRef
.
value
.
validate
()
return
formValid
&&
approvalValid
}
return
formValid
}
return
true
},
submit
:
handleSubmit
})
</
script
>
<
style
scoped
lang=
"scss"
>
.workflow-form-drawer
{
:deep
(
.ant-drawer-header
)
{
background-color
:
#f5f7fa
;
border-bottom
:
1px
solid
#e8eef2
;
padding
:
16px
24px
;
.ant-drawer-title
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#1f2f3d
;
}
.ant-drawer-close
{
color
:
#8c8c8c
;
&
:hover
{
color
:
#1f2f3d
;
}
}
}
:deep
(
.ant-drawer-body
)
{
padding
:
0
;
height
:
100%
;
overflow
:
hidden
;
}
:deep
(
.ant-drawer-footer
)
{
padding
:
12px
24px
;
border-top
:
1px
solid
#e8eef2
;
background-color
:
#fff
;
}
}
.drawer-layout
{
display
:
flex
;
height
:
100%
;
overflow
:
hidden
;
// 三栏布局
&
.three-columns
{
.history-panel
{
width
:
30%
;
}
.form-wrapper
{
width
:
40%
;
}
.approval-panel
{
width
:
30%
;
}
}
// 两栏布局(普通表单节点)
&
:not
(
.three-columns
)
{
.history-panel
{
width
:
40%
;
}
.form-wrapper
{
flex
:
1
;
}
}
}
.form-wrapper
{
overflow
:
hidden
;
display
:
flex
;
flex-direction
:
column
;
}
.drawer-footer
{
display
:
flex
;
justify-content
:
flex-end
;
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/common/WorkFlowFormDrawer.vue
浏览文件 @
78599ce5
...
...
@@ -5,91 +5,82 @@
:width=
"drawerWidth"
:closable=
"true"
:mask-closable=
"maskClosable"
:header-style=
"
{ backgroundColor: '#018ffb', borderBottom: '1px solid #e8eef2' }"
@close="handleClose"
class="workflow-form-drawer"
style="padding: 0px;"
>
<div
class=
"drawer-layout"
:class=
"
{ 'three-columns': showApprovalPanel }">
<HistoryPanel
:readonly-nodes=
"readonlyNodes"
:data-id=
"dataId"
:show-history-form-data=
"showHistoryFormData"
/>
<div
class=
"form-wrapper"
>
<CurrentFormPanel
ref=
"currentFormPanelRef"
:editable-node=
"editableNode"
:data-id=
"dataId"
:external-form-data=
"externalFormData"
@
update:form-data=
"handleFormDataUpdate"
@
form-mounted=
"handleFormMounted"
/>
</div>
<!-- 选项卡模式 -->
<a-tabs
v-model:activeKey=
"activeTabKey"
type=
"card"
class=
"form-tabs"
>
<!-- 只读选项卡:索引小于 currentNodeIndex 的节点 -->
<a-tab-pane
v-for=
"node in readonlyNodes"
:key=
"node.id"
:tab=
"node.name"
>
<template
#
tab
>
<span>
{{
node
.
name
}}
<a-tag
color=
"blue"
size=
"small"
class=
"tab-tag"
>
只读
</a-tag>
</span>
</
template
>
<div
class=
"tab-content readonly-content"
>
<component
:is=
"getComponent(node.formUrl || node.formListUrl)"
:disabled=
"true"
:readonly=
"true"
:form-data=
"getFormData(node.id)"
:current-flow-node=
"node"
/>
</div>
</a-tab-pane>
<!-- 可编辑选项卡:索引等于 currentNodeIndex 的节点 -->
<a-tab-pane
v-if=
"editableNode"
:key=
"editableNode.id"
:tab=
"editableNode.name"
>
<
template
#
tab
>
<span>
{{
editableNode
.
name
}}
<a-tag
color=
"green"
size=
"small"
class=
"tab-tag"
>
可编辑
</a-tag>
</span>
</
template
>
<div
class=
"tab-content editable-content"
>
<component
:is=
"getComponent(editableNode.formUrl || editableNode.formListUrl)"
ref=
"editableFormRef"
:disabled=
"false"
:readonly=
"false"
:form-data=
"currentFormData"
:current-flow-node=
"editableNode"
@
update:form-data=
"handleFormDataUpdate"
/>
</div>
</a-tab-pane>
</a-tabs>
<!-- 空状态 -->
<div
v-if=
"!editableNode && readonlyNodes.length === 0"
class=
"empty-state"
>
<a-empty
description=
"未找到有效表单节点"
/>
</div>
<div
v-if=
"showApprovalPanel"
class=
"approval-wrapper"
>
<slot
name=
"approval-panel"
:approval-data=
"approvalData"
:node=
"editableNode"
>
<ApprovalPanel
ref=
"approvalPanelRef"
:title=
"approvalPanelTitle"
:current-node=
"editableNode"
:current-user=
"currentUser"
:reject-nodes=
"rejectNodes"
:can-select-reject-node=
"canSelectRejectNode"
:initial-data=
"initialApprovalData"
:show-summary=
"showApprovalSummary"
:result-options=
"approvalResultOptions"
:default-result=
"defaultApprovalResult"
:comment-placeholder=
"commentPlaceholder"
:require-comment-on-reject=
"requireCommentOnReject"
:show-reject-node-select=
"showRejectNodeSelect"
:show-approval-panel=
"true"
@
update:approval-data=
"handleApprovalDataUpdate"
/>
</slot>
</div>
</div>
<template
#
footer
>
<div
class=
"drawer-footer"
>
<a-button
@
click=
"handleClose"
>
取消
</a-button>
<a-button
type=
"primary"
:loading=
"submitLoading"
@
click=
"handleSubmit"
>
提交
</a-button>
</div>
</
template
>
</a-drawer>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
onMounted
,
defineAsyncComponent
,
h
,
watch
}
from
'vue'
import
{
ref
,
computed
,
onMounted
,
watch
,
nextTick
}
from
'vue'
import
{
message
}
from
'ant-design-vue'
import
HistoryPanel
from
'./HistoryPanel.vue'
import
CurrentFormPanel
from
'./CurrentFormPanel.vue'
import
ApprovalPanel
from
'./ApprovalPanel.vue'
interface
WorkflowNode
{
id
:
string
name
:
string
formName
?:
string
formUrl
?:
string
formListUrl
?:
string
procDefId
?:
string
dataId
?:
string
processTime
?:
string
processor
?:
string
comment
?:
string
nodeType
?:
string
[
key
:
string
]:
any
}
const
props
=
defineProps
({
// 基础配置
visible
:
{
type
:
Boolean
,
default
:
false
...
...
@@ -100,13 +91,14 @@ const props = defineProps({
},
width
:
{
type
:
[
Number
,
String
],
default
:
720
default
:
"90%"
},
maskClosable
:
{
type
:
Boolean
,
default
:
false
},
// 当前节点索引(从0开始),这个索引对应的节点是可编辑的
// 工作流配置
currentNodeIndex
:
{
type
:
Number
,
required
:
true
,
...
...
@@ -124,22 +116,104 @@ const props = defineProps({
procDefId
:
{
type
:
String
,
default
:
''
},
dataId
:
{
type
:
String
,
default
:
''
},
showHistoryFormData
:
{
type
:
Boolean
,
default
:
false
},
// 审核面板配置
showApprovalPanel
:
{
type
:
Boolean
,
default
:
false
},
approvalPanelTitle
:
{
type
:
String
,
default
:
'审核意见'
},
submitButtonText
:
{
type
:
String
,
default
:
'提交'
},
// 审核结果配置
approvalResultOptions
:
{
type
:
Array
,
default
:
()
=>
[
{
value
:
'approved'
,
label
:
'通过'
,
icon
:
'CheckCircleOutlined'
,
className
:
'approved'
},
{
value
:
'rejected'
,
label
:
'退回'
,
icon
:
'CloseCircleOutlined'
,
className
:
'rejected'
}
]
},
defaultApprovalResult
:
{
type
:
String
,
default
:
'approved'
},
// 审核意见配置
commentPlaceholder
:
{
type
:
String
,
default
:
'请输入审核意见...'
},
requireCommentOnReject
:
{
type
:
Boolean
,
default
:
true
},
// 退回节点配置
showRejectNodeSelect
:
{
type
:
Boolean
,
default
:
true
},
rejectNodes
:
{
type
:
Array
as
()
=>
WorkflowNode
[],
default
:
()
=>
[]
},
canSelectRejectNode
:
{
type
:
Boolean
,
default
:
true
},
// 其他配置
currentUser
:
{
type
:
String
,
default
:
''
},
initialApprovalData
:
{
type
:
Object
as
()
=>
{
result
?:
string
comment
?:
string
rejectNode
?:
string
},
default
:
()
=>
({})
},
showApprovalSummary
:
{
type
:
Boolean
,
default
:
true
}
})
const
emit
=
defineEmits
([
'update:visible'
,
'submit'
,
'close'
,
'form-data-update'
])
// 组件缓存
const
componentCache
=
new
Map
()
const
modules
=
import
.
meta
.
glob
(
'@/views/**/*.vue'
)
// 状态
const
loading
=
ref
(
false
)
const
submitLoading
=
ref
(
false
)
const
editableFormRef
=
ref
()
const
currentFormData
=
ref
<
any
>
({})
const
formDataMap
=
ref
<
Record
<
string
,
any
>>
({})
const
activeTabKey
=
ref
<
string
>
(
''
)
const
approvalData
=
ref
<
any
>
({})
const
currentFormPanelRef
=
ref
<
InstanceType
<
typeof
CurrentFormPanel
>
|
null
>
(
null
)
const
approvalPanelRef
=
ref
<
InstanceType
<
typeof
ApprovalPanel
>
|
null
>
(
null
)
// 计算属性
const
drawerTitle
=
computed
(()
=>
props
.
title
)
...
...
@@ -151,14 +225,8 @@ const readonlyNodes = computed(() => {
return
[]
}
const
idx
=
props
.
currentNodeIndex
console
.
log
(
'只读节点 - 当前索引:'
,
idx
)
console
.
log
(
'只读节点 - 所有节点:'
,
props
.
workflowNodes
.
map
((
n
,
i
)
=>
`
${
i
}
:
${
n
.
name
}
`
))
if
(
idx
<=
0
)
return
[]
// 返回索引小于 idx 的节点
const
nodes
=
props
.
workflowNodes
.
slice
(
0
,
idx
)
console
.
log
(
'只读节点:'
,
nodes
.
map
(
n
=>
n
.
name
))
return
nodes
return
props
.
workflowNodes
.
slice
(
0
,
idx
)
})
// 可编辑节点:索引等于 currentNodeIndex 的节点
...
...
@@ -168,86 +236,25 @@ const editableNode = computed(() => {
}
const
idx
=
props
.
currentNodeIndex
if
(
idx
<
0
||
idx
>=
props
.
workflowNodes
.
length
)
{
console
.
warn
(
'可编辑节点索引无效:'
,
idx
)
return
null
}
const
node
=
props
.
workflowNodes
[
idx
]
console
.
log
(
'可编辑节点:'
,
node
?.
name
,
'索引:'
,
idx
)
return
node
return
props
.
workflowNodes
[
idx
]
})
// 获取表单数据
function
getFormData
(
nodeId
:
string
):
any
{
const
data
=
formDataMap
.
value
[
nodeId
]
||
{}
console
.
log
(
'获取表单数据 - 节点:'
,
nodeId
,
'数据:'
,
data
)
return
data
}
// 获取或加载组件
function
getComponent
(
url
:
string
)
{
if
(
!
url
)
{
console
.
warn
(
'URL为空,返回空组件'
)
return
createEmptyComponent
()
}
if
(
componentCache
.
has
(
url
))
{
return
componentCache
.
get
(
url
)
}
let
componentPath
=
''
if
(
url
.
includes
(
'/views'
))
{
componentPath
=
`/src
${
url
}
`
}
else
{
componentPath
=
`/src/views
${
url
}
`
}
if
(
!
componentPath
.
match
(
/
\.(
vue|js|ts|jsx|tsx
)
$/
))
{
componentPath
+=
'.vue'
}
console
.
log
(
'加载组件路径:'
,
componentPath
)
const
loader
=
modules
[
componentPath
]
if
(
!
loader
)
{
console
.
error
(
'未找到组件:'
,
componentPath
)
const
ErrorComponent
=
createErrorComponent
(
`组件未找到:
${
componentPath
}
`
)
componentCache
.
set
(
url
,
ErrorComponent
)
return
ErrorComponent
}
const
AsyncComponent
=
defineAsyncComponent
({
loader
:
()
=>
loader
()
as
Promise
<
{
default
:
any
}
>
,
loadingComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'text-align: center; padding: 20px;'
},
'加载中...'
)
},
errorComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
'组件加载失败'
)
},
delay
:
200
,
timeout
:
3000
})
componentCache
.
set
(
url
,
AsyncComponent
)
return
AsyncComponent
// 处理表单数据更新
function
handleFormDataUpdate
(
data
:
any
)
{
currentFormData
.
value
=
data
emit
(
'form-data-update'
,
data
)
}
function
createEmptyComponent
()
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: #999; padding: 20px; text-align: center;'
},
'该节点未配置表单'
)
}
// 处理审核数据更新
function
handleApprovalDataUpdate
(
data
:
any
)
{
approvalData
.
value
=
data
}
function
createErrorComponent
(
msg
:
string
)
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
msg
)
}
}
// 处理表单数据更新
function
handleFormDataUpdate
(
data
:
any
)
{
currentFormData
.
value
=
{
...
currentFormData
.
value
,
...
data
}
emit
(
'form-data-update'
,
currentFormData
.
value
)
// 处理表单组件挂载
function
handleFormMounted
({
nodeId
,
instance
}:
{
nodeId
:
string
;
instance
:
any
})
{
console
.
log
(
'表单组件已挂载 - 节点:'
,
nodeId
)
}
// 提交处理
...
...
@@ -257,35 +264,61 @@ async function handleSubmit() {
return
}
if
(
editableFormRef
.
value
&&
editableFormRef
.
value
.
validate
)
{
try
{
await
editableFormRef
.
value
.
validate
()
}
catch
(
error
)
{
message
.
error
(
'请完善表单信息'
)
if
(
!
currentFormPanelRef
.
value
)
{
message
.
warning
(
'表单组件未加载'
)
return
}
// 如果显示审核面板,验证审核数据
if
(
props
.
showApprovalPanel
&&
approvalPanelRef
.
value
)
{
const
result
=
await
approvalPanelRef
.
value
.
validate
()
if
(
!
result
.
valid
)
{
message
.
error
(
result
.
errors
[
0
]
||
'请填写完整的审核信息'
)
return
}
}
let
submitData
=
currentFormData
.
value
if
(
editableFormRef
.
value
&&
editableFormRef
.
value
.
getFormData
)
{
submitData
=
editableFormRef
.
value
.
getFormData
()
}
else
if
(
editableFormRef
.
value
&&
editableFormRef
.
value
.
formData
)
{
submitData
=
editableFormRef
.
value
.
formData
}
submitLoading
.
value
=
true
try
{
// 1. 表单验证
const
isValid
=
await
currentFormPanelRef
.
value
.
validateForm
()
if
(
!
isValid
)
{
message
.
error
(
'请完善表单信息'
)
return
}
// 2. 获取表单数据
const
submitData
=
await
currentFormPanelRef
.
value
.
getFormData
()
// 3. 构建提交数据
const
finalSubmitData
:
any
=
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
submitData
,
procDefId
:
props
.
procDefId
,
formComponent
:
currentFormPanelRef
.
value
.
getFormInstance
()
}
// 如果显示审核面板,添加审核数据
if
(
props
.
showApprovalPanel
&&
approvalPanelRef
.
value
)
{
const
approval
=
await
approvalPanelRef
.
value
.
getApprovalData
()
finalSubmitData
.
approval
=
approval
}
console
.
log
(
'最终提交数据:'
,
finalSubmitData
)
// 4. 触发提交事件
emit
(
'submit'
,
finalSubmitData
)
console
.
log
(
'提交数据:'
,
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
submitData
,
procDefId
:
props
.
procDefId
})
emit
(
'submit'
,
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
submitData
,
procDefId
:
props
.
procDefId
})
message
.
success
(
props
.
showApprovalPanel
?
'审核提交成功'
:
'提交成功'
)
handleClose
()
}
catch
(
error
:
any
)
{
console
.
error
(
'提交失败:'
,
error
)
message
.
error
(
error
?.
message
||
'提交失败,请重试'
)
}
finally
{
submitLoading
.
value
=
false
}
}
// 关闭抽屉
...
...
@@ -296,208 +329,154 @@ function handleClose() {
// 重置数据
function
resetFormData
()
{
currentFormData
.
value
=
{}
const
newFormDataMap
:
Record
<
string
,
any
>
=
{}
readonlyNodes
.
value
.
forEach
(
node
=>
{
newFormDataMap
[
node
.
id
]
=
props
.
externalFormData
[
node
.
id
]
||
{}
})
formDataMap
.
value
=
newFormDataMap
if
(
editableNode
.
value
&&
props
.
externalFormData
[
editableNode
.
value
.
id
])
{
currentFormData
.
value
=
{
...
props
.
externalFormData
[
editableNode
.
value
.
id
]
}
if
(
currentFormPanelRef
.
value
)
{
currentFormPanelRef
.
value
.
resetFormData
()
}
// 设置默认激活的选项卡为可编辑的选项卡
if
(
editableNode
.
value
)
{
activeTabKey
.
value
=
editableNode
.
value
.
id
console
.
log
(
'设置激活选项卡为可编辑节点:'
,
editableNode
.
value
.
name
)
}
else
if
(
readonlyNodes
.
value
.
length
>
0
)
{
activeTabKey
.
value
=
readonlyNodes
.
value
[
0
].
id
console
.
log
(
'设置激活选项卡为第一个只读节点:'
,
readonlyNodes
.
value
[
0
].
name
)
if
(
approvalPanelRef
.
value
)
{
approvalPanelRef
.
value
.
resetForm
()
}
}
// 预加载组件
function
preloadComponents
()
{
props
.
workflowNodes
.
forEach
(
node
=>
{
const
url
=
node
.
formUrl
||
node
.
formListUrl
if
(
url
)
{
getComponent
(
url
)
}
})
currentFormData
.
value
=
{}
approvalData
.
value
=
{}
}
// 监听抽屉打开
watch
(()
=>
props
.
visible
,
(
newVal
)
=>
{
watch
(()
=>
props
.
visible
,
async
(
newVal
)
=>
{
if
(
newVal
)
{
console
.
log
(
'抽屉打开,currentNodeIndex:'
,
props
.
currentNodeIndex
)
console
.
log
(
'所有节点:'
,
props
.
workflowNodes
)
resetFormData
()
preloadComponents
()
await
nextTick
()
if
(
currentFormPanelRef
.
value
)
{
await
currentFormPanelRef
.
value
.
reloadFormData
()
}
}
},
{
immediate
:
true
})
})
// 监听 dataId 变化
watch
(()
=>
props
.
dataId
,
async
()
=>
{
if
(
props
.
visible
&&
currentFormPanelRef
.
value
)
{
await
nextTick
()
await
currentFormPanelRef
.
value
.
reloadFormData
()
}
})
// 监听外部数据变化
watch
(()
=>
props
.
externalFormData
,
(
newData
)
=>
{
if
(
newData
&&
Object
.
keys
(
newData
).
length
>
0
)
{
console
.
log
(
'外部数据变化:'
,
newData
)
const
newFormDataMap
=
{
...
formDataMap
.
value
}
readonlyNodes
.
value
.
forEach
(
node
=>
{
if
(
newData
[
node
.
id
])
{
newFormDataMap
[
node
.
id
]
=
newData
[
node
.
id
]
}
})
formDataMap
.
value
=
newFormDataMap
if
(
editableNode
.
value
&&
newData
[
editableNode
.
value
.
id
])
{
currentFormData
.
value
=
newData
[
editableNode
.
value
.
id
]
}
if
(
newData
&&
editableNode
.
value
&&
newData
[
editableNode
.
value
.
id
])
{
currentFormData
.
value
=
newData
[
editableNode
.
value
.
id
]
}
},
{
deep
:
true
})
onMounted
(()
=>
{
console
.
log
(
'组件挂载,workflowNodes:'
,
props
.
workflowNodes
)
console
.
log
(
'currentNodeIndex:'
,
props
.
currentNodeIndex
)
resetFormData
()
preloadComponents
()
})
defineExpose
({
resetFormData
,
getFormData
:
()
=>
currentFormData
.
value
,
getApprovalData
:
()
=>
approvalData
.
value
,
getCurrentFormData
:
async
()
=>
{
if
(
currentFormPanelRef
.
value
)
{
return
await
currentFormPanelRef
.
value
.
getFormData
()
}
return
currentFormData
.
value
},
validate
:
async
()
=>
{
if
(
editableFormRef
.
value
&&
editableFormRef
.
value
.
validate
)
{
return
await
editableFormRef
.
value
.
validate
()
if
(
currentFormPanelRef
.
value
)
{
const
formValid
=
await
currentFormPanelRef
.
value
.
validateForm
()
if
(
props
.
showApprovalPanel
&&
approvalPanelRef
.
value
)
{
const
approvalResult
=
await
approvalPanelRef
.
value
.
validate
()
return
formValid
&&
approvalResult
.
valid
}
return
formValid
}
return
true
}
},
submit
:
handleSubmit
})
</
script
>
<
style
scoped
lang=
"scss"
>
.workflow-form-drawer
{
width
:
100%
;
height
:
100%
;
overflow-y
:
auto
;
.form-card
{
border-radius
:
8px
;
box-shadow
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0
.05
);
:deep
(
.ant-card-body
)
{
padding
:
0
;
}
}
.form-tabs
{
:deep
(
.ant-tabs-nav
)
{
margin-bottom
:
0
;
background-color
:
#fafbfc
;
padding
:
0
16px
;
border-bottom
:
1px
solid
#e8eef2
;
}
:deep
(
.ant-tabs-tab
)
{
padding
:
12px
20px
;
:deep
(
.ant-drawer-header
)
{
background-color
:
#f5f7fa
;
border-bottom
:
1px
solid
#e8eef2
;
padding
:
16px
24px
;
.ant-drawer-title
{
font-size
:
16px
;
font-weight
:
500
;
transition
:
all
0
.3s
;
&
:hover
{
color
:
#1890ff
;
}
}
:deep
(
.ant-tabs-tab-active
)
{
.ant-tabs-tab-btn
{
color
:
#1890ff
;
}
}
:deep
(
.ant-tabs-ink-bar
)
{
background
:
#1890ff
;
color
:
#1f2f3d
;
}
}
.tab-content
{
padding
:
24px
;
min-height
:
400px
;
background-color
:
#fff
;
&
.readonly-content
{
background-color
:
#f5f5f5
;
// 让只读内容区域内的所有表单输入框都显示为只读样式
:deep
(
input
),
:deep
(
textarea
),
:deep
(
.ant-input
),
:deep
(
.ant-select-selector
),
:deep
(
.ant-picker
)
{
background-color
:
#f5f5f5
!
important
;
cursor
:
not
-
allowed
!
important
;
color
:
#666
!
important
;
}
.ant-drawer-close
{
color
:
#8c8c8c
;
:deep
(
.ant-input-affix-wrapper
)
{
background-color
:
#f5f5f5
!
important
;
&
:hover
{
color
:
#1f2f3d
;
}
}
&
.editable-content
{
background-color
:
#fff
;
}
}
.tab-tag
{
margin-left
:
8px
;
font-size
:
12px
;
transform
:
scale
(
0
.9
);
display
:
inline-block
;
:deep
(
.ant-drawer-body
)
{
padding
:
0
;
height
:
100%
;
overflow
:
hidden
;
}
.empty-state
{
padding
:
60px
0
;
text-align
:
center
;
:deep
(
.ant-drawer-footer
)
{
padding
:
12px
24px
;
border-top
:
1px
solid
#e8eef2
;
background-color
:
red
;
}
}
&
:
:-
webkit-scrollbar
{
width
:
6px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
border-radius
:
3px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
3px
;
&
:hover
{
background
:
#a8a8a8
;
.drawer-layout
{
display
:
flex
;
height
:
100%
;
overflow
:
hidden
;
// 三栏布局
&
.three-columns
{
.history-panel
{
width
:
30%
;
}
.form-wrapper
{
width
:
40%
;
}
.approval-wrapper
{
width
:
30%
;
}
}
}
.drawer-footer
{
text-align
:
right
;
:deep
(
.ant-btn
)
{
margin-left
:
8px
;
// 两栏布局
&
:not
(
.three-columns
)
{
.history-panel
{
width
:
40%
;
}
&
:first-child
{
margin-left
:
0
;
.form-wrapper
{
flex
:
1
;
}
}
}
:deep
(
.ant-drawer-body
)
{
padding
:
16px
;
background-color
:
#f5f7fa
;
.form-wrapper
{
overflow
:
hidden
;
display
:
flex
;
flex-direction
:
column
;
}
.approval-wrapper
{
overflow
:
hidden
;
display
:
flex
;
flex-direction
:
column
;
}
:deep
(
.ant-drawer-footer
)
{
padding
:
12px
16px
;
border-top
:
1px
solid
#e8eef2
;
.drawer-footer
{
display
:
flex
;
justify-content
:
flex-end
;
height
:
2px
;
background
:
#018ffb
;
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/flowable/task/components/ShowFormModal.vue
浏览文件 @
78599ce5
<
template
>
<BasicModal
@
register=
"registerModal"
v-bind=
"$attrs"
:title=
"modalTitle"
:canFullscreen=
"true"
:maxHeight=
"600"
width=
"50%"
:showOkBtn=
"false"
cancelText=
"关闭"
@
cancel=
"handleCancel"
<a-drawer
:title=
"drawerTitle"
:visible=
"visible"
:width=
"drawerWidth"
:closable=
"true"
:mask-closable=
"maskClosable"
@
close=
"handleClose"
class=
"workflow-form-drawer"
>
<Card
:bordered=
"false"
class=
"form-modal-card"
>
<!-- 加载状态 -->
<div
v-show=
"loading"
class=
"loading-container"
>
<div
class=
"loading-content"
>
<a-spin
size=
"large"
tip=
"表单加载中..."
/>
<!-- 选项卡模式 -->
<a-tabs
v-model:activeKey=
"activeTabKey"
type=
"card"
class=
"form-tabs"
>
<!-- 只读选项卡:索引小于 currentNodeIndex 的节点 -->
<a-tab-pane
v-for=
"node in readonlyNodes"
:key=
"node.id"
:tab=
"node.name"
>
<template
#
tab
>
<span>
{{
node
.
name
}}
<a-tag
color=
"blue"
size=
"small"
class=
"tab-tag"
>
只读
</a-tag>
</span>
</
template
>
<div
class=
"tab-content readonly-content"
>
<component
:is=
"getComponent(node.formUrl || node.formListUrl)"
:disabled=
"true"
:readonly=
"true"
:form-data=
"getFormData(node.id)"
:current-flow-node=
"node"
/>
</div>
</div>
<!-- 错误状态 -->
<div
v-show=
"hasError"
class=
"error-container"
>
<a-result
status=
"error"
:title=
"errorTitle"
:sub-title=
"errorMessage"
>
<template
#
extra
>
<a-button
type=
"primary"
@
click=
"retryLoad"
>
重新加载
</a-button>
</
template
>
</a-result>
</div>
<!-- 内容区域 -->
<div
class=
"form-content-container"
>
<!-- 外部表单(iframe类型) -->
<div
v-show=
"formType == 2"
>
<!-- <div class="iframe-header">
<a-alert
type="info"
show-icon
message="外部表单"
description="此表单通过外部链接加载,请确保网络连接正常"
class="iframe-alert"
/>
</div> -->
<div
class=
"iframe-container"
>
<iframe
v-if=
"formUrl"
:src=
"formUrl"
class=
"responsive-iframe"
:title=
"'表单-' + formTitle"
frameborder=
"0"
@
load=
"handleIframeLoad"
@
error=
"handleIframeError"
></iframe>
</div>
</div>
<div
v-show=
"formType == 1"
class=
"inner-form-wrapper"
>
<div
class=
"form-body"
>
<FlowInnerForm
ref=
"refInnerForm"
/>
</div>
</div>
<div
v-show=
"formType == 0 || !formType"
class=
"unsupported-container"
>
<a-result
status=
"warning"
title=
"暂不支持的表单类型"
sub-title=
"当前表单类型配置有误或暂不支持"
>
<
template
#
extra
>
<a-space>
<a-button
type=
"primary"
@
click=
"closeModal"
>
关闭
</a-button>
<a-button
@
click=
"handleReport"
>
报告问题
</a-button>
</a-space>
</
template
>
</a-result>
</a-tab-pane>
<!-- 可编辑选项卡:索引等于 currentNodeIndex 的节点 -->
<a-tab-pane
v-if=
"editableNode"
:key=
"editableNode.id"
:tab=
"editableNode.name"
>
<
template
#
tab
>
<span>
{{
editableNode
.
name
}}
<a-tag
color=
"green"
size=
"small"
class=
"tab-tag"
>
可编辑
</a-tag>
</span>
</
template
>
<div
class=
"tab-content editable-content"
>
<component
:is=
"getComponent(editableNode.formUrl || editableNode.formListUrl)"
:ref=
"setEditableFormRef"
:disabled=
"false"
:readonly=
"false"
:form-data=
"currentFormData"
:current-flow-node=
"editableNode"
@
update:form-data=
"handleFormDataUpdate"
/>
</div>
</a-tab-pane>
</a-tabs>
<!-- 空状态 -->
<div
v-if=
"!editableNode && readonlyNodes.length === 0"
class=
"empty-state"
>
<a-empty
description=
"未找到有效表单节点"
/>
</div>
<
template
#
footer
>
<div
class=
"drawer-footer"
>
<a-button
@
click=
"handleClose"
>
取消
</a-button>
<a-button
type=
"primary"
:loading=
"submitLoading"
@
click=
"handleSubmit"
>
提交
</a-button>
</div>
</
Card
>
</
BasicModal
>
</
template
>
</
a-drawer
>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
onUnmounted
,
nextTick
,
watch
}
from
'vue'
;
import
{
Card
,
message
}
from
'ant-design-vue'
;
import
{
BasicModal
,
useModalInner
}
from
'/@/components/Modal'
;
import
FlowInnerForm
from
'./FlowInnerForm.vue'
;
import
{
findFlowFormVal
}
from
"/@/components/Process/api/todo"
// 状态定义
const
loading
=
ref
(
false
);
const
submitLoading
=
ref
(
false
);
const
hasError
=
ref
(
false
);
const
errorTitle
=
ref
(
''
);
const
errorMessage
=
ref
(
''
);
const
showInnerForm
=
ref
(
false
);
// 控制内部表单显示
// 表单相关
const
formType
=
ref
<
number
>
(
0
);
const
formUrl
=
ref
<
string
>
(
''
);
const
formTitle
=
ref
<
string
>
(
'节点表单'
);
const
formContent
=
ref
<
string
>
(
''
);
const
refInnerForm
=
ref
<
InstanceType
<
typeof
FlowInnerForm
>
|
null
>
(
null
);
import
{
ref
,
computed
,
onMounted
,
defineAsyncComponent
,
h
,
watch
,
ComponentPublicInstance
}
from
'vue'
import
{
message
}
from
'ant-design-vue'
interface
WorkflowNode
{
id
:
string
name
:
string
formUrl
?:
string
formListUrl
?:
string
procDefId
?:
string
[
key
:
string
]:
any
}
// 计算属性
const
modalTitle
=
computed
(()
=>
{
return
formTitle
.
value
||
'节点表单'
;
});
// 监听 formType 变化
watch
(()
=>
formType
.
value
,
(
newVal
)
=>
{
if
(
newVal
==
1
)
{
// 当表单类型变为内部表单时,显示组件
showInnerForm
.
value
=
true
;
// 等待组件渲染完成后再初始化数据
nextTick
(()
=>
{
//if (refInnerForm.value && formContent.value) {
initializeInnerForm
();
//}
});
}
else
{
// 其他表单类型时隐藏
showInnerForm
.
value
=
false
;
// 表单组件实例类型
interface
FormComponentInstance
extends
ComponentPublicInstance
{
validate
?:
()
=>
Promise
<
any
>
getFormData
?:
()
=>
any
submitForm
?:
()
=>
Promise
<
any
>
// 添加 submitForm 方法类型
formData
?:
any
[
key
:
string
]:
any
}
const
props
=
defineProps
({
visible
:
{
type
:
Boolean
,
default
:
false
},
title
:
{
type
:
String
,
default
:
'表单处理'
},
width
:
{
type
:
[
Number
,
String
],
default
:
720
},
maskClosable
:
{
type
:
Boolean
,
default
:
false
},
// 当前节点索引(从0开始),这个索引对应的节点是可编辑的
currentNodeIndex
:
{
type
:
Number
,
required
:
true
,
default
:
2
},
workflowNodes
:
{
type
:
Array
as
()
=>
WorkflowNode
[],
required
:
true
,
default
:
()
=>
[]
},
externalFormData
:
{
type
:
Object
as
()
=>
Record
<
string
,
any
>
,
default
:
()
=>
({})
},
procDefId
:
{
type
:
String
,
default
:
''
}
})
;
})
// 初始化内部表单数据
const
initializeInnerForm
=
async
()
=>
{
if
(
!
refInnerForm
.
value
)
{
console
.
warn
(
'Inner form ref is not available'
);
return
;
const
emit
=
defineEmits
([
'update:visible'
,
'submit'
,
'close'
,
'form-data-update'
])
// 组件缓存
const
componentCache
=
new
Map
()
const
modules
=
import
.
meta
.
glob
(
'@/views/**/*.vue'
)
// 状态
const
loading
=
ref
(
false
)
const
submitLoading
=
ref
(
false
)
// 使用数组来存储表单组件实例
const
editableFormRefs
=
ref
<
FormComponentInstance
[]
>
([])
const
currentFormData
=
ref
<
any
>
({})
const
formDataMap
=
ref
<
Record
<
string
,
any
>>
({})
const
activeTabKey
=
ref
<
string
>
(
''
)
// 计算属性
const
drawerTitle
=
computed
(()
=>
props
.
title
)
const
drawerWidth
=
computed
(()
=>
props
.
width
)
// 只读节点:索引小于 currentNodeIndex 的节点
const
readonlyNodes
=
computed
(()
=>
{
if
(
!
props
.
workflowNodes
||
props
.
workflowNodes
.
length
===
0
)
{
return
[]
}
const
idx
=
props
.
currentNodeIndex
console
.
log
(
'只读节点 - 当前索引:'
,
idx
)
console
.
log
(
'只读节点 - 所有节点:'
,
props
.
workflowNodes
.
map
((
n
,
i
)
=>
`
${
i
}
:
${
n
.
name
}
`
))
try
{
await
refInnerForm
.
value
.
iniData
(
formContent
.
value
);
console
.
log
(
'Inner form initialized successfully'
);
}
catch
(
error
)
{
console
.
error
(
'Failed to initialize inner form:'
,
error
);
handleError
(
'表单初始化失败'
,
error
);
if
(
idx
<=
0
)
return
[]
const
nodes
=
props
.
workflowNodes
.
slice
(
0
,
idx
)
console
.
log
(
'只读节点:'
,
nodes
.
map
(
n
=>
n
.
name
))
return
nodes
})
// 可编辑节点:索引等于 currentNodeIndex 的节点
const
editableNode
=
computed
(()
=>
{
if
(
!
props
.
workflowNodes
||
props
.
workflowNodes
.
length
===
0
)
{
return
null
}
};
const
idx
=
props
.
currentNodeIndex
if
(
idx
<
0
||
idx
>=
props
.
workflowNodes
.
length
)
{
console
.
warn
(
'可编辑节点索引无效:'
,
idx
)
return
null
}
const
node
=
props
.
workflowNodes
[
idx
]
console
.
log
(
'可编辑节点:'
,
node
?.
name
,
'索引:'
,
idx
)
return
node
})
// 设置表单组件 ref 的方法
function
setEditableFormRef
(
el
:
any
)
{
if
(
el
)
{
// 清除旧的引用,只保留当前激活的可编辑表单
editableFormRefs
.
value
=
[
el
]
console
.
log
(
'表单组件已挂载,组件方法:'
,
Object
.
keys
(
el
))
console
.
log
(
'是否有 submitForm 方法:'
,
typeof
el
.
submitForm
===
'function'
)
}
}
// 获取当前可编辑的表单组件实例
function
getCurrentEditableForm
():
FormComponentInstance
|
null
{
return
editableFormRefs
.
value
[
0
]
||
null
}
// 获取表单数据
function
getFormData
(
nodeId
:
string
):
any
{
const
data
=
formDataMap
.
value
[
nodeId
]
||
{}
console
.
log
(
'获取表单数据 - 节点:'
,
nodeId
,
'数据:'
,
data
)
return
data
}
// 表单准备就绪的回调
const
handleFormReady
=
()
=>
{
console
.
log
(
'Inner form component is ready'
);
// 如果已经有数据,可以在这里初始化
if
(
formContent
.
value
&&
refInnerForm
.
value
)
{
initializeInnerForm
();
// 获取或加载组件
function
getComponent
(
url
:
string
)
{
if
(
!
url
)
{
console
.
warn
(
'URL为空,返回空组件'
)
return
createEmptyComponent
()
}
};
// 模态框注册
const
[
registerModal
,
{
setModalProps
,
closeModal
}]
=
useModalInner
(
async
(
data
)
=>
{
try
{
resetState
();
loading
.
value
=
true
;
// 重置内部表单显示状态
showInnerForm
.
value
=
false
;
if
(
componentCache
.
has
(
url
))
{
return
componentCache
.
get
(
url
)
}
const
taskId
=
data
.
data
.
taskId
// 设置表单属性
formType
.
value
=
data
.
data
?.
formTp
||
0
;
formUrl
.
value
=
data
.
data
?.
formUrl
||
''
;
formTitle
.
value
=
data
.
data
?.
formTitle
||
'节点表单'
;
formContent
.
value
=
data
.
data
?.
formContent
||
''
;
console
.
log
(
'Modal data received:'
,
{
formType
:
formType
.
value
,
formTitle
:
formTitle
.
value
,
hasContent
:
!!
formContent
.
value
});
// 根据表单类型处理
if
(
formType
.
value
==
1
)
{
showInnerForm
.
value
=
true
;
await
nextTick
();
if
(
refInnerForm
.
value
)
{
await
initializeInnerForm
();
await
setNodeVals
(
taskId
)
}
}
else
if
(
formType
.
value
==
2
)
{
// if (!formUrl.value || !isValidUrl(formUrl.value)) {
// throw new Error('表单链接无效');
// }
}
// 设置模态框属性
setModalProps
({
title
:
modalTitle
.
value
,
confirmLoading
:
false
,
});
loading
.
value
=
false
;
}
catch
(
error
)
{
handleError
(
'表单加载失败'
,
error
);
}
});
// 方法定义
const
isValidUrl
=
(
url
:
string
):
boolean
=>
{
try
{
new
URL
(
url
);
return
true
;
}
catch
{
return
false
;
}
};
const
resetState
=
()
=>
{
loading
.
value
=
false
;
submitLoading
.
value
=
false
;
hasError
.
value
=
false
;
errorTitle
.
value
=
''
;
errorMessage
.
value
=
''
;
// 不重置 showInnerForm,由 watch 控制
};
const
handleError
=
(
title
:
string
,
error
:
any
)
=>
{
console
.
error
(
'表单错误:'
,
error
);
loading
.
value
=
false
;
hasError
.
value
=
true
;
errorTitle
.
value
=
title
;
errorMessage
.
value
=
error
.
message
||
'未知错误,请检查配置或联系管理员'
;
message
.
error
(
title
);
};
const
retryLoad
=
()
=>
{
resetState
();
window
.
location
.
reload
();
};
const
handleIframeLoad
=
()
=>
{
console
.
log
(
'iframe加载完成'
);
loading
.
value
=
false
;
};
const
handleIframeError
=
()
=>
{
handleError
(
'外部表单加载失败'
,
new
Error
(
'无法加载外部表单,请检查链接或网络连接'
));
};
const
handleCancel
=
()
=>
{
closeModal
();
};
const
handleReport
=
()
=>
{
message
.
info
(
'问题报告功能开发中...'
);
};
const
setNodeVals
=
async
(
taskId
)
=>
{
if
(
taskId
)
{
await
findFlowFormVal
({
taskId
:
taskId
}).
then
((
resValues
)
=>
{
nextTick
(()
=>
{
if
(
refInnerForm
.
value
)
{
refInnerForm
.
value
.
setFormData
(
resValues
)
}
})
})
let
componentPath
=
''
if
(
url
.
includes
(
'/views'
))
{
componentPath
=
`/src
${
url
}
`
}
else
{
componentPath
=
`/src/views
${
url
}
`
}
}
if
(
!
componentPath
.
match
(
/
\.(
vue|js|ts|jsx|tsx
)
$/
))
{
componentPath
+=
'.vue'
}
onUnmounted
(()
=>
{
formUrl
.
value
=
''
;
formContent
.
value
=
''
;
refInnerForm
.
value
=
null
;
});
</
script
>
console
.
log
(
'加载组件路径:'
,
componentPath
)
<
style
lang=
"scss"
scoped
>
.form-modal-card
{
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
:deep
(
.ant-card-body
)
{
flex
:
1
;
padding
:
0
;
display
:
flex
;
flex-direction
:
column
;
const
loader
=
modules
[
componentPath
]
if
(
!
loader
)
{
console
.
error
(
'未找到组件:'
,
componentPath
)
const
ErrorComponent
=
createErrorComponent
(
`组件未找到:
${
componentPath
}
`
)
componentCache
.
set
(
url
,
ErrorComponent
)
return
ErrorComponent
}
}
.loading-container
,
.error-container
,
.unsupported-container
{
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
min-height
:
500px
;
const
AsyncComponent
=
defineAsyncComponent
({
loader
:
()
=>
loader
()
as
Promise
<
{
default
:
any
}
>
,
loadingComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'text-align: center; padding: 20px;'
},
'加载中...'
)
},
errorComponent
:
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
'组件加载失败'
)
},
delay
:
200
,
timeout
:
3000
})
componentCache
.
set
(
url
,
AsyncComponent
)
return
AsyncComponent
}
.loading-content
{
text-align
:
center
;
:deep
(
.ant-spin-text
)
{
margin-top
:
12px
;
font-size
:
14px
;
color
:
#1890ff
;
function
createEmptyComponent
()
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: #999; padding: 20px; text-align: center;'
},
'该节点未配置表单'
)
}
}
.form-content-container
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
height
:
100%
;
function
createErrorComponent
(
msg
:
string
)
{
return
{
render
:
()
=>
h
(
'div'
,
{
style
:
'color: red; padding: 20px;'
},
msg
)
}
}
.iframe-wrapper
,
.inner-form-wrapper
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
height
:
100%
;
// 处理表单数据更新
function
handleFormDataUpdate
(
data
:
any
)
{
currentFormData
.
value
=
{
...
currentFormData
.
value
,
...
data
}
emit
(
'form-data-update'
,
currentFormData
.
value
)
}
.iframe-header
{
padding
:
16px
24px
0
;
/**
* 从表单组件获取数据
* 优先调用组件的 getFormData 方法,如果没有则返回 formData 属性或 currentFormData
*/
async
function
getFormDataFromComponent
():
Promise
<
any
>
{
const
formComponent
=
getCurrentEditableForm
()
.iframe-alert
{
border-radius
:
6px
;
:deep
(
.ant-alert-message
)
{
font-weight
:
500
;
if
(
!
formComponent
)
{
console
.
warn
(
'未找到表单组件实例'
)
return
currentFormData
.
value
}
console
.
log
(
'当前表单组件实例:'
,
formComponent
)
console
.
log
(
'组件方法列表:'
,
Object
.
keys
(
formComponent
))
// 方式1:调用组件的 getFormData 方法
if
(
typeof
formComponent
.
getFormData
===
'function'
)
{
try
{
const
data
=
await
formComponent
.
getFormData
()
console
.
log
(
'通过 getFormData 方法获取的数据:'
,
data
)
return
data
}
catch
(
error
)
{
console
.
error
(
'调用 getFormData 失败:'
,
error
)
}
}
// 方式2:获取组件的 formData 属性
if
(
formComponent
.
formData
!==
undefined
)
{
console
.
log
(
'通过 formData 属性获取的数据:'
,
formComponent
.
formData
)
return
formComponent
.
formData
}
// 方式3:如果组件有内部表单数据,尝试获取
if
(
formComponent
.
getValues
&&
typeof
formComponent
.
getValues
===
'function'
)
{
try
{
const
data
=
await
formComponent
.
getValues
()
console
.
log
(
'通过 getValues 方法获取的数据:'
,
data
)
return
data
}
catch
(
error
)
{
console
.
error
(
'调用 getValues 失败:'
,
error
)
}
}
// 方式4:返回本地维护的 currentFormData
console
.
log
(
'使用本地维护的 currentFormData:'
,
currentFormData
.
value
)
return
currentFormData
.
value
}
.iframe-container
{
flex
:
1
;
padding
:
16px
24px
24px
;
height
:
calc
(
100vh
-
50px
);
min-height
:
400px
;
.responsive-iframe
{
width
:
100%
;
height
:
100%
;
border
:
none
;
border-radius
:
8px
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.1
);
transition
:
box-shadow
0
.3s
ease
;
&
:hover
{
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0
.15
);
/**
* 验证表单数据
*/
async
function
validateForm
():
Promise
<
boolean
>
{
const
formComponent
=
getCurrentEditableForm
()
if
(
!
formComponent
)
{
return
true
}
// 方式1:调用组件的 validate 方法
if
(
typeof
formComponent
.
validate
===
'function'
)
{
try
{
await
formComponent
.
validate
()
return
true
}
catch
(
error
)
{
console
.
error
(
'表单验证失败:'
,
error
)
return
false
}
}
// 方式2:如果组件有 vee-validate 或其他验证库的实例
if
(
formComponent
.
v$
&&
typeof
formComponent
.
v$
.
$validate
===
'function'
)
{
try
{
const
isValid
=
await
formComponent
.
v$
.
$validate
()
return
isValid
}
catch
(
error
)
{
console
.
error
(
'验证失败:'
,
error
)
return
false
}
}
// 如果没有验证方法,默认通过
return
true
}
.inner-form-wrapper
{
.form-header
{
padding
:
20px
24px
0
;
// 提交处理
async
function
handleSubmit
()
{
if
(
!
editableNode
.
value
)
{
message
.
warning
(
'没有可编辑的表单'
)
return
}
submitLoading
.
value
=
true
try
{
const
formComponent
=
getCurrentEditableForm
()
.form-title
{
margin
:
0
;
font-size
:
18px
;
font-weight
:
600
;
color
:
#1f2d3d
;
// 🔥 优先调用子组件的 submitForm 方法
if
(
formComponent
&&
typeof
formComponent
.
submitForm
===
'function'
)
{
console
.
log
(
'调用子组件的 submitForm 方法'
)
// 调用子组件的 submitForm 方法,并等待返回结果
const
result
=
await
formComponent
.
submitForm
()
// 如果子组件的 submitForm 返回了数据,则使用返回的数据
if
(
result
!==
undefined
)
{
console
.
log
(
'submitForm 返回的数据:'
,
result
)
// 触发提交事件
emit
(
'submit'
,
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
result
,
procDefId
:
props
.
procDefId
,
formComponent
:
formComponent
})
message
.
success
(
'提交成功'
)
handleClose
()
return
}
}
:deep
(
.ant-divider
)
{
margin
:
16px
0
;
// 如果没有 submitForm 方法或 submitForm 没有返回数据,则使用原来的逻辑
console
.
log
(
'使用默认提交逻辑'
)
// 1. 先进行表单验证
const
isValid
=
await
validateForm
()
if
(
!
isValid
)
{
message
.
error
(
'请完善表单信息'
)
return
}
// 2. 获取表单数据
const
submitData
=
await
getFormDataFromComponent
()
console
.
log
(
'最终提交数据:'
,
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
submitData
,
procDefId
:
props
.
procDefId
})
// 3. 触发提交事件
emit
(
'submit'
,
{
nodeId
:
editableNode
.
value
.
id
,
nodeName
:
editableNode
.
value
.
name
,
formData
:
submitData
,
procDefId
:
props
.
procDefId
,
formComponent
:
formComponent
})
message
.
success
(
'提交成功'
)
// 提交成功后关闭抽屉
handleClose
()
}
catch
(
error
:
any
)
{
console
.
error
(
'提交失败:'
,
error
)
message
.
error
(
error
?.
message
||
'提交失败,请重试'
)
}
finally
{
submitLoading
.
value
=
false
}
}
// 关闭抽屉
function
handleClose
()
{
emit
(
'update:visible'
,
false
)
emit
(
'close'
)
// 关闭后清空表单引用
editableFormRefs
.
value
=
[]
}
// 重置数据
function
resetFormData
()
{
currentFormData
.
value
=
{}
const
newFormDataMap
:
Record
<
string
,
any
>
=
{}
readonlyNodes
.
value
.
forEach
(
node
=>
{
newFormDataMap
[
node
.
id
]
=
props
.
externalFormData
[
node
.
id
]
||
{}
})
formDataMap
.
value
=
newFormDataMap
if
(
editableNode
.
value
&&
props
.
externalFormData
[
editableNode
.
value
.
id
])
{
currentFormData
.
value
=
{
...
props
.
externalFormData
[
editableNode
.
value
.
id
]
}
}
.form-body
{
flex
:
1
;
padding
:
0
24px
24px
;
overflow-y
:
auto
;
min-height
:
400px
;
// 设置默认激活的选项卡为可编辑的选项卡
if
(
editableNode
.
value
)
{
activeTabKey
.
value
=
editableNode
.
value
.
id
console
.
log
(
'设置激活选项卡为可编辑节点:'
,
editableNode
.
value
.
name
)
}
else
if
(
readonlyNodes
.
value
.
length
>
0
)
{
activeTabKey
.
value
=
readonlyNodes
.
value
[
0
].
id
console
.
log
(
'设置激活选项卡为第一个只读节点:'
,
readonlyNodes
.
value
[
0
].
name
)
}
}
// 响应式设计
@media
(
max-width
:
768px
)
{
.form-modal-card
{
:deep
(
.ant-card-body
)
{
padding
:
0
;
// 预加载组件
function
preloadComponents
()
{
props
.
workflowNodes
.
forEach
(
node
=>
{
const
url
=
node
.
formUrl
||
node
.
formListUrl
if
(
url
)
{
getComponent
(
url
)
}
})
}
// 监听抽屉打开
watch
(()
=>
props
.
visible
,
(
newVal
)
=>
{
if
(
newVal
)
{
console
.
log
(
'抽屉打开,currentNodeIndex:'
,
props
.
currentNodeIndex
)
console
.
log
(
'所有节点:'
,
props
.
workflowNodes
)
resetFormData
()
preloadComponents
()
// 清空之前的表单引用
editableFormRefs
.
value
=
[]
}
.iframe-container
{
height
:
calc
(
100vh
-
220px
);
padding
:
12px
;
},
{
immediate
:
true
})
// 监听外部数据变化
watch
(()
=>
props
.
externalFormData
,
(
newData
)
=>
{
if
(
newData
&&
Object
.
keys
(
newData
).
length
>
0
)
{
console
.
log
(
'外部数据变化:'
,
newData
)
const
newFormDataMap
=
{
...
formDataMap
.
value
}
readonlyNodes
.
value
.
forEach
(
node
=>
{
if
(
newData
[
node
.
id
])
{
newFormDataMap
[
node
.
id
]
=
newData
[
node
.
id
]
}
})
formDataMap
.
value
=
newFormDataMap
if
(
editableNode
.
value
&&
newData
[
editableNode
.
value
.
id
])
{
currentFormData
.
value
=
newData
[
editableNode
.
value
.
id
]
}
}
.inner-form-wrapper
{
.form-header
,
.form-body
{
padding-left
:
16px
;
padding-right
:
16px
;
},
{
deep
:
true
})
onMounted
(()
=>
{
console
.
log
(
'组件挂载,workflowNodes:'
,
props
.
workflowNodes
)
console
.
log
(
'currentNodeIndex:'
,
props
.
currentNodeIndex
)
resetFormData
()
preloadComponents
()
})
defineExpose
({
resetFormData
,
getFormData
:
()
=>
currentFormData
.
value
,
getCurrentFormData
:
getFormDataFromComponent
,
validate
:
validateForm
,
submit
:
handleSubmit
})
</
script
>
<
style
scoped
lang=
"scss"
>
.workflow-form-drawer
{
width
:
100%
;
height
:
100%
;
overflow-y
:
auto
;
.form-tabs
{
:deep
(
.ant-tabs-nav
)
{
margin-bottom
:
0
;
background-color
:
#fafbfc
;
padding
:
0
16px
;
border-bottom
:
1px
solid
#e8eef2
;
}
:deep
(
.ant-tabs-tab
)
{
padding
:
12px
20px
;
font-weight
:
500
;
transition
:
all
0
.3s
;
&
:hover
{
color
:
#1890ff
;
}
}
:deep
(
.ant-tabs-tab-active
)
{
.ant-tabs-tab-btn
{
color
:
#1890ff
;
}
}
:deep
(
.ant-tabs-ink-bar
)
{
background
:
#1890ff
;
}
}
}
// 动画效果
.fade-enter-active
,
.fade-leave-active
{
transition
:
opacity
0
.3s
ease
;
}
.tab-content
{
padding
:
24px
;
min-height
:
400px
;
background-color
:
#fff
;
&
.readonly-content
{
background-color
:
#f5f5f5
;
:deep
(
input
),
:deep
(
textarea
),
:deep
(
.ant-input
),
:deep
(
.ant-select-selector
),
:deep
(
.ant-picker
)
{
background-color
:
#f5f5f5
!
important
;
cursor
:
not
-
allowed
!
important
;
color
:
#666
!
important
;
}
:deep
(
.ant-input-affix-wrapper
)
{
background-color
:
#f5f5f5
!
important
;
}
}
.fade-enter-from
,
.fade-leave-to
{
opacity
:
0
;
}
&
.editable-content
{
background-color
:
#fff
;
}
}
// 滚动条美化
.
form-body
:
:-
webkit-scrollbar
{
width
:
6px
;
.tab-tag
{
margin-left
:
8px
;
font-size
:
12px
;
transform
:
scale
(
0
.9
);
display
:
inline-block
;
}
.empty-state
{
padding
:
60px
0
;
text-align
:
center
;
}
&
:
:-
webkit-scrollbar
{
width
:
6px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
border-radius
:
3px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
3px
;
&
:hover
{
background
:
#a8a8a8
;
}
}
}
.
form-body
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
border-radius
:
3px
;
.drawer-footer
{
text-align
:
right
;
:deep
(
.ant-btn
)
{
margin-left
:
8px
;
&
:first-child
{
margin-left
:
0
;
}
}
}
.
form-body
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
b
order-radius
:
3px
;
:deep
(
.ant-drawer-body
)
{
padding
:
16px
;
b
ackground-color
:
#f5f7fa
;
}
.
form-body
:
:-
webkit-scrollbar-thumb
:
hover
{
background
:
#a8a8a8
;
:deep
(
.ant-drawer-footer
)
{
padding
:
12px
16px
;
border-top
:
1px
solid
#e8eef2
;
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/flowable/task/todo/components/TodoIndex.vue
浏览文件 @
78599ce5
...
...
@@ -548,7 +548,7 @@
submitData
.
values
[
'approvalType'
]
=
'role'
;
}
// }
console
.
log
(
"执行发送 "
,
submitData
);
// 执行发送
const
result
=
await
complete
(
submitData
);
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemArchiveList.vue
浏览文件 @
78599ce5
...
...
@@ -25,9 +25,22 @@
import
{
list
}
from
'./StProblemCheck.api'
;
import
StProblemCheckExecuteModal
from
'./components/StProblemCheckExecuteModal.vue'
;
import
{
useRoute
}
from
'vue-router'
;
const
route
=
useRoute
();
const
emit
=
defineEmits
([
'callback'
])
const
props
=
defineProps
({
beforeFlowNode
:
{
type
:
Object
,
default
:
()
=>
({})
},
currentFlowNode
:
{
type
:
Object
,
default
:
()
=>
({})
},
nextFlowNode
:
{
type
:
Object
,
default
:
()
=>
({})
}
})
//注册model
...
...
@@ -48,7 +61,7 @@
fieldMapToTime
:
[],
},
beforeFetch
(
params
)
{
params
[
'id'
]
=
route
.
query
.
id
params
[
'bmpNodeId'
]
=
props
.
currentFlowNode
.
id
},
actionColumn
:
{
width
:
200
,
...
...
@@ -60,11 +73,7 @@
const
[
registerTable
,
{
reload
},
{
rowSelection
,
selectedRowKeys
}]
=
tableContext
;
function
handleArchive
(
record
:
Recordable
)
{
openExecuteModal
(
true
,
{
record
,
isUpdate
:
true
,
showFooter
:
true
,
});
emit
(
"callback"
,
record
)
}
function
handleSuccess
()
{
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemCheck.data.ts
浏览文件 @
78599ce5
...
...
@@ -46,7 +46,8 @@ export const columns: BasicColumn[] = [
align
:
'center'
,
dataIndex
:
'problemDes'
,
resizable
:
true
,
ifShow
:
true
,
ifShow
:
false
,
},
{
title
:
'风险等级'
,
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemCheckList.vue
浏览文件 @
78599ce5
...
...
@@ -25,7 +25,7 @@
</
template
>
</BasicTable>
<!-- 表单区域 -->
<StProblemCheckModal
@
register=
"registerModal"
@
success=
"handleSuccess"
/>
<StProblemCheckModal
@
register=
"registerModal"
@
success=
"handleSuccess"
:center=
"true"
/>
</div>
</template>
...
...
@@ -53,7 +53,7 @@
}
})
const
emit
=
defineEmits
([
'callback'
])
const
emit
=
defineEmits
([
'callback'
,
'startWorkFlow'
,
'sendWorkFlow'
])
//注册model
const
[
registerModal
,
{
openModal
}]
=
useModal
();
...
...
@@ -76,7 +76,7 @@
params
[
'bmpNodeId'
]
=
props
.
currentFlowNode
.
id
},
actionColumn
:
{
width
:
4
00
,
width
:
2
00
,
fixed
:
'right'
,
},
},
...
...
@@ -95,7 +95,7 @@
/**
* 新增事件
*/
function
handleAdd
333333
()
{
function
handleAdd
()
{
openModal
(
true
,
{
isUpdate
:
false
,
showFooter
:
true
,
...
...
@@ -107,12 +107,7 @@
});
}
function
handleAdd
()
{
emit
(
"callback"
,
null
)
}
/**
* 编辑事件
*/
function
handleEdit
(
record
:
Recordable
)
{
openModal
(
true
,
{
record
,
...
...
@@ -120,17 +115,6 @@
showFooter
:
true
,
});
}
function
handlePlan
(
record
:
Recordable
)
{
openPlanModal
(
true
,
{
record
,
isUpdate
:
true
,
showFooter
:
true
,
});
}
/**
* 详情
*/
function
handleDetail
(
record
:
Recordable
)
{
openModal
(
true
,
{
record
,
...
...
@@ -138,27 +122,22 @@
showFooter
:
false
,
});
}
/**
* 删除事件
*/
async
function
handleDelete
(
record
)
{
await
deleteOne
({
id
:
record
.
id
},
handleSuccess
);
}
/**
* 批量删除事件
*/
async
function
batchHandleDelete
()
{
await
batchDelete
({
ids
:
selectedRowKeys
.
value
},
handleSuccess
);
}
/**
* 成功回调
*/
function
handleSuccess
()
{
function
handleSuccess
(
retData
)
{
(
selectedRowKeys
.
value
=
[])
&&
reload
();
if
(
retData
){
emit
(
'startWorkFlow'
,
retData
)
}
}
/**
* 操作栏
*/
function
getTableAction
(
record
)
{
return
[
{
...
...
@@ -189,17 +168,14 @@
}
async
function
handleFlow
(
record
:
Recordable
)
{
//alert(JSON.stringify(props.currentFlowNode))
//alert(JSON.stringify(props.nextFlowNode))
///alert(JSON.stringify(props.beforeFlowNode))
record
[
'deployId'
]
=
props
.
nextFlowNode
.
deployId
record
[
'bmpNodeId'
]
=
props
.
nextFlowNode
.
id
record
[
'bpmStatus'
]
=
2
await
saveOrUpdate
(
record
,
true
).
then
(
res
=>
{
handleSuccess
()
handleSuccess
(
record
)
})
emit
(
"sendWorkFlow"
,
record
)
}
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemExecApprovalList.vue
浏览文件 @
78599ce5
...
...
@@ -25,6 +25,7 @@
import
{
list
}
from
'./StProblemCheck.api'
;
import
StProblemCheckExecuteModal
from
'./components/StProblemCheckExecuteModal.vue'
;
const
emit
=
defineEmits
([
'callback'
])
const
props
=
defineProps
({
beforeFlowNode
:
{
...
...
@@ -59,7 +60,7 @@
fieldMapToTime
:
[],
},
beforeFetch
(
params
)
{
params
[
'id'
]
=
route
.
query
.
id
params
[
'bmpNodeId'
]
=
props
.
currentFlowNode
.
id
},
actionColumn
:
{
width
:
200
,
...
...
@@ -71,11 +72,7 @@
const
[
registerTable
,
{
reload
},
{
rowSelection
,
selectedRowKeys
}]
=
tableContext
;
function
handleExecuteApproval
(
record
:
Recordable
)
{
openExecuteModal
(
true
,
{
record
,
isUpdate
:
true
,
showFooter
:
true
,
});
emit
(
"callback"
,
record
)
}
function
handleSuccess
()
{
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemExecList.vue
浏览文件 @
78599ce5
...
...
@@ -25,6 +25,8 @@
import
{
list
}
from
'./StProblemCheck.api'
;
import
StProblemCheckExecuteModal
from
'./components/StProblemCheckExecuteModal.vue'
;
const
emit
=
defineEmits
([
'callback'
])
const
props
=
defineProps
({
beforeFlowNode
:
{
type
:
Object
,
...
...
@@ -58,7 +60,7 @@
fieldMapToTime
:
[],
},
beforeFetch
(
params
)
{
params
[
'id'
]
=
route
.
query
.
id
params
[
'bmpNodeId'
]
=
props
.
currentFlowNode
.
id
},
actionColumn
:
{
width
:
200
,
...
...
@@ -70,11 +72,7 @@
const
[
registerTable
,
{
reload
},
{
rowSelection
,
selectedRowKeys
}]
=
tableContext
;
function
handleExecute
(
record
:
Recordable
)
{
openExecuteModal
(
true
,
{
record
,
isUpdate
:
true
,
showFooter
:
true
,
});
emit
(
"callback"
,
record
)
}
function
handleSuccess
()
{
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemIndex.vue
浏览文件 @
78599ce5
...
...
@@ -5,6 +5,7 @@
<div
v-if=
"node.formListUrl"
class=
"tab-content"
>
<component
:is=
"loadComponent(node.formListUrl)"
@
startWorkFlow=
"handleDefinitionStart"
@
sendWorkFlow=
"handleDefinitionSend"
:beforeFlowNode=
"workflowNodes[index-1]"
:currentFlowNode=
"node"
:nextFlowNode=
"workflowNodes[index+1]"
...
...
@@ -17,8 +18,6 @@
</div>
</a-tab-pane>
</a-tabs>
<!-- 多表单抽屉组件 -->
<WorkFlowFormDrawer
v-model:visible=
"drawerVisible"
:title=
"drawerTitle"
...
...
@@ -26,23 +25,60 @@
:workflow-nodes=
"workflowNodes"
:external-form-data=
"externalFormData"
:proc-def-id=
"currentProcDefId"
:data-id=
"dataId"
:show-approval-panel=
"isShowApprovalPanel"
@
submit=
"handleMultiFormSubmit"
@
close=
"handleDrawerClose"
@
form-data-update=
"handleMultiFormDataUpdate"
width=
"50%"
width=
"90%"
/>
<TaskAssigneeDrawer
v-model:visible=
"drawerTaskVisible"
:task-id=
"taskId"
:task-name=
"taskName"
:proc-def-id=
"procDefId"
:proc-def-name=
"procDefName"
:form-data=
"formData"
:show-task-info=
"true"
:show-form-data=
"true"
@
success=
"handleSuccess"
@
error=
"handleError"
/>
</div>
</
template
>
<
script
lang=
"ts"
name=
"problem-stProblemCheck"
setup
>
import
{
ref
,
nextTick
,
onMounted
,
defineAsyncComponent
,
h
}
from
'vue'
;
import
{
getNodesByTableName
}
from
'/@/components/Process/api/definition'
;
import
{
definitionStart
,
flowXmlAndNode
}
from
"/@/components/Process/api/definition"
;
import
{
definitionStart
,
definitionStartByDeployId
}
from
"/@/components/Process/api/definition"
;
import
WorkFlowFormDrawer
from
'/@/views/common/WorkFlowFormDrawer.vue'
;
import
TaskAssigneeDrawer
from
'/@/views/common/TaskAssigneeDrawer.vue'
const
workflowNodes
=
ref
<
any
[]
>
([]);
const
activeTab
=
ref
(
1
);
const
dataId
=
ref
(
''
);
const
currentNode
=
ref
<
any
>
({});
const
isShowApprovalPanel
=
ref
(
true
);
// 任务指派抽屉相关状态
const
drawerTaskVisible
=
ref
(
false
);
const
taskId
=
ref
(
'task_123456'
)
const
taskName
=
ref
(
'部门经理审批'
)
const
procDefId
=
ref
(
'process_001'
)
const
procDefName
=
ref
(
'请假流程'
)
const
formData
=
ref
({
申请人
:
'张三'
,
请假类型
:
'年假'
,
开始时间
:
'2024-01-15'
,
结束时间
:
'2024-01-20'
,
请假天数
:
5
,
请假事由
:
'家庭旅游'
})
// 抽屉相关状态
const
drawerVisible
=
ref
(
false
);
const
drawerTitle
=
ref
(
'表单处理'
);
...
...
@@ -56,6 +92,7 @@
function
handleTabChange
(
key
)
{
activeTab
.
value
=
key
;
currentMultiFormIndex
.
value
=
key
-
1
;
currentNode
.
value
=
workflowNodes
.
value
[
key
-
1
];
}
function
loadComponent
(
url
:
string
)
{
...
...
@@ -106,12 +143,27 @@
return
AsyncComponent
;
}
const
handleDefinitionStart
=
(
procDefId
:
string
,
submitData
:
any
)
=>
{
return
definitionStart
({
procDefId
,
variables
:
submitData
})
const
handleDefinitionStart
=
async
(
data
)
=>
{
const
formData
=
{
dataId
:
data
.
id
,
dataName
:
'id'
};
const
startResRaw
=
await
definitionStartByDeployId
(
currentNode
.
value
?.
deployId
||
''
,
formData
);
if
(
startResRaw
?.
data
?.
id
)
{
dataId
.
value
=
startResRaw
.
data
.
id
;
}
}
const
handleDefinitionSend
=
async
(
data
)
=>
{
drawerTaskVisible
.
value
=
true
;
taskId
.
value
=
data
.
taskId
;
taskName
.
value
=
data
.
taskName
;
procDefId
.
value
=
data
.
procDefId
;
procDefName
.
value
=
data
.
procDefName
;
}
const
handleOpenMultiForm
=
(
params
:
{
nodeIndex
?:
number
;
title
?:
string
;
...
...
@@ -150,10 +202,12 @@
})
=>
{
console
.
log
(
'多表单提交数据:'
,
submitData
);
try
{
await
definitionStart
({
procDefId
:
submitData
.
procDefId
,
variables
:
submitData
.
formData
});
await
definitionStart
(
currentProcDefId
.
value
,
{
...
submitData
.
formData
}
);
drawerVisible
.
value
=
false
;
const
currentTabKey
=
activeTab
.
value
;
const
currentComponent
=
loadComponent
(
workflowNodes
.
value
[
currentTabKey
-
1
]?.
formListUrl
);
...
...
@@ -179,6 +233,17 @@
formData
});
};
function handleSuccess(response: any) {
console.log('任务处理成功:', response)
// 刷新列表等操作
}
function handleError(error: any) {
console.error('任务处理失败:', error)
}
defineExpose({
openHistoryForms,
...
...
@@ -188,11 +253,8 @@
onMounted(async () => {
await nextTick();
try {
console.log('开始获取工作流节点...');
const nodes = await getNodesByTableName("st_problem_check");
workflowNodes.value = nodes;
console.log('获取到的工作流节点:', workflowNodes.value);
workflowNodes.value.forEach((node, index) => {
console.log(`
节点
$
{
index
+
1
}:
`, node.name, 'formListUrl:', node.formListUrl);
});
...
...
@@ -206,21 +268,31 @@
}
});
}
currentNode.value = workflowNodes.value[0];
} catch (error) {
console.error('获取工作流节点失败:', error);
}
});
function handleCallback(data: any) {
drawerVisible.value = true;
const currentNode = workflowNodes.value[currentMultiFormIndex.value];
currentProcDefId.value = currentNode.procDefId || '';
const userid = currentNode.assignee || '';
const nodeId = currentNode.id
const deployId = currentNode.deployId || '';
const procDefId = currentNode.procDefId || '';
const attributes = currentNode.attributes || {};
dataId.value = data.id || '';
currentNode.value = workflowNodes.value[currentMultiFormIndex.value];
currentProcDefId.value = currentNode.value.procDefId || '';
if(currentNode.value.name.indexOf('审') > -1) {
isShowApprovalPanel.value = true;
} else {
isShowApprovalPanel.value = false;
}
const userid = currentNode.value?.assignee || '';
const nodeId = currentNode.value?.id
const procDefId = currentNode.value?.procDefId || '';
const attributes = currentNode.value?.attributes || {};
const userType = attributes.userType || [];
if (userType.length > 0) {
data['userType'] = userType[0].value || '';
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemPlanApprovalList.vue
浏览文件 @
78599ce5
...
...
@@ -40,6 +40,7 @@
}
})
const
emit
=
defineEmits
([
'callback'
])
//注册model
const
[
registerExecuteModal
,
{
openModal
:
openExecuteModal
}]
=
useModal
();
...
...
@@ -59,7 +60,7 @@
fieldMapToTime
:
[],
},
beforeFetch
(
params
)
{
params
[
'id'
]
=
route
.
query
.
id
params
[
'bmpNodeId'
]
=
props
.
currentFlowNode
.
id
},
actionColumn
:
{
width
:
200
,
...
...
@@ -71,11 +72,7 @@
const
[
registerTable
,
{
reload
},
{
rowSelection
,
selectedRowKeys
}]
=
tableContext
;
function
handlePlanApproval
(
record
:
Recordable
)
{
openExecuteModal
(
true
,
{
record
,
isUpdate
:
true
,
showFooter
:
true
,
});
emit
(
"callback"
,
record
)
}
function
handleSuccess
()
{
...
...
zrch-risk-client-39/src/views/project/problemCheck/StProblemPlanList.vue
浏览文件 @
78599ce5
...
...
@@ -25,7 +25,7 @@
import
{
list
}
from
'./StProblemCheck.api'
;
import
StProblemCheckExecuteModal
from
'./components/StProblemCheckExecuteModal.vue'
;
const
emit
=
defineEmits
([
'callback'
])
const
emit
=
defineEmits
([
'callback'
,
'sendWorkFlow'
])
const
props
=
defineProps
({
beforeFlowNode
:
{
...
...
@@ -72,6 +72,10 @@
function
handlePlan
(
record
:
Recordable
)
{
emit
(
"callback"
,
record
)
}
function
handleSendNext
(
record
:
Recordable
)
{
emit
(
"sendWorkFlow"
,
record
)
}
function
handleSuccess
()
{
(
selectedRowKeys
.
value
=
[])
&&
reload
();
...
...
@@ -80,12 +84,18 @@
function
getTableAction
(
record
)
{
return
[
{
label
:
'制定
整改
计划'
,
label
:
'制定计划'
,
onClick
:
handlePlan
.
bind
(
null
,
record
),
},
{
label
:
'提交'
,
onClick
:
handleSendNext
.
bind
(
null
,
record
),
},
];
}
</
script
>
<
style
scoped
></
style
>
zrch-risk-client-39/src/views/project/problemCheck/components/StProblemCheckForm.vue
浏览文件 @
78599ce5
...
...
@@ -23,6 +23,7 @@
props
:
{
formData
:
propTypes
.
object
.
def
({}),
formBpm
:
propTypes
.
bool
.
def
(
true
),
dataId
:
propTypes
.
string
.
def
(
''
),
},
setup
(
props
)
{
const
[
registerForm
,
{
setFieldsValue
,
setProps
,
getFieldsValue
}]
=
useForm
({
...
...
@@ -41,8 +42,8 @@
let
formData
=
{};
const
queryByIdUrl
=
'/problem/stProblemCheck/queryById/'
;
async
function
initFormData
()
{
let
params
=
{
id
:
props
.
formData
.
dataI
d
};
async
function
initFormData
(
did
)
{
let
params
=
{
id
:
props
.
dataId
||
di
d
};
const
data
=
await
defHttp
.
get
({
url
:
queryByIdUrl
,
params
});
formData
=
{
...
data
};
//设置表单的值
...
...
@@ -52,19 +53,17 @@
}
async
function
submitForm
()
{
alert
(
888
)
let
data
=
getFieldsValue
();
let
params
=
Object
.
assign
({},
formData
,
data
);
console
.
log
(
'表单数据'
,
params
);
await
saveOrUpdate
(
params
,
true
);
}
//initFormData();
return
{
registerForm
,
formDisabled
,
submitForm
,
initFormData
};
},
});
...
...
zrch-risk-client-39/src/views/project/problemCheck/components/StProblemCheckModal.vue
浏览文件 @
78599ce5
...
...
@@ -51,11 +51,11 @@
values
.
deployId
=
bpmFlowData
.
value
?.
deployId
||
''
;
setModalProps
({
confirmLoading
:
true
});
//提交表单
await
saveOrUpdate
(
values
,
isUpdate
.
value
);
const
retData
=
await
saveOrUpdate
(
values
,
isUpdate
.
value
);
//关闭弹窗
closeModal
();
//刷新列表
emit
(
'success'
);
emit
(
'success'
,
retData
);
}
finally
{
setModalProps
({
confirmLoading
:
false
});
}
...
...
zrch-risk-client-39/src/views/project/problemCheck/components/StProblemCheckPlanForm.vue
浏览文件 @
78599ce5
...
...
@@ -12,7 +12,7 @@
import
{
computed
,
defineComponent
}
from
'vue'
;
import
{
defHttp
}
from
'/@/utils/http/axios'
;
import
{
propTypes
}
from
'/@/utils/propTypes'
;
import
{
getPlanFormSchema
}
from
'../StProblemCheck.data'
;
import
{
getPlanFormSchema
,
formSchema
}
from
'../StProblemCheck.data'
;
import
{
saveOrUpdate
,
}
from
'../StProblemCheck.api'
;
export
default
defineComponent
({
...
...
@@ -23,11 +23,12 @@
props
:
{
formData
:
propTypes
.
object
.
def
({}),
formBpm
:
propTypes
.
bool
.
def
(
true
),
dataId
:
propTypes
.
string
.
def
(
''
),
},
setup
(
props
)
{
const
[
registerForm
,
{
setFieldsValue
,
setProps
,
getFieldsValue
}]
=
useForm
({
labelWidth
:
150
,
schemas
:
getPlanFormSchema
(
props
.
formData
)
,
schemas
:
formSchema
,
showActionButtonGroup
:
false
,
baseColProps
:
{
span
:
24
},
});
...
...
@@ -41,29 +42,24 @@
let
formData
=
{};
const
queryById
=
'/problem/stProblemCheck/queryById'
;
async
function
initFormData
()
{
let
params
=
{
id
:
props
.
formData
.
dataI
d
};
async
function
initFormData
(
did
)
{
let
params
=
{
id
:
props
.
dataId
||
di
d
};
const
data
=
await
defHttp
.
get
({
url
:
queryById
,
params
});
formData
=
{
...
data
};
//设置表单的值
await
setFieldsValue
(
formData
);
//默认是禁用
await
setProps
({
disabled
:
formDisabled
.
value
});
}
async
function
submitForm
()
{
let
data
=
getFieldsValue
();
let
params
=
Object
.
assign
({},
formData
,
data
);
console
.
log
(
'表单数据'
,
params
);
await
saveOrUpdate
(
params
,
true
);
}
initFormData
();
return
{
registerForm
,
formDisabled
,
submitForm
,
initFormData
};
},
});
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论