fix(bpm):修正流程实例审批弹窗网关分支重算的并发与提交问题
- 提交时不再用节点表单值覆盖 data.variables;与预览阶段使用同一份合并变量 - onChange 加 useDebounceFn(300ms) + 请求序号去重,handleAudit 提交前 await 最新一轮重算 - 切换任务时重置请求序号与 pending 重算 - 改用 form-create 官方 formData() 取节点表单当前值 - 双 nextTick 改为 until 等 fApi 就绪,1s 兜底超时feat/mes
parent
8571a27a15
commit
6d5705b655
|
|
@ -526,6 +526,7 @@ import {
|
|||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { until, useDebounceFn } from '@vueuse/core'
|
||||
import SignDialog from './SignDialog.vue'
|
||||
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
|
@ -574,6 +575,8 @@ const signRef = ref()
|
|||
const approveSignFormRef = ref()
|
||||
const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 下一个审批节点信息
|
||||
const nextAssigneesTimelineRef = ref() // 下一个节点审批人时间线组件的引用
|
||||
let nextApprovalRequestId = 0 // 请求序号;onChange 高频触发时,丢弃过期请求结果
|
||||
let pendingNextNodesTask: Promise<unknown> | null = null // 跟踪 onChange 触发的最新一轮重算,提交前需 await 等其完成
|
||||
const approveReasonForm = reactive({
|
||||
reason: '',
|
||||
signPicUrl: '',
|
||||
|
|
@ -582,7 +585,11 @@ const approveReasonForm = reactive({
|
|||
const approveReasonRule = computed(() => {
|
||||
return {
|
||||
reason: [
|
||||
{ required: reasonRequire.value, message: nodeTypeName.value + '意见不能为空', trigger: 'blur' }
|
||||
{
|
||||
required: reasonRequire.value,
|
||||
message: nodeTypeName.value + '意见不能为空',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }],
|
||||
nextAssignees: [{ required: true, message: '审批人不能为空', trigger: 'blur' }]
|
||||
|
|
@ -709,11 +716,16 @@ const openPopover = async (type: string) => {
|
|||
popOverVisible.value[item] = item === type
|
||||
})
|
||||
if (type === 'approve') {
|
||||
// 等待表单渲染完成后,再初始化下一个节点信息
|
||||
await nextTick()
|
||||
// 再等待一个 tick,确保 form-create 的 API 已经初始化
|
||||
await nextTick()
|
||||
initNextAssigneesFormField()
|
||||
// 当前任务有节点表单时,等 form-create 的 fApi 就绪后再计算下一个节点;
|
||||
// 没有节点表单时,approveFormFApi 永远不会被赋值,跳过等待
|
||||
if (runningTask.value?.formId > 0) {
|
||||
// 1s 兜底超时;超时 until 会抛错,这里静默吞掉,让首次计算照常进行
|
||||
await until(() => typeof approveFormFApi.value?.validate === 'function')
|
||||
.toBeTruthy({ timeout: 1000 })
|
||||
.catch(() => {})
|
||||
}
|
||||
// 初始化下一个审批人表单字段
|
||||
await initNextAssigneesFormField()
|
||||
}
|
||||
// await nextTick()
|
||||
// formRef.value.resetFields()
|
||||
|
|
@ -734,6 +746,8 @@ const closePopover = (type: string, formRef: FormInstance | undefined) => {
|
|||
|
||||
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
|
||||
const initNextAssigneesFormField = async () => {
|
||||
// 记录当前请求序号;如果在等待响应期间又有新请求发出,本次结果作废
|
||||
const requestId = ++nextApprovalRequestId
|
||||
// 获取修改的流程变量, 暂时只支持流程表单
|
||||
const variables = getUpdatedProcessInstanceVariables()
|
||||
const data = await ProcessInstanceApi.getNextApprovalNodes({
|
||||
|
|
@ -741,6 +755,12 @@ const initNextAssigneesFormField = async () => {
|
|||
taskId: runningTask.value.id,
|
||||
processVariablesStr: JSON.stringify(variables)
|
||||
})
|
||||
// 已有更新的请求发出,丢弃本次过期结果,避免把旧分支节点回写到当前列表
|
||||
if (requestId !== nextApprovalRequestId) {
|
||||
return
|
||||
}
|
||||
// 在最新结果到达时再清空,避免请求期间出现节点信息抖动
|
||||
nextAssigneesActivityNode.value = []
|
||||
if (data && data.length > 0) {
|
||||
const customApproveUsersData: Record<string, any[]> = {} // 用于收集需要设置到 Timeline 组件的自定义审批人数据
|
||||
data.forEach((node: any) => {
|
||||
|
|
@ -769,6 +789,9 @@ const initNextAssigneesFormField = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
/** onChange 高频触发时合并 300ms 内的连续按键,减少网关查询请求 */
|
||||
const debouncedInitNextAssigneesFormField = useDebounceFn(initNextAssigneesFormField, 300)
|
||||
|
||||
/** 选择下一个节点的审批人 */
|
||||
const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
|
||||
approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id)
|
||||
|
|
@ -803,6 +826,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
}
|
||||
|
||||
if (pass) {
|
||||
// 等待 onChange 触发的最新一轮重算落地,避免拿旧分支节点 + 旧审批人选择 + 新表单变量的错配组合提交
|
||||
if (pendingNextNodesTask) {
|
||||
await pendingNextNodesTask
|
||||
}
|
||||
const nextAssigneesValid = validateNextAssignees()
|
||||
if (!nextAssigneesValid) return
|
||||
const variables = getUpdatedProcessInstanceVariables()
|
||||
|
|
@ -817,13 +844,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
if (runningTask.value.signEnable) {
|
||||
data.signPicUrl = approveReasonForm.signPicUrl
|
||||
}
|
||||
// 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
// TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
|
||||
// 多表单处理:节点表单需要校验;变量已经在 getUpdatedProcessInstanceVariables 中合并到 data.variables,无需再覆盖
|
||||
const formCreateApi = approveFormFApi.value
|
||||
if (Object.keys(formCreateApi)?.length > 0) {
|
||||
await formCreateApi.validate()
|
||||
// @ts-ignore
|
||||
data.variables = approveForm.value.value
|
||||
}
|
||||
await TaskApi.approveTask(data)
|
||||
popOverVisible.value.approve = false
|
||||
|
|
@ -1081,27 +1105,23 @@ const loadTodoTask = (task: any) => {
|
|||
approveForm.value = {}
|
||||
runningTask.value = task
|
||||
approveFormFApi.value = {}
|
||||
// 切换任务时重置请求序号与 pending 重算,避免旧任务飞行中的请求/Promise 串到新任务
|
||||
nextApprovalRequestId += 1
|
||||
pendingNextNodesTask = null
|
||||
reasonRequire.value = task?.reasonRequire ?? false
|
||||
nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
|
||||
// 处理 approve 表单.
|
||||
// 处理 approve 表单
|
||||
if (task && task.formId && task.formConf) {
|
||||
const tempApproveForm = {}
|
||||
const tempApproveForm: { option?: any; rule?: any; value?: any } = {}
|
||||
setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
|
||||
// 为表单添加 onChange 事件,当表单值变化时,重新计算下一个节点的信息
|
||||
// @ts-ignore
|
||||
if (!tempApproveForm.option) {
|
||||
// @ts-ignore
|
||||
tempApproveForm.option = {}
|
||||
}
|
||||
// @ts-ignore
|
||||
// 为表单添加 onChange 事件,当表单值变化时,重新计算下一个节点的信息;网关分支可能依赖表单字段
|
||||
tempApproveForm.option.onChange = () => {
|
||||
// 当弹窗打开时,才重新计算下一个节点的信息
|
||||
if (popOverVisible.value.approve) {
|
||||
// 清空之前的节点信息
|
||||
nextAssigneesActivityNode.value = []
|
||||
// 重新计算下一个节点的信息
|
||||
initNextAssigneesFormField()
|
||||
// 弹窗打开时,才重新计算下一个节点的信息
|
||||
if (!popOverVisible.value.approve) {
|
||||
return
|
||||
}
|
||||
// useDebounceFn 会把前一次返回的 Promise reject 掉,需 catch 吞掉 'cancelled'
|
||||
pendingNextNodesTask = debouncedInitNextAssigneesFormField().catch(() => {})
|
||||
}
|
||||
approveForm.value = tempApproveForm
|
||||
} else {
|
||||
|
|
@ -1128,38 +1148,15 @@ const validateNormalForm = async () => {
|
|||
const getUpdatedProcessInstanceVariables = () => {
|
||||
const variables = {}
|
||||
// 从流程表单(流程定义级别)中获取变量
|
||||
if (props.writableFields && props.writableFields.length > 0 && props.normalFormApi) {
|
||||
if (props.writableFields?.length && props.normalFormApi) {
|
||||
props.writableFields.forEach((field) => {
|
||||
variables[field] = props.normalFormApi.getValue(field)
|
||||
})
|
||||
}
|
||||
// 从节点表单(节点级别)中获取变量
|
||||
// 优先从 approveForm.value.value 中获取(这是 form-create 存储的值)
|
||||
if (approveForm.value?.value) {
|
||||
Object.assign(variables, approveForm.value.value)
|
||||
}
|
||||
// 再从 formVariables 中获取(这是后端返回的已保存的变量)
|
||||
if (approveForm.value?.formVariables) {
|
||||
Object.assign(variables, approveForm.value.formVariables)
|
||||
}
|
||||
// 再从 formFields 中获取(这是表单的字段值)
|
||||
if (approveForm.value?.formFields) {
|
||||
Object.assign(variables, approveForm.value.formFields)
|
||||
}
|
||||
// 最后尝试从 approveFormFApi 中获取(这是用户在表单中修改的值)
|
||||
if (approveFormFApi.value && approveForm.value?.rule) {
|
||||
approveForm.value.rule.forEach((field: any) => {
|
||||
if (field.field) {
|
||||
try {
|
||||
const value = approveFormFApi.value.getValue(field.field)
|
||||
if (value !== undefined && value !== null) {
|
||||
variables[field.field] = value
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略获取值时的错误
|
||||
}
|
||||
}
|
||||
})
|
||||
// 从节点表单(节点级别)中获取变量;通过 form-create 官方的 formData() 拿当前值
|
||||
const nodeFormData = approveFormFApi.value?.formData?.()
|
||||
if (nodeFormData) {
|
||||
Object.assign(variables, nodeFormData)
|
||||
}
|
||||
return variables
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue