af-mobile-client-vue3 1.6.19 → 1.6.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.env +11 -11
  2. package/package.json +121 -121
  3. package/src/api/user/index.ts +50 -50
  4. package/src/components/common/MateChat/apiService.ts +310 -310
  5. package/src/components/data/OtherCharge/OtherChargeGroupModal.vue +542 -542
  6. package/src/components/data/UserDetail/api.ts +24 -24
  7. package/src/components/data/UserDetail/index.vue +660 -660
  8. package/src/components/data/XFormGroup/doc/UserForm.vue +102 -102
  9. package/src/components/data/step/index.vue +1975 -1975
  10. package/src/router/invoiceRoutes.ts +37 -37
  11. package/src/services/api/Login.ts +6 -6
  12. package/src/services/v3Api.ts +170 -170
  13. package/src/stores/modules/user.ts +441 -441
  14. package/src/types/platform.ts +194 -194
  15. package/src/utils/Storage.ts +124 -124
  16. package/src/utils/http/index.ts +228 -228
  17. package/src/utils/login/loginVerify.ts +317 -317
  18. package/src/views/SafeInspection/SecurityCertificate/AddDevice/index.vue +662 -661
  19. package/src/views/SafeInspection/SecurityCertificate/OverallHiddenDangers/index.vue +376 -376
  20. package/src/views/SafeInspection/SecurityCertificate/contractSign/index.vue +80 -80
  21. package/src/views/SafeInspection/SecurityCertificate/photoSignature/SignatureComponent/SignatureComponent.vue +285 -285
  22. package/src/views/SafeInspection/SecurityCertificate/photoSignature/index.vue +258 -258
  23. package/src/views/SafeInspection/SecurityCertificate/photoSignature/slots/QinHuaSignature.vue +82 -82
  24. package/src/views/SafeInspection/SecurityCertificate/slots/GasDevice.vue +132 -132
  25. package/src/views/SafeInspection/SecurityCertificate/userInfo/index.vue +1 -0
  26. package/src/views/SafeInspection/SecurityCertificate/userInfo/upaddress.vue +239 -239
  27. package/src/views/SafeInspection/SecurityFormItem/XMultiSelect/index.vue +194 -194
  28. package/src/views/SafeInspection/SecurityFormItem/XSignature/index.vue +68 -68
  29. package/src/views/SafeInspection/SecurityFormItem/index.vue +418 -418
  30. package/src/views/component/UserDetailView/UserDetailPage.vue +78 -78
  31. package/src/views/component/UserDetailView/index.vue +234 -234
  32. package/src/views/external/index.vue +158 -158
  33. package/src/views/user/employeeBinding/index.vue +392 -392
  34. package/src/views/user/register/index.vue +995 -995
  35. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -21
  36. package/src/views/userRecords/CardReplacementRecords.vue +21 -21
  37. package/src/views/userRecords/ChangeRecords.vue +19 -19
  38. package/src/views/userRecords/CommandViewRecords.vue +20 -20
  39. package/src/views/userRecords/GasCompensationRecords.vue +20 -20
  40. package/src/views/userRecords/GasPurchaseRecords.vue +19 -19
  41. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -21
  42. package/src/views/userRecords/MeterRecords.vue +20 -20
  43. package/src/views/userRecords/OperateRecords.vue +51 -51
  44. package/src/views/userRecords/OtherChargeRecords.vue +19 -19
  45. package/src/views/userRecords/PaymentRecords.vue +114 -114
  46. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -19
  47. package/src/views/userRecords/RepairRecords.vue +19 -19
  48. package/src/views/userRecords/ReplacementRecords.vue +19 -19
  49. package/src/views/userRecords/SafetyRecords.vue +19 -19
  50. package/src/views/userRecords/TransactionRecords.vue +21 -21
  51. package/src/views/userRecords/TransferGasRecords.vue +19 -19
  52. package/src/views/userRecords/TransferRecords.vue +19 -19
  53. package/vite.config.ts +121 -121
  54. package/certs/127.0.0.1+2-key.pem +0 -28
  55. package/certs/127.0.0.1+2.pem +0 -27
  56. package/mock/modules/prose.mock.ts.timestamp-1758877157774.mjs +0 -53
  57. package/mock/modules/user.mock.ts.timestamp-1758877157774.mjs +0 -97
@@ -1,1975 +1,1975 @@
1
- <script setup lang="ts">
2
- import BeautifulLoading from '@af-mobile-client-vue3/components/core/BeautifulLoading/index.vue'
3
-
4
- import FileItem from '@af-mobile-client-vue3/components/data/step/file-item.vue'
5
- import HistoryFormData from '@af-mobile-client-vue3/components/data/step/history-form-data.vue'
6
-
7
- import PersonSelect from '@af-mobile-client-vue3/components/data/step/person-select.vue'
8
-
9
- import XForm from '@af-mobile-client-vue3/components/data/XForm/index.vue'
10
-
11
- import XFormItem from '@af-mobile-client-vue3/components/data/XFormItem/index.vue'
12
- import JSONObject from '@af-mobile-client-vue3/expression/instances/JSONObject'
13
- import LogicRunner from '@af-mobile-client-vue3/logic/LogicRunner'
14
- import { getConfigByName, runLogic } from '@af-mobile-client-vue3/services/api/common'
15
- import useStepStore from '@af-mobile-client-vue3/stores/modules/step'
16
- import useUserStore from '@af-mobile-client-vue3/stores/modules/user'
17
- import * as util from '@af-mobile-client-vue3/utils/common'
18
-
19
- import { getDictItemByValue } from '@af-mobile-client-vue3/utils/dictUtil'
20
- import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
21
-
22
- import { formatDate } from '@vueuse/core'
23
- import {
24
- closeDialog,
25
- showConfirmDialog,
26
- showToast,
27
- ActionSheet as VanActionSheet,
28
- Button as VanButton,
29
- Cell as VanCell,
30
- CellGroup as VanCellGroup,
31
- Empty as VanEmpty,
32
- Field as VanField,
33
- FloatingBubble as VanFloatingBubble,
34
- Icon as VanIcon,
35
- } from 'vant'
36
- import { computed, getCurrentInstance, onActivated, onBeforeMount, provide, ref } from 'vue'
37
- import { useRoute, useRouter } from 'vue-router'
38
-
39
- defineOptions({ name: 'StepDetailBase' })
40
-
41
- const props = withDefaults(defineProps<Props>(), {
42
- workflowId: undefined,
43
- stepId: undefined,
44
- serviceName: 'af-apply',
45
- onSelectAddress: undefined,
46
- onFormFunc: undefined,
47
- })
48
-
49
- interface Props {
50
- workflowId?: string
51
- stepId?: string
52
- serviceName?: string
53
- // 业务相关的处理函数
54
- onSelectAddress?: () => void
55
- onFormFunc?: (func: string) => void
56
- }
57
-
58
- const router = useRouter()
59
- const route = useRoute()
60
- const workflowId: string = props.workflowId || (route.query.workflowId as string)
61
- const stepId: string = props.stepId || (route.query.stepId as string)
62
-
63
- const userStore = useUserStore()
64
-
65
- const stepStore = useStepStore()
66
-
67
- const serviceName = ref(props.serviceName)
68
-
69
- const isLoading = ref(false)
70
- const isFinish = ref(false)
71
- const isError = ref(false)
72
- // 错误信息
73
- const errorMsg = ref('')
74
- // 错误图片类型
75
- const errorImage = ref('default')
76
- // 录入表单实例
77
- const xAddForm = ref<any>(undefined)
78
- // 展示类型 0 表单 1 只读资源
79
- const showType = ref(0)
80
- // 显示供用户填写的当前步骤表单
81
- const showForm = ref(false)
82
- // 当前步骤完成状态
83
- const stepDone = ref(false)
84
- // 工作流详情数据
85
- const workflowDetailData = ref(undefined)
86
- // 所有步骤定义
87
- const stepDefines = ref([])
88
- // 所有流程元数据
89
- const stepMetaDefines = ref([])
90
- // 当前步骤信息
91
- const currentStep = ref(undefined)
92
- // 流程表单定义
93
- const stepFormDefine = ref(undefined)
94
- const historyFormCompletedDataPreview = ref(undefined)
95
- // 流程表单数据
96
- const stepFormData = ref(undefined)
97
- // 流程附件数据
98
- const stepFilesData = ref(undefined)
99
- // 流程图片数据
100
- const stepImageData = ref(undefined)
101
- // 所有节点联通数据
102
- const directions = ref([])
103
- // 当前步骤能通向的后续步骤
104
- const currentDirections = ref([])
105
- // 当前步骤是否是最后一步
106
- const isLastStep = ref(false)
107
- // 是否显示下一步按钮
108
- const isShowNextStepBtn = ref(false)
109
- // 是否显示回退按钮
110
- const isShowPrevStepBtn = ref(false)
111
- // 是否显示跳过按钮
112
- const isShowSkipStepBtn = ref(false)
113
- // 下一步按钮目标
114
- const nextStepBtnToObj = ref(undefined)
115
- // 回退按钮目标
116
- const prevStepBtnToObj = ref(undefined)
117
- // 跳过按钮目标
118
- const skipStepBtnToObj = ref(undefined)
119
- // 下一步按钮提示
120
- const nextBtnPrompt = ref('')
121
- // 回退按钮提示
122
- const prevBtnPrompt = ref('')
123
- // 跳过按钮提示
124
- const skipBtnPrompt = ref('')
125
- // 操作类型,提交到下一步或跳过
126
- const operationType = ref('submit')
127
- // 是否需要选择人员
128
- const isNeedSelectPerson = ref(false)
129
- // 已选择的下一环节处理人
130
- const checkedNextStepPerson = ref(undefined)
131
- // 已选择的下一环节处理人名称
132
- const checkedNextStepPersonName = ref(undefined)
133
- // 下一环节处理人选项列表
134
- const nextStepPersonOptions = ref([])
135
- // 下一环节人员信息
136
- const nextStepPersonInfo = ref(undefined)
137
- // 下一环节截止时间
138
- const deadline = ref(undefined)
139
- // 备注表单内容
140
- const noteContent = ref('')
141
- // 退回原因内容
142
- const backNoteContent = ref('')
143
- // 用于将已有数据,填回x-add表单中
144
- const formCompletedData = ref({})
145
- // 用于展示提交的数据
146
- const formCompletedDataPreview = ref(undefined)
147
- // 展示更多按钮下拉菜单
148
- const showMoreMenu = ref(false)
149
- // 所有操作按钮
150
- const allButtons = ref([])
151
- // 展示工具下拉菜单
152
- const showToolsMenu = ref(false)
153
- // 浮动按钮工具列表
154
- const toolActions = ref([])
155
- /** 分支节点相关数据 */
156
- // 需要选择人员的分支节点列表
157
- const branchNodes = ref([])
158
- // 分支节点人员选择 格式:{stepId: personId}
159
- const branchChargePersons = ref({})
160
- // 是否需要为多个分支节点预先选择人员(包含WF_RESULT的条件分支或并行分支)
161
- const needMultipleBranchSelection = ref(false)
162
- // 前台计算出的目标节点
163
- const calculatedTargetNode = ref(undefined)
164
- // 缓存当前的分支动作,避免重复调用
165
- const currentBranchActions = ref(undefined)
166
- // xAddForm 默认值初始化
167
- const xAddFormData = ref({})
168
- // 是否展示Dialog
169
- const dialogVisible = ref(false)
170
- // Dialog标题
171
- const dialogTitle = ref('')
172
- // 当前环节 小工具中工具名称
173
- const mobileToolNames = ref('')
174
-
175
- // 是否可以提交
176
- const canSubmit = computed(() => {
177
- // 对于超级管理员直接认为可以提交
178
- if (userStore.getUserInfo().username === '1') {
179
- return true
180
- }
181
- if (!currentStep.value) {
182
- return false
183
- }
184
- // 已完成节点,检查是否展示表单数据
185
- if (currentStep.value.status !== 1 || workflowDetailData.value?.f_state === 1) {
186
- // 历史节点为当前登录人操作则不做查看数据权限校验
187
- if (currentStep.value.handler === userStore.getUserInfo().name) {
188
- return true
189
- }
190
- const viewers = currentStep.value.properties?.otherProperty?.viewers ?? []
191
- if (viewers.length > 0) {
192
- // 使用some方法判断当前人员是否满足任一条件
193
- return viewers.some((item) => {
194
- if (item.type === 'role') {
195
- // 检查rolestr是否存在并包含指定角色
196
- return userStore.getUserInfo().rolestr && userStore.getUserInfo().rolestr.split(',').includes(item.name)
197
- }
198
- if (item.type === 'department') {
199
- // 检查depname是否存在并包含指定部门
200
- return userStore.getUserInfo().deps && userStore.getUserInfo().deps?.includes(item.name)
201
- }
202
- return false
203
- })
204
- }
205
- else {
206
- return true
207
- }
208
- }
209
- else {
210
- // 检查角色和部门权限
211
- if (currentStep.value.properties && currentStep.value.properties.chargePerson) {
212
- // 如果当前节点的负责人选项中有设置选择人员 当前节点就只能由上一步设置的负责人操作
213
- if (currentStep.value.properties.chargePerson.needSelectPerson) {
214
- return currentStep.value.handler === userStore.getUserInfo().name
215
- }
216
- if (currentStep.value.properties.chargePerson.personList && currentStep.value.properties.chargePerson.personList.length > 0) {
217
- // 使用some方法判断当前人员是否满足任一条件
218
- return currentStep.value.properties.chargePerson.personList.some((item) => {
219
- if (item.type === 'role') {
220
- // 检查rolestr是否存在并包含指定角色
221
- return userStore.getUserInfo().rolestr && userStore.getUserInfo().rolestr.split(',').includes(item.name)
222
- }
223
- if (item.type === 'department') {
224
- // 检查parentname是否存在并包含指定部门
225
- return userStore.getUserInfo().parentname && userStore.getUserInfo().parentname.includes(item.name)
226
- }
227
- return false
228
- })
229
- }
230
- }
231
-
232
- // 检查handler是否包含当前用户
233
- if (currentStep.value && currentStep.value.handler) {
234
- return currentStep.value.handler.includes(userStore.getUserInfo().name)
235
- }
236
- }
237
- return false
238
- })
239
-
240
- // 判断当前节点的分支状态
241
- const isBranchLastNode = computed(() => {
242
- // 默认返回值
243
- const defaultResult = {
244
- isBranchNode: false, // 是否为分支流程中的节点
245
- isLastBranch: false, // 是否为最后一个未完成的分支(需要选择下一环节人员)
246
- }
247
-
248
- // 如果是最后一步或没有下一步或者不能提交,则不是分支节点
249
- if (isLastStep.value || !nextStepBtnToObj.value || !canSubmit.value) {
250
- return defaultResult
251
- }
252
-
253
- // 检查下一个节点是否为分支退出节点
254
- const nextStep = stepMetaDefines.value.find(step => step.id === nextStepBtnToObj.value)
255
-
256
- // 如果下一个节点的flowRole不是branchExit,说明不是分支的最后一个节点
257
- if (!nextStep || nextStep.properties?.flowRole !== 'branchExit') {
258
- return defaultResult
259
- }
260
-
261
- // 获取分支退出节点等待的所有分支步骤ID
262
- const waitStepIds = nextStep.properties.waitStepIds || []
263
- if (waitStepIds.length === 0) {
264
- return {
265
- isBranchNode: true,
266
- isLastBranch: true, // 如果没有等待步骤配置,默认认为是最后一个
267
- }
268
- }
269
-
270
- // 当前步骤必须在等待列表中才算是分支节点
271
- if (!waitStepIds.includes(currentStep.value.id)) {
272
- return defaultResult
273
- }
274
-
275
- // 检查除当前步骤外的其他分支步骤状态
276
- const otherBranchSteps = waitStepIds.filter(stepId => stepId !== currentStep.value.id)
277
-
278
- // 判断其他所有分支步骤是否都已完成(状态为2)
279
- const allOtherBranchesCompleted = otherBranchSteps.every((stepId) => {
280
- const step = stepDefines.value.find(s => s.id === stepId)
281
- return step && step.status === 2 // 已处理
282
- })
283
-
284
- return {
285
- isBranchNode: true, // 当前节点是分支流程中的节点
286
- isLastBranch: allOtherBranchesCompleted, // 只有当其他所有分支都已完成时,当前分支才是最后一个
287
- }
288
- })
289
-
290
- // 实际展示的按钮
291
- const displayedButtons = computed(() => {
292
- if (allButtons.value.length <= 3) {
293
- return allButtons.value
294
- }
295
-
296
- // 当按钮超过3个时,显示前2个和一个"更多"按钮
297
- const buttons = [...allButtons.value.slice(0, 2)]
298
- buttons.push({
299
- text: '更多',
300
- isMoreButton: true,
301
- action: 'showMore',
302
- })
303
- return buttons
304
- })
305
-
306
- // 扩展按钮(从第3个开始)
307
- const actionSheetActions = computed(() => {
308
- if (allButtons.value.length <= 3)
309
- return []
310
- return allButtons.value.slice(2).map((button) => {
311
- if (button.show === true) {
312
- return {
313
- name: button.text,
314
- action: button.action,
315
- func: button.func,
316
- }
317
- }
318
- else {
319
- return undefined
320
- }
321
- }).filter(item => item !== undefined)
322
- })
323
-
324
- // 初始化组件
325
- async function initComponent() {
326
- isLoading.value = true;
327
- // 设置标题
328
- (window as any).microApp?.dispatch({ title: route.query.title })
329
- // 获取流程定义集合和当前流程信息
330
- const defineData: any = await runLogic('getStepNoteAndHandler', {
331
- workflowId,
332
- }, serviceName.value)
333
- const stepIdValue = Number.parseInt(stepId as string)
334
- stepDefines.value = defineData
335
- // 当前节点状态信息
336
- const currentStepState: any = await runLogic('getWorkFlowCurrentSubState', {
337
- workflowId,
338
- }, serviceName.value)
339
- // 当前节点定义信息
340
- const currentStepDefine: any = defineData.filter((item: any) => item.id === stepIdValue)[0]
341
- const currentStepData: any = {}
342
- Object.assign(currentStepData, currentStepDefine)
343
- Object.assign(currentStepData, currentStepState)
344
- currentStep.value = currentStepData
345
- mobileToolNames.value = currentStep.value.properties?.otherProperty?.mobileToolsRoutes?.map(d => d.name).join('、') ?? ''
346
- if (!currentStep.value) {
347
- errorMsg.value = '工作流步骤不存在'
348
- errorImage.value = 'default'
349
- isError.value = true
350
- return
351
- }
352
- if (!canSubmit.value) {
353
- errorMsg.value = '您没有访问该步骤的权限'
354
- errorImage.value = 'error'
355
- isError.value = true
356
- return
357
- }
358
- // 获取到当前步骤后复制下一步时间
359
- deadline.value = getDefaultDeadline(currentStep.value.properties?.otherProperty?.nextNodeInterval)
360
- // 获取当前步骤节点,连通的节点
361
- await getDirection()
362
- if (!currentStep.value.note) {
363
- currentStep.value.note = '无'
364
- }
365
- // 获取工单基本信息
366
- await getBaseInfo()
367
- if (currentStep.value.status !== 1 || workflowDetailData.value?.f_state === 1) {
368
- // 已完成流程获取流程表单数据
369
- const formData: any = await runLogic('getWorkFlowCompletedStepData', {
370
- workflowId,
371
- stepId,
372
- }, serviceName.value)
373
- stepFormData.value = getFormData(formData.data)
374
- stepFilesData.value = formData.files
375
- stepImageData.value = formData.images
376
- showType.value = 1
377
- }
378
- else {
379
- stepFormDefine.value = currentStep.value.properties.form
380
- addActionButtons()
381
- appendCustomizeButtons()
382
- showType.value = 0
383
- }
384
- // 当前环节配置的历史表单数据展示配置
385
- await buildHistoryFormCompletedData()
386
- isLoading.value = false
387
- isFinish.value = true
388
- }
389
- async function buildHistoryFormCompletedData() {
390
- historyFormCompletedDataPreview.value = null
391
- const otherProperty = currentStep.value?.properties?.otherProperty
392
- const historyFormDataConfig = otherProperty?.historyFormData
393
-
394
- if (!Array.isArray(historyFormDataConfig))
395
- return
396
-
397
- // ===== 预处理 =====
398
- const stepIdByName = new Map(
399
- stepDefines.value.map(step => [step.name, step.id]),
400
- )
401
-
402
- const stepFormJsonByStepId = new Map(
403
- stepDefines.value.map(step => [
404
- step.id,
405
- step?.properties?.form?.formJson,
406
- ]),
407
- )
408
-
409
- // ===== 收集需要查询的 stepId =====
410
- const stepIdSet = new Set()
411
-
412
- for (const historyFormConfig of historyFormDataConfig) {
413
- const targetStepId = stepIdByName.get(historyFormConfig.stepName)
414
- if (targetStepId != null) {
415
- stepIdSet.add(targetStepId)
416
- }
417
- }
418
-
419
- const stepIdS = Array.from(stepIdSet)
420
- if (stepIdS.length === 0)
421
- return
422
-
423
- // ===== 请求所有步骤数据 =====
424
- const allStepData = await runLogic('geWorkFlowCompletedStepDataBySteps', {
425
- workflowId,
426
- stepIdS,
427
- }, serviceName.value)
428
-
429
- // ===== 组装数据 =====
430
- const historyFormData = []
431
- const stepAttachmentsMap = new Map()
432
-
433
- for (const historyFormConfig of historyFormDataConfig) {
434
- const stepId = stepIdByName.get(historyFormConfig.stepName)
435
- if (stepId == null)
436
- continue
437
-
438
- const targetStepId = Number.parseInt(stepId)
439
-
440
- const stepFormJson = stepFormJsonByStepId.get(targetStepId)
441
- if (!Array.isArray(stepFormJson))
442
- continue
443
-
444
- const stepDataRaw
445
- = allStepData?.[targetStepId]
446
- ?? allStepData?.[String(targetStepId)]
447
-
448
- if (!stepDataRaw)
449
- continue
450
-
451
- const isAttachment
452
- = historyFormConfig.displayLabel === '附件'
453
- && historyFormConfig.formLabel === '附件'
454
-
455
- // ===== 附件 =====
456
- if (isAttachment) {
457
- if (!stepAttachmentsMap.has(targetStepId)) {
458
- stepAttachmentsMap.set(targetStepId, {
459
- stepId: targetStepId,
460
- stepName: historyFormConfig.stepName,
461
- data: [],
462
- images: [],
463
- files: [],
464
- })
465
- }
466
-
467
- const attachment = stepAttachmentsMap.get(targetStepId)
468
- attachment.images = Array.isArray(stepDataRaw.images)
469
- ? stepDataRaw.images
470
- : []
471
- attachment.files = Array.isArray(stepDataRaw.files)
472
- ? stepDataRaw.files
473
- : []
474
-
475
- continue
476
- }
477
-
478
- // ===== 普通字段 =====
479
- const model = historyFormConfig.formLabel
480
-
481
- const value = stepDataRaw.data?.[model]
482
- if (value === undefined || value === null)
483
- continue
484
-
485
- historyFormData.push({
486
- label: historyFormConfig.displayLabel,
487
- value,
488
- })
489
- }
490
-
491
- // ===== 汇总结果 =====
492
- historyFormCompletedDataPreview.value = {
493
- data: historyFormData,
494
- stepAttachments: Array.from(stepAttachmentsMap.values()),
495
- }
496
- }
497
-
498
- // 获取默认截止时间
499
- function getDefaultDeadline(day = 1) {
500
- const date = new Date()
501
- date.setDate(date.getDate() + day)
502
- date.setHours(date.getHours() + 2)
503
- return formatDate(date, 'YYYY-MM-DD HH:mm')
504
- }
505
-
506
- // 获取表单数据
507
- function getFormData(stepFormData: any) {
508
- const formDataArr = []
509
- const formConfig = currentStep.value?.properties?.form
510
- const isKeyHandle = formConfig?.isKeyHandle || false
511
- const formJson = formConfig?.formJson || []
512
-
513
- for (const stepDefine of formJson) {
514
- // 如果是特殊函数展示的字段,跳过
515
- if (stepDefine.showFormItemFunc)
516
- continue
517
-
518
- const key = getRealKey(stepDefine.model, isKeyHandle)
519
- if (!(key in stepFormData) || ['FilesId', 'Images'].includes(key) || ['image', 'signature'].includes(stepDefine.type))
520
- continue
521
-
522
- const value = stepFormData[key]
523
- const item = { label: stepDefine.name, value }
524
-
525
- // 如果是需要字典转换的 select
526
- if (stepDefine.type === 'select' && stepDefine.keyName?.includes('config@')) {
527
- const dictKey = stepDefine.keyName.split('@')[1]
528
- convertDictValue(dictKey, value, (label) => {
529
- item.value = label
530
- })
531
- }
532
-
533
- formDataArr.push(item)
534
- }
535
-
536
- return formDataArr.length > 0 ? formDataArr : null
537
- }
538
-
539
- function getRealKey(key: string, isHandleFormKey: boolean) {
540
- if (!key)
541
- return ''
542
- if (key === 'selected_id')
543
- return key
544
- return isHandleFormKey ? key.split('_').slice(1).join('_') : key
545
- }
546
-
547
- // 获取当前步骤节点,连通的节点
548
- async function getDirection() {
549
- // 获取流程定义
550
- const res: any = await runLogic('getWorkFlowDefine', {
551
- id: workflowId,
552
- }, serviceName.value)
553
- directions.value = []
554
- stepMetaDefines.value = res.steps
555
- await resolveDirections()
556
- appendToolsViewOptions()
557
- }
558
-
559
- // 追加自定义按钮
560
- function appendCustomizeButtons() {
561
- // 获取当前步骤的按钮组配置
562
- if (currentStep.value.properties?.buttonGroup?.actionArr) {
563
- for (const item of currentStep.value.properties.buttonGroup.actionArr) {
564
- allButtons.value.push({
565
- text: item.text,
566
- isMoreButton: false,
567
- action: 'other',
568
- func: item.func,
569
- show: checkButtonVisible(item),
570
- })
571
- }
572
- }
573
- }
574
-
575
- // 追加浮动按钮的工具选项
576
- function appendToolsViewOptions() {
577
- // 获取当前步骤的浮动按钮工具集配置
578
- toolActions.value = []
579
- if (currentStep.value.properties?.otherProperty?.mobileToolsRoutes) {
580
- for (const item of currentStep.value.properties.otherProperty.mobileToolsRoutes) {
581
- toolActions.value.push({
582
- name: item.name,
583
- value: item.value,
584
- type: 'tools',
585
- func: item.func,
586
- })
587
- }
588
- }
589
- toolActions.value.push({
590
- name: '流程查看',
591
- value: 'applyDetail',
592
- type: 'applyDetail',
593
- })
594
- toolActions.value.push({
595
- name: '返回首页',
596
- value: 'home',
597
- type: 'backHome',
598
- })
599
- }
600
-
601
- // 浮动按钮点击操作
602
- function handleToolsPopoverClick() {
603
- showToolsMenu.value = true
604
- }
605
-
606
- // 分析当前节点,能通向的节点
607
- async function resolveDirections() {
608
- if (currentStep.value?.properties?.actions) {
609
- currentDirections.value = currentStep.value.properties.actions
610
- }
611
- // 判断是否是最后ige节点
612
- isLastStep.value = (currentStep.value?.properties?.actions || []).filter((item) => {
613
- return item.type !== 'back'
614
- }).length === 0
615
-
616
- // 处理跳转按钮
617
- workflowControl()
618
-
619
- if (!isLastStep.value) {
620
- // 分析分支节点并设置智能人员选择
621
- await analyzeBranchNodes()
622
- }
623
- }
624
-
625
- // 重置分支选择状态
626
- function resetBranchSelection() {
627
- branchNodes.value = []
628
- branchChargePersons.value = {}
629
- needMultipleBranchSelection.value = false
630
- calculatedTargetNode.value = undefined
631
- currentBranchActions.value = undefined
632
- }
633
-
634
- // 分析分支节点并设置选择框
635
- async function analyzeBranchNodes() {
636
- try {
637
- // 初始化状态
638
- resetBranchSelection()
639
-
640
- // 获取所有类型的分支动作并缓存
641
- currentBranchActions.value = getBranchActions()
642
-
643
- // 如果没有任何分支,处理普通流程
644
- if (!currentBranchActions.value.hasAnyBranch) {
645
- await handleNormalFlow()
646
- return
647
- }
648
-
649
- // 判断是否需要多分支选择
650
- needMultipleBranchSelection.value = shouldUseMultipleBranchSelection(currentBranchActions.value)
651
-
652
- if (needMultipleBranchSelection.value) {
653
- // 需要为多个分支预先选择人员
654
- await setupMultipleBranchSelection(currentBranchActions.value.allBranchActions)
655
- }
656
- else {
657
- // 可以前台实时计算的条件分支
658
- await calculateTargetNodeFromForm(currentBranchActions.value.conditionalActions)
659
- }
660
- }
661
- catch (error) {
662
- console.error('分析分支节点失败:', error)
663
- // 降级处理:使用普通流程
664
- await handleNormalFlow()
665
- }
666
- }
667
-
668
- // 设置多分支人员选择
669
- async function setupMultipleBranchSelection(branchActions) {
670
- branchNodes.value = []
671
- branchChargePersons.value = {}
672
- let hasPersonSelection = false
673
-
674
- for (const action of branchActions) {
675
- const stepDefine = stepMetaDefines.value.find(step => step.id === action.to)
676
- const stepPerson = stepDefine?.properties?.chargePerson
677
-
678
- // 所有分支节点都加入branchNodes,保持数据结构完整
679
- const nodeData = {
680
- stepId: action.to,
681
- stepName: getStepNameByStepId(action.to),
682
- chargePerson: stepPerson,
683
- needSelectPerson: stepPerson?.needSelectPerson || false,
684
- chargePersonOptions: [],
685
- }
686
-
687
- if (stepPerson?.needSelectPerson) {
688
- // 需要选择人员的分支
689
- nodeData.chargePersonOptions = await getStepPersonOptionsForStep(action.to)
690
-
691
- // 初始化选择值
692
- branchChargePersons.value[action.to] = undefined
693
- hasPersonSelection = true
694
- }
695
- else {
696
- // 不需要选择人员的分支,直接存储配置
697
- branchChargePersons.value[action.to] = stepPerson
698
- }
699
-
700
- branchNodes.value.push(nodeData)
701
- }
702
-
703
- // 只有当存在需要选择人员的分支时才显示选择区域
704
- isNeedSelectPerson.value = hasPersonSelection
705
- }
706
-
707
- // 根据表单数据计算目标节点
708
- async function calculateTargetNodeFromForm(conditionalActions = null) {
709
- if (!conditionalActions) {
710
- conditionalActions = currentDirections.value.filter(action => action.type === 'conditionalBranch')
711
- }
712
-
713
- if (conditionalActions.length === 0) {
714
- // 如果当前节点不是在分支流程中,才清空人员选择
715
- // 避免在并行分支节点中误清除人员选择框
716
- const isInBranch = currentStep.value?.properties?.branchPath
717
- if (!isInBranch) {
718
- clearPersonSelection()
719
- }
720
- return
721
- }
722
-
723
- try {
724
- const formData = xAddForm.value.form || {}
725
-
726
- // 遍历条件找到匹配的
727
- for (const action of conditionalActions) {
728
- if (action.expression) {
729
- const result = await evaluateExpression(action.expression, formData)
730
- if (result) {
731
- calculatedTargetNode.value = action.to
732
- await setupSingleTargetPersonSelection(action.to)
733
- return
734
- }
735
- }
736
- }
737
- // 如果没有匹配的条件,清空选择
738
- clearPersonSelection()
739
- }
740
- catch (error) {
741
- console.warn('计算目标节点失败:', error)
742
- clearPersonSelection()
743
- }
744
- }
745
-
746
- // 表达式评估 - 使用 LogicRunner
747
- async function evaluateExpression(expression, formData) {
748
- try {
749
- // 构建参数对象,传递当前表单数据
750
- const params = {
751
- WF_FORM: { ...formData },
752
- }
753
-
754
- // 使用 LogicRunner 执行表达式
755
- return await LogicRunner.runExpression(expression, new JSONObject(params))
756
- }
757
- catch (error) {
758
- console.warn('表达式评估失败:', expression, error)
759
- return false
760
- }
761
- }
762
-
763
- // 判断是否应该使用多分支选择模式
764
- function shouldUseMultipleBranchSelection(branchActions) {
765
- // 情况1: 有并行分支
766
- if (branchActions.parallelActions.length > 0) {
767
- return true
768
- }
769
-
770
- // 情况2: 条件分支包含WF_RESULT(后台结果决定)
771
- if (branchActions.conditionalActions.length > 0) {
772
- const conditionalActions = currentDirections.value.filter(action => action.type === 'conditionalBranch')
773
- if (conditionalActions.length === 0)
774
- return false
775
-
776
- return conditionalActions.some((action) => {
777
- return action.expression && action.expression.includes('WF_RESULT')
778
- })
779
- }
780
-
781
- return false
782
- }
783
-
784
- // 处理普通单线流程(没有分支的情况)
785
- async function handleNormalFlow() {
786
- const targetStepId = operationType.value === 'skip' ? skipStepBtnToObj.value : nextStepBtnToObj.value
787
-
788
- calculatedTargetNode.value = targetStepId
789
- const stepDefine = stepMetaDefines.value.find(step => step.id === targetStepId)
790
-
791
- if (!stepDefine) {
792
- console.warn('未找到目标步骤定义:', targetStepId)
793
- clearPersonSelection()
794
- return
795
- }
796
-
797
- const defineProperties = stepDefine.properties
798
- nextStepPersonInfo.value = defineProperties?.chargePerson
799
-
800
- // 兼容旧格式
801
- if (defineProperties.chargePerson) {
802
- defineProperties.chargePerson = normalizeChargePersonFormat(defineProperties.chargePerson)
803
- }
804
-
805
- if (defineProperties?.chargePerson?.needSelectPerson) {
806
- await setupSingleTargetPersonSelection(targetStepId)
807
- // 确保 branchChargePersons 中有该节点的数据结构
808
- branchChargePersons.value[targetStepId] = {
809
- handler: '',
810
- handlerId: undefined,
811
- ...defineProperties.chargePerson,
812
- }
813
- }
814
- else {
815
- // 不需要选择人员的情况,直接存储配置
816
- branchChargePersons.value[targetStepId] = defineProperties?.chargePerson || {}
817
- isNeedSelectPerson.value = false
818
- nextStepPersonOptions.value = []
819
- }
820
- }
821
-
822
- // 设置单个目标节点的人员选择
823
- async function setupSingleTargetPersonSelection(stepId) {
824
- const stepDefine = stepMetaDefines.value.find(step => step.id === stepId)
825
-
826
- if (!stepDefine?.properties?.chargePerson?.needSelectPerson) {
827
- isNeedSelectPerson.value = false
828
- nextStepPersonOptions.value = []
829
- return
830
- }
831
-
832
- // 设置人员选择
833
- nextStepPersonInfo.value = stepDefine.properties.chargePerson
834
- checkedNextStepPerson.value = undefined
835
- checkedNextStepPersonName.value = undefined
836
- nextStepPersonOptions.value = await getStepPersonOptionsForStep(stepId)
837
- // 如果只有一个选项,自动选中
838
- if (nextStepPersonOptions.value.length === 1) {
839
- checkedNextStepPerson.value = nextStepPersonOptions.value[0].value
840
- checkedNextStepPersonName.value = nextStepPersonOptions.value[0].label
841
- }
842
-
843
- isNeedSelectPerson.value = true
844
- }
845
-
846
- // 获取下一步操作人的人员列表
847
- async function getStepPersonOptionsForStep(stepId) {
848
- const define = stepMetaDefines.value.find(item => item.id === stepId)
849
- if (!define?.properties?.chargePerson)
850
- return []
851
-
852
- const stepPerson = define.properties.chargePerson
853
- nextStepPersonInfo.value = normalizeChargePersonFormat(stepPerson)
854
-
855
- if (!stepPerson.needSelectPerson || !stepPerson.personList)
856
- return []
857
-
858
- // 获取所有用户信息
859
- const allUser = await runLogic('getAllUserOptionList', {}, 'af-system')
860
-
861
- // 根据配置筛选用户
862
- const options = filterUsersByPersonConfig(stepPerson.personList, allUser)
863
-
864
- // 去重处理
865
- return Array.from(new Map(options.map(item => [item.value, item])).values())
866
- }
867
-
868
- // 根据人员配置筛选用户
869
- function filterUsersByPersonConfig(personList, allUsers) {
870
- return personList.reduce((acc, personItem) => {
871
- let filteredUsers = []
872
-
873
- if (personItem.type === 'role') {
874
- filteredUsers = allUsers.filter(user =>
875
- user.rolestr && user.rolestr.split(',').includes(personItem.name),
876
- ).map(user => ({
877
- label: user.label,
878
- value: user.value,
879
- }))
880
- }
881
- else if (personItem.type === 'department') {
882
- filteredUsers = allUsers.filter(user =>
883
- user.depname === personItem.name,
884
- ).map(user => ({
885
- label: user.label,
886
- value: user.value,
887
- }))
888
- }
889
-
890
- return [...acc, ...filteredUsers]
891
- }, [])
892
- }
893
-
894
- // 标准化人员配置格式(兼容旧格式)
895
- function normalizeChargePersonFormat(chargePerson) {
896
- if (chargePerson.role || chargePerson.department) {
897
- chargePerson.needSelectPerson = true
898
- chargePerson.personList = [{
899
- type: chargePerson.role ? 'role' : 'department',
900
- name: chargePerson.role || chargePerson.department,
901
- }]
902
- }
903
- return chargePerson
904
- }
905
-
906
- // 清空人员选择
907
- function clearPersonSelection() {
908
- calculatedTargetNode.value = undefined
909
- isNeedSelectPerson.value = false
910
- nextStepPersonOptions.value = []
911
- checkedNextStepPerson.value = undefined
912
- checkedNextStepPersonName.value = undefined
913
- }
914
-
915
- // 获取分支动作分类
916
- function getBranchActions() {
917
- const conditionalActions = currentDirections.value.filter(action => action.type === 'conditionalBranch')
918
- const parallelActions = currentDirections.value.filter(action => action.type === 'parallelBranch')
919
- const allBranchActions = [...conditionalActions, ...parallelActions]
920
-
921
- return {
922
- conditionalActions,
923
- parallelActions,
924
- allBranchActions,
925
- hasAnyBranch: allBranchActions.length > 0,
926
- }
927
- }
928
-
929
- // 根据步骤id获取步骤名称
930
- function getStepNameByStepId(stepId) {
931
- return stepMetaDefines.value.find(item => item.id === stepId)?.name
932
- }
933
-
934
- // 根据当前节点,判断之后流程,以及按钮的显示
935
- function workflowControl() {
936
- // 重置所有控制数据为默认值,避免之前状态的影响
937
- isShowNextStepBtn.value = false
938
- isShowPrevStepBtn.value = false
939
- isShowSkipStepBtn.value = false
940
- nextStepBtnToObj.value = undefined
941
- prevStepBtnToObj.value = undefined
942
- skipStepBtnToObj.value = undefined
943
- nextBtnPrompt.value = ''
944
- prevBtnPrompt.value = ''
945
- skipBtnPrompt.value = ''
946
-
947
- // 分类收集不同类型的actions
948
- const submitActions = currentDirections.value.filter(item => item.type === 'submit')
949
- const conditionalActions = currentDirections.value.filter(item => item.type === 'conditionalBranch')
950
- const parallelActions = currentDirections.value.filter(item => item.type === 'parallelBranch')
951
- const skipActions = currentDirections.value.filter(item => item.type === 'skip')
952
- const backActions = currentDirections.value.filter(item => item.type === 'back')
953
-
954
- // 处理提交按钮 - 优先级:submit > conditionalBranch > parallelBranch
955
- if (submitActions.length > 0) {
956
- isShowNextStepBtn.value = true
957
- nextStepBtnToObj.value = submitActions[0].to // 如果有多个submit,取第一个作为主要跳转目标
958
- if (submitActions.length === 1) {
959
- nextBtnPrompt.value = `进行下一环节:${getStepNameByStepId(submitActions[0].to)}`
960
- }
961
- else {
962
- nextBtnPrompt.value = `将进入以下环节:${submitActions.map(item => getStepNameByStepId(item.to)).join(' , ')}`
963
- }
964
- }
965
- else if (conditionalActions.length > 0) {
966
- isShowNextStepBtn.value = true
967
- nextStepBtnToObj.value = conditionalActions[0].to // 条件分支的第一个作为主要跳转目标
968
- const targetSteps = conditionalActions.map(item => getStepNameByStepId(item.to)).join(' , ')
969
- nextBtnPrompt.value = `后台判断后跳转到:${targetSteps}`
970
- }
971
- else if (parallelActions.length > 0) {
972
- isShowNextStepBtn.value = true
973
- nextStepBtnToObj.value = parallelActions[0].to // 并行分支的第一个作为主要跳转目标
974
- const targetSteps = parallelActions.map(item => getStepNameByStepId(item.to)).join(' , ')
975
- nextBtnPrompt.value = `将并行进入以下分支:${targetSteps}`
976
- }
977
-
978
- // 处理跳过按钮
979
- if (skipActions.length > 0) {
980
- isShowSkipStepBtn.value = true
981
- skipStepBtnToObj.value = skipActions[0].to
982
- if (skipActions.length === 1) {
983
- skipBtnPrompt.value = `跳至${getStepNameByStepId(skipActions[0].to)}`
984
- }
985
- else {
986
- const targetSteps = skipActions.map(item => getStepNameByStepId(item.to)).join(' , ')
987
- skipBtnPrompt.value = `跳至${targetSteps}`
988
- }
989
- }
990
-
991
- // 处理退回按钮
992
- if (backActions.length > 0) {
993
- const backAction = backActions[0] // 通常只有一个back action
994
- for (let i = currentStep.value.id - 1; i > 0; i--) {
995
- if (backAction.to >= i && stepDefines.value.find(item => item.id === i)?.handler) {
996
- prevStepBtnToObj.value = i
997
- prevBtnPrompt.value = `退至${getStepNameByStepId(i)}`
998
- isShowPrevStepBtn.value = true
999
- break
1000
- }
1001
- }
1002
- }
1003
- }
1004
-
1005
- // 清理数据
1006
- function clearData() {
1007
- checkedNextStepPerson.value = undefined
1008
- checkedNextStepPersonName.value = undefined
1009
- directions.value = []
1010
- currentDirections.value = []
1011
- isShowNextStepBtn.value = false
1012
- isShowSkipStepBtn.value = false
1013
- isShowPrevStepBtn.value = false
1014
- stepDone.value = false
1015
- formCompletedData.value = {}
1016
- formCompletedDataPreview.value = undefined
1017
- operationType.value = 'submit'
1018
-
1019
- // 清理分支节点相关数据
1020
- branchNodes.value = []
1021
- branchChargePersons.value = {}
1022
- needMultipleBranchSelection.value = false
1023
- calculatedTargetNode.value = undefined
1024
- currentBranchActions.value = undefined
1025
-
1026
- // 清理操作按钮
1027
- allButtons.value = []
1028
- }
1029
-
1030
- // 分离出字典转换逻辑
1031
- function convertDictValue(dictKey: string, value: string, callback: Function) {
1032
- getDictItemByValue(dictKey, serviceName.value, value, (dictItem: any) => {
1033
- if (dictItem) {
1034
- callback(dictItem.label)
1035
- }
1036
- })
1037
- }
1038
-
1039
- // 获取工单基础信息
1040
- async function getBaseInfo() {
1041
- const res: any = await runLogic('getWorkFlowBasicInfo', {
1042
- workflowId,
1043
- }, serviceName.value)
1044
- // 主动传入得优先级较高
1045
- if (currentStep.value.id) {
1046
- res.f_sub_state = stepDefines.value.find(item => item.id === currentStep.value.id)?.name
1047
- }
1048
- else {
1049
- res.f_sub_state = stepDefines.value.find(item => item.id === res.f_step_id)?.name
1050
- }
1051
- workflowDetailData.value = res
1052
- }
1053
-
1054
- // 检查按钮是否显示
1055
- function checkButtonVisible(button) {
1056
- try {
1057
- if (button.customFunction) {
1058
- getBaseInfo()
1059
- return executeStrFunctionByContext(getCurrentInstance(), button.customFunction, [workflowDetailData.value, xAddForm.value, util, runLogic, getConfigByName])
1060
- }
1061
- return true
1062
- }
1063
- catch (error) {
1064
- console.error('执行按钮显示函数失败:', error)
1065
- return false
1066
- }
1067
- }
1068
-
1069
- // 处理动态按钮点击
1070
- function handleCustomButtonClick(button) {
1071
- try {
1072
- // 执行自定义函数
1073
- if (button.func) {
1074
- getBaseInfo()
1075
- const result = executeStrFunctionByContext(getCurrentInstance(), button.func, [workflowDetailData.value, xAddForm.value, util, runLogic, getConfigByName, 'mobile', userStore.getUserInfo(), showToast, showConfirmDialog])
1076
- if (result) {
1077
- // 如果返回true,执行对应的操作
1078
- if (button.text === '提交') {
1079
- handleButtonClick({
1080
- action: 'submit',
1081
- })
1082
- }
1083
- }
1084
- }
1085
- }
1086
- catch (error) {
1087
- console.error('执行自定义按钮函数失败:', error)
1088
- showToast('执行操作失败')
1089
- }
1090
- }
1091
-
1092
- // 表单提交的回调
1093
- async function submitForm(obj) {
1094
- const formData = obj.realForm
1095
- const time = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
1096
- return runLogic('saveWorkFlowStepFormData', {
1097
- workflowId,
1098
- stepId: currentStep.value.id,
1099
- form: formData,
1100
- data: time,
1101
- handler: userStore.getUserInfo().name,
1102
- note: noteContent.value.trim(),
1103
- }, serviceName.value).then(() => {
1104
- noteContent.value = ''
1105
- showForm.value = false
1106
- stepDone.value = true
1107
- })
1108
- }
1109
-
1110
- // 保存工作流日志
1111
- function saveWorkflowLog(operation, desc, extra) {
1112
- runLogic('saveWorkFlowLog', {
1113
- workflowId,
1114
- operation,
1115
- desc,
1116
- operator: userStore.getUserInfo().name,
1117
- notes: '',
1118
- setHandler: '',
1119
- setDeadline: '',
1120
- ...extra,
1121
- }, serviceName.value)
1122
- }
1123
-
1124
- // 业务提交统一处理
1125
- async function handleButtonClick(button: any) {
1126
- if (button.action === 'showMore') {
1127
- showMoreMenu.value = true
1128
- return
1129
- }
1130
-
1131
- if ((button.action === 'submit' || button.action === 'skip' || button.action === 'done')) {
1132
- await xAddForm.value.validate()
1133
- }
1134
-
1135
- if (button.action === 'submit' || button.action === 'skip' || button.action === 'back') {
1136
- operationType.value = button.action
1137
- // 重新分析分支节点
1138
- checkedNextStepPerson.value = undefined
1139
- checkedNextStepPersonName.value = undefined
1140
- branchChargePersons.value = {}
1141
- await analyzeBranchNodes()
1142
- }
1143
-
1144
- // 根据按钮类型执行不同操作
1145
- switch (button.action) {
1146
- case 'submit': {
1147
- dialogTitle.value = '提交环节'
1148
- dialogVisible.value = true
1149
- break
1150
- }
1151
- case 'skip': {
1152
- dialogTitle.value = '跳过环节'
1153
- dialogVisible.value = true
1154
- break
1155
- }
1156
- case 'done': {
1157
- await doneClick()
1158
- break
1159
- }
1160
- case 'back': {
1161
- dialogTitle.value = `${prevBtnPrompt.value}环节`
1162
- dialogVisible.value = true
1163
- break
1164
- }
1165
- case 'other': {
1166
- handleCustomButtonClick(button)
1167
- break
1168
- }
1169
- }
1170
- }
1171
-
1172
- // 完工按钮
1173
- async function doneClick() {
1174
- xAddForm.value.asyncSubmit().then((res) => {
1175
- submitForm(res).then((_) => {
1176
- runLogic('afterWorkFlowFinalStepSubmit', {
1177
- workflowId,
1178
- stepId: currentStep.value.id,
1179
- submitUser: userStore.getUserInfo().name,
1180
- submitUserId: userStore.getUserInfo().id,
1181
- }, serviceName.value).then((_res) => {
1182
- saveWorkflowLog('确认完工', `最后一步: ${getStepNameByStepId(currentStep.value.id)}`, { notes: noteContent.value.trim() })
1183
- showToast('已完工')
1184
- clearData()
1185
- back()
1186
- }, () => {
1187
- showToast('提交失败!')
1188
- })
1189
- })
1190
- }).catch((err) => {
1191
- if (err && err.message === 'Form validation failed') {
1192
- showToast('请检查表单必填项!')
1193
- }
1194
- else {
1195
- // 对于其他错误,继续向上抛出
1196
- throw err
1197
- }
1198
- })
1199
- }
1200
-
1201
- function back() {
1202
- history.back()
1203
- }
1204
-
1205
- // 获取提交步骤的额外数据
1206
- function getApplyStepExtraData(stepId) {
1207
- // 验证人员选择 - 只有在需要显示人员选择器的情况下才验证
1208
- // 判断条件与模板中的 WorkflowPersonSelector 显示条件保持一致
1209
- const shouldShowPersonSelector = !isBranchLastNode.value.isBranchNode || isBranchLastNode.value.isLastBranch
1210
-
1211
- if (shouldShowPersonSelector && isNeedSelectPerson.value) {
1212
- if (needMultipleBranchSelection.value) {
1213
- // 多分支情况:检查是否所有需要的分支都选择了人员
1214
- const missingSelections = branchNodes.value.filter(node => !branchChargePersons.value[node.stepId])
1215
- if (missingSelections.length > 0) {
1216
- const missingNames = missingSelections.map(node => node.stepName).join('、')
1217
- showToast(`请设置以下节点的处理人:${missingNames}`)
1218
- return false
1219
- }
1220
- }
1221
- else {
1222
- // 单分支情况:检查是否选择了处理人
1223
- if (!checkedNextStepPerson.value) {
1224
- showToast('请设置下一环节处理人')
1225
- return false
1226
- }
1227
- }
1228
- }
1229
-
1230
- // 统一获取目标步骤IDs
1231
- let stepIds: any[]
1232
- if (needMultipleBranchSelection.value) {
1233
- // 多分支选择:获取所有分支的目标stepId(包括不需要选择人员的)
1234
- if (!currentBranchActions.value) {
1235
- currentBranchActions.value = getBranchActions()
1236
- }
1237
- stepIds = currentBranchActions.value.allBranchActions.map(action => action.to)
1238
- }
1239
- else if (calculatedTargetNode.value) {
1240
- // 条件判断节点:使用计算出的目标节点
1241
- stepIds = [calculatedTargetNode.value]
1242
- }
1243
- else {
1244
- // 普通节点:使用传入的stepId
1245
- stepIds = [stepId]
1246
- }
1247
- // 处理bug 有的时候没带到 branchChargePersons 先循环在同一地方处理
1248
- stepIds.forEach((stepItemId) => {
1249
- if (!branchChargePersons.value[stepItemId]) {
1250
- const chargePerson = stepDefines.value.find(item => item.id === stepItemId)?.properties?.chargePerson
1251
- branchChargePersons.value[stepItemId] = normalizeChargePersonFormat(chargePerson)
1252
- }
1253
- })
1254
-
1255
- return {
1256
- form: xAddForm.value.getFormData(),
1257
- workflowId,
1258
- activeStepId: currentStep.value.id,
1259
- stepId, // 当前步骤ID
1260
- stepIds, // 目标步骤IDs数组(统一格式)
1261
- name: getStepNameByStepId(stepId),
1262
- handler: getStepHandler(),
1263
- handlerId: checkedNextStepPerson.value,
1264
- needSelectPerson: isNeedSelectPerson.value,
1265
- personList: nextStepPersonInfo.value?.personList || [],
1266
- deadline: deadline.value,
1267
- submitUser: userStore.getUserInfo().name,
1268
- submitUserId: userStore.getUserInfo().id,
1269
- branchChargePersons: branchChargePersons.value,
1270
- }
1271
- }
1272
-
1273
- // 获取当前选择的负责人
1274
- function getStepHandler() {
1275
- let stepHandler
1276
-
1277
- if (needMultipleBranchSelection.value) {
1278
- // 多分支情况:返回所有分支的人员信息
1279
- const selectedHandlers = []
1280
- for (const node of branchNodes.value) {
1281
- const branchData = branchChargePersons.value[node.stepId]
1282
-
1283
- if (node.needSelectPerson && branchData) {
1284
- // 需要选择人员的分支:显示选中的人员
1285
- const personName = getBranchPersonName(node.stepId)
1286
- selectedHandlers.push(`${node.stepName}:${personName}`)
1287
- }
1288
- else if (!node.needSelectPerson && branchData) {
1289
- // 不需要选择人员的分支:显示角色/部门配置
1290
- if (branchData.personList && branchData.personList.length > 0) {
1291
- const personNames = branchData.personList.map(item => item.name).join(',')
1292
- selectedHandlers.push(`${node.stepName}:${personNames}`)
1293
- }
1294
- }
1295
- }
1296
- stepHandler = selectedHandlers.join(';')
1297
- }
1298
- else {
1299
- // 单分支情况:原有逻辑
1300
- if (checkedNextStepPerson.value) {
1301
- // 使用 value 找到对应的 label
1302
- stepHandler = nextStepPersonOptions.value.find(item => item.value === checkedNextStepPerson.value)?.label
1303
- }
1304
- else if (nextStepPersonInfo.value && nextStepPersonInfo.value.personList && nextStepPersonInfo.value.personList.length > 0) {
1305
- stepHandler = nextStepPersonInfo.value.personList.map(item => item.name).join(',')
1306
- }
1307
- }
1308
-
1309
- return stepHandler
1310
- }
1311
-
1312
- // 获取分支节点选择的人员姓名
1313
- function getBranchPersonName(stepId) {
1314
- const personId = branchChargePersons.value[stepId]
1315
- if (!personId)
1316
- return ''
1317
-
1318
- const node = branchNodes.value.find(n => n.stepId === stepId)
1319
- if (!node)
1320
- return ''
1321
-
1322
- const person = node.chargePersonOptions.find(p => p.value === personId)
1323
- return person ? person.label : ''
1324
- }
1325
-
1326
- // 生成工作流日志步骤变化描述
1327
- function generateStepChangeText(fromStepId, toStepId) {
1328
- // 如果是多分支场景,toStepId可能是数组或字符串
1329
- if (needMultipleBranchSelection.value && Array.isArray(toStepId)) {
1330
- const targetSteps = toStepId.map(id => `第${id}步: ${getStepNameByStepId(id)}`).join('、')
1331
- return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> ${targetSteps}`
1332
- }
1333
- else if (needMultipleBranchSelection.value && typeof toStepId === 'string') {
1334
- // 如果传入的是目标步骤名称字符串
1335
- return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> ${toStepId}`
1336
- }
1337
- else {
1338
- // 单步骤场景
1339
- return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> 第${toStepId}步: ${getStepNameByStepId(toStepId)}`
1340
- }
1341
- }
1342
-
1343
- // 生成多分支场景的步骤变化描述
1344
- function generateBranchStepChangeText(fromStepId) {
1345
- if (!needMultipleBranchSelection.value) {
1346
- return generateStepChangeText(fromStepId, nextStepBtnToObj.value)
1347
- }
1348
-
1349
- // 使用所有分支节点生成描述
1350
- const targetSteps = branchNodes.value.map(node => `第${node.stepId}步: ${node.stepName}`).join('、')
1351
- return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> ${targetSteps}`
1352
- }
1353
-
1354
- function showConfirmDialogPromise(title: string, message: string): Promise<void> {
1355
- if (mobileToolNames.value.length > 0) {
1356
- const mobileToolMessage = `注意:当前环节工具箱中支持添加【${mobileToolNames.value}】`
1357
- message = `${message}<br><span style=\"color:#1677ff;\">${mobileToolMessage}</span>`
1358
- }
1359
- return new Promise((resolve, reject) => {
1360
- showConfirmDialog({
1361
- title,
1362
- message,
1363
- allowHtml: true,
1364
- })
1365
- .then(() => resolve())
1366
- .catch(() => {
1367
- closeDialog()
1368
- // eslint-disable-next-line prefer-promise-reject-errors
1369
- reject()
1370
- })
1371
- })
1372
- }
1373
-
1374
- async function dialogClick() {
1375
- switch (operationType.value) {
1376
- case 'back':
1377
- await backStep()
1378
- break
1379
- case 'skip':
1380
- await skipStep()
1381
- break
1382
- case 'submit':
1383
- await submitStep()
1384
- break
1385
- }
1386
- }
1387
-
1388
- async function backStep() {
1389
- const notes = backNoteContent.value.trim()
1390
- if (!notes) {
1391
- showToast('退回请在备注中填写理由')
1392
- return
1393
- }
1394
- return runLogic('updateWorkFlowState', {
1395
- stepId: prevStepBtnToObj.value,
1396
- workflowId,
1397
- type: 'back',
1398
- }, serviceName.value)
1399
- .then(
1400
- () => {
1401
- saveWorkflowLog('退回', generateStepChangeText(currentStep.value.id, prevStepBtnToObj.value), { notes })
1402
- showToast('退回成功')
1403
- clearData()
1404
- back()
1405
- },
1406
- (err) => {
1407
- showToast('退回失败!')
1408
- console.log(err)
1409
- },
1410
- )
1411
- }
1412
-
1413
- async function submitStep() {
1414
- // 检查是否为非最后分支节点
1415
- const branchStatus = isBranchLastNode.value
1416
- const isWaitingBranch = branchStatus.isBranchNode && !branchStatus.isLastBranch
1417
-
1418
- const extraData = getApplyStepExtraData(nextStepBtnToObj.value)
1419
- if (!extraData) {
1420
- return
1421
- }
1422
-
1423
- // 确认对话框
1424
- const confirmContent = isWaitingBranch
1425
- ? '确定完成当前分支么?完成后将等待其他分支,不会立即流转到下一环节。'
1426
- : '确定提交么?提交之后数据不可更改!'
1427
-
1428
- await showConfirmDialogPromise('提交确认', confirmContent)
1429
-
1430
- xAddForm.value.asyncSubmit().then((res) => {
1431
- submitForm(res).then((_) => {
1432
- extraData.form = res.realForm
1433
- runLogic('submitToNextStep', extraData, serviceName.value)
1434
- .then(
1435
- () => {
1436
- let successMessage = '提交成功'
1437
-
1438
- // 只有非等待分支节点才保存工作流日志
1439
- if (!isWaitingBranch) {
1440
- const branchStatus = isBranchLastNode.value
1441
- const operation = branchStatus.isBranchNode && branchStatus.isLastBranch ? '分支汇合' : '提交'
1442
-
1443
- const extra = {
1444
- setHandler: getStepHandler(),
1445
- setDeadline: deadline.value,
1446
- notes: noteContent.value.trim(),
1447
- }
1448
- saveWorkflowLog(operation, generateBranchStepChangeText(currentStep.value.id), extra)
1449
- }
1450
- else {
1451
- successMessage = '分支完成,等待其他分支完成后自动汇合'
1452
- }
1453
- showToast(successMessage)
1454
- clearData()
1455
- back()
1456
- },
1457
- (err) => {
1458
- showToast('提交失败!')
1459
- console.log(err)
1460
- },
1461
- )
1462
- })
1463
- }).catch((err) => {
1464
- if (err && err.message === 'Form validation failed') {
1465
- showToast('请检查表单必填项!')
1466
- }
1467
- else {
1468
- // 对于其他错误,继续向上抛出
1469
- throw err
1470
- }
1471
- })
1472
- }
1473
-
1474
- async function skipStep() {
1475
- const extraData = getApplyStepExtraData(skipStepBtnToObj.value)
1476
- if (!extraData) {
1477
- return
1478
- }
1479
- // 跳过前确认
1480
- await showConfirmDialogPromise('跳过确认', '确定跳过当前步骤吗?跳过后将进入下一环节。')
1481
- xAddForm.value.asyncSubmit().then((res) => {
1482
- submitForm(res).then((_) => {
1483
- runLogic('submitToNextStep', extraData, serviceName.value)
1484
- .then(
1485
- () => {
1486
- const extra = {
1487
- setHandler: getStepHandler(),
1488
- setDeadline: deadline.value,
1489
- notes: noteContent.value.trim(),
1490
- }
1491
- saveWorkflowLog('跳过', generateBranchStepChangeText(currentStep.value.id), extra)
1492
- showToast('提交成功')
1493
- clearData()
1494
- back()
1495
- },
1496
- (err) => {
1497
- showToast('提交失败!')
1498
- console.log(err)
1499
- },
1500
- )
1501
- })
1502
- }).catch((err) => {
1503
- if (err && err.message === 'Form validation failed') {
1504
- showToast('请检查表单必填项!')
1505
- }
1506
- else {
1507
- // 对于其他错误,继续向上抛出
1508
- throw err
1509
- }
1510
- })
1511
- }
1512
-
1513
- // 增加操作按钮组
1514
- function addActionButtons() {
1515
- allButtons.value = []
1516
- // 根据按钮类型执行不同操作
1517
- if (operationType.value === 'submit' && !isLastStep.value) {
1518
- allButtons.value.push({
1519
- text: '提交申请',
1520
- icon: 'checked',
1521
- isMoreButton: false,
1522
- action: 'submit',
1523
- type: 'primary',
1524
- class: 'submit-btn',
1525
- show: true,
1526
- })
1527
- }
1528
- if (isShowSkipStepBtn.value && !isLastStep.value) {
1529
- allButtons.value.push({
1530
- text: `${skipBtnPrompt.value}`,
1531
- icon: 'share',
1532
- isMoreButton: false,
1533
- action: 'skip',
1534
- class: 'skip-btn',
1535
- show: true,
1536
- })
1537
- }
1538
- if (isLastStep.value) {
1539
- allButtons.value.push({
1540
- text: '确认完成',
1541
- isMoreButton: false,
1542
- action: 'done',
1543
- type: 'primary',
1544
- show: true,
1545
- })
1546
- }
1547
- if (canSubmit.value && isShowPrevStepBtn.value && workflowDetailData.value?.f_state !== 1) {
1548
- allButtons.value.push({
1549
- text: `${prevBtnPrompt.value}`,
1550
- icon: 'edit',
1551
- isMoreButton: false,
1552
- action: 'back',
1553
- type: 'warning',
1554
- class: 'prev-btn',
1555
- show: true,
1556
- })
1557
- }
1558
- }
1559
-
1560
- // 扩展按钮ActionSheet 选项选择
1561
- async function onActionSheetSelect(item: any) {
1562
- showMoreMenu.value = false
1563
- await handleButtonClick(item)
1564
- }
1565
-
1566
- // 工具集ActionSheet 选项选择
1567
- function onActionToolsSelect(item: any) {
1568
- showToolsMenu.value = false
1569
- if (item.type === 'tools') {
1570
- const formData = xAddForm.value?.form || {}
1571
- stepStore.setWorkflowId(workflowId)
1572
- stepStore.setStepDefines(stepDefines.value)
1573
- stepStore.setFormData(formData)
1574
- stepStore.setCurrentStepData(currentStep.value)
1575
- stepStore.setReadOnly(showType.value === 1)
1576
- if (item.func) {
1577
- stepStore.setCallbackFunction(async (data: any) => {
1578
- return await executeStrFunctionByContext(
1579
- getCurrentInstance(),
1580
- item.func,
1581
- [data, stepDefines.value, formData, currentStep.value, util, runLogic, getConfigByName],
1582
- )
1583
- })
1584
- }
1585
- router.push({
1586
- name: item.value,
1587
- })
1588
- }
1589
- else if (item.type === 'clearTempData') {
1590
- showToast('清除临时数据成功')
1591
- stepStore.setCallbackFunction(undefined)
1592
- }
1593
- else if (item.type === 'backHome') {
1594
- stepStore.setCallbackFunction(undefined)
1595
- router.push({
1596
- path: '/',
1597
- })
1598
- }
1599
- else if (item.type === 'applyDetail') { // 小工具 流程查看
1600
- stepStore.setCallbackFunction(undefined)
1601
- router.push({
1602
- name: 'applyDetail',
1603
- query: {
1604
- workflowid: workflowId,
1605
- },
1606
- })
1607
- }
1608
- }
1609
-
1610
- // 处理业务相关的表单函数
1611
- function formFunc(func: string) {
1612
- if (props.onFormFunc) {
1613
- props.onFormFunc(func)
1614
- }
1615
- }
1616
-
1617
- // 处理地址选择
1618
- function selectAddressFunc() {
1619
- if (props.onSelectAddress) {
1620
- props.onSelectAddress()
1621
- }
1622
- }
1623
-
1624
- provide('workflowHandleWrap', {
1625
- branchChargePersons,
1626
- calculatedTargetNode,
1627
- isNeedSelectPerson,
1628
- needMultipleBranchSelection,
1629
- branchNodes,
1630
- checkedNextStepPerson,
1631
- checkedNextStepPersonName,
1632
- nextStepPersonOptions,
1633
- getStepNameByStepId,
1634
- })
1635
-
1636
- // 暴露方法给父组件
1637
- defineExpose({
1638
- xAddForm,
1639
- setForm: (data) => {
1640
- xAddForm.value?.setForm(data)
1641
- },
1642
- workflowDetailData,
1643
- })
1644
-
1645
- // 从缓存中还原之后 执行编辑路由回调函数
1646
- async function callbackFunction() {
1647
- if (stepStore.getCallbackFunction()) {
1648
- try {
1649
- const func = stepStore.getCallbackFunction()
1650
- await func({ workflowId })
1651
- }
1652
- catch (error) {
1653
- console.error('func 执行出错:', error)
1654
- }
1655
- finally {
1656
- stepStore.setCallbackFunction(undefined)
1657
- }
1658
- }
1659
- }
1660
-
1661
- onActivated(async () => {
1662
- await callbackFunction()
1663
- })
1664
-
1665
- onBeforeMount(async () => {
1666
- if (!workflowId || !stepId) {
1667
- router.push({
1668
- path: '/',
1669
- })
1670
- }
1671
- else {
1672
- await initComponent()
1673
- }
1674
- })
1675
- </script>
1676
-
1677
- <template>
1678
- <div v-if="isFinish" id="apply-step">
1679
- <div v-if="currentStep.properties?.otherProperty?.toast" class="info-card">
1680
- <VanIcon name="info-o" color="#1890ff" class="info-icon" />
1681
- <div class="info-content">
1682
- <div> {{ currentStep.properties?.otherProperty?.toast }} </div>
1683
- </div>
1684
- </div>
1685
- <VanCellGroup title="步骤信息">
1686
- <VanCell title="步骤名称" :value="currentStep.name" />
1687
- <VanCell title="负责人" :value="currentStep.handler" />
1688
- <VanCell v-if="currentStep.date" title="填报时间" :value="currentStep.date" />
1689
- <VanCell title="截止时间" :value="currentStep.deadline" />
1690
- </VanCellGroup>
1691
- <VanCellGroup v-if="currentStep.back" title="退回信息">
1692
- <VanCell title="说明" value="流程被退回,请重新填写信息发起提交" />
1693
- <VanCell title="操作人" :value="currentStep.back.f_operator" />
1694
- <VanCell title="操作时间" :value="currentStep.back.f_date" />
1695
- <VanCell title="退回原因" :value="currentStep.back.f_notes" />
1696
- </VanCellGroup>
1697
- <template v-if="showType === 0">
1698
- <HistoryFormData
1699
- v-if="historyFormCompletedDataPreview"
1700
- :data="historyFormCompletedDataPreview.data"
1701
- :step-attachments="historyFormCompletedDataPreview.stepAttachments"
1702
- />
1703
- <XForm
1704
- ref="xAddForm"
1705
- mode="新增"
1706
- group-title="填写表单"
1707
- :is-group-form="true"
1708
- :form-data="xAddFormData"
1709
- :group-form-items="stepFormDefine"
1710
- :submit-button="false"
1711
- :service-name="serviceName"
1712
- :is-handle-form-key="false"
1713
- :param-logic-name-param="{ workflowId }"
1714
- @select-address="selectAddressFunc"
1715
- @x-form-item-emit-func="formFunc"
1716
- >
1717
- <template #extraCellGroup>
1718
- <VanCellGroup title="工单信息">
1719
- <!-- 备注信息 -->
1720
- <VanField
1721
- v-model="noteContent"
1722
- rows="3"
1723
- autosize
1724
- label="备注"
1725
- type="textarea"
1726
- maxlength="200"
1727
- placeholder="填写本环节备注事项"
1728
- show-word-limit
1729
- />
1730
- </VanCellGroup>
1731
- </template>
1732
- <template #extraActionSpace>
1733
- <div class="form-footer-fixed">
1734
- <!-- 按钮容器 -->
1735
- <div class="button-container">
1736
- <!-- 显示的主按钮 (最多3个) -->
1737
- <template v-for="(button, index) in displayedButtons" :key="index">
1738
- <VanButton
1739
- :type="button.type || 'default'"
1740
- class="flex-1" :class="[
1741
- button.class,
1742
- { 'more-button': button.isMoreButton },
1743
- ]"
1744
- @click="handleButtonClick(button)"
1745
- >
1746
- <template v-if="button.icon">
1747
- <VanIcon class="btn-icon" :name="button.icon" />
1748
- </template>
1749
- <template v-if="button.isMoreButton">
1750
- <VanIcon name="ellipsis" />
1751
- </template>
1752
- <template v-else>
1753
- {{ button.text }}
1754
- </template>
1755
- </VanButton>
1756
- </template>
1757
- </div>
1758
- </div>
1759
- </template>
1760
- </XForm>
1761
- <!-- 扩展操作弹出区域 -->
1762
- <VanActionSheet
1763
- v-model:show="showMoreMenu"
1764
- :actions="actionSheetActions"
1765
- cancel-text="取消"
1766
- @select="onActionSheetSelect"
1767
- @cancel="showMoreMenu = false"
1768
- />
1769
- </template>
1770
- <template v-else-if="showType === 1">
1771
- <VanCellGroup title="填写历史">
1772
- <template v-if="stepFormData && stepFormData.length > 0">
1773
- <template v-for="(item, i) in stepFormData" :key="`${stepFormData.label} - ${i}`">
1774
- <VanCell :title="item.label" :value="item.value" />
1775
- </template>
1776
- </template>
1777
- <template v-else>
1778
- <VanEmpty description="该步骤暂无数据" />
1779
- </template>
1780
- </VanCellGroup>
1781
- <VanCellGroup title="备注">
1782
- <VanCell title="备注信息" :value="currentStep.note" />
1783
- </VanCellGroup>
1784
- <VanCellGroup v-if="stepFilesData.length > 0" title="附件">
1785
- <FileItem :files="stepFilesData" />
1786
- </VanCellGroup>
1787
- <VanCellGroup v-if="stepImageData.length > 0" title="图片">
1788
- <FileItem :files="stepImageData" />
1789
- </VanCellGroup>
1790
- </template>
1791
- <!-- 工具集弹出区域 -->
1792
- <VanActionSheet
1793
- v-model:show="showToolsMenu"
1794
- :actions="toolActions"
1795
- cancel-text="取消"
1796
- @select="onActionToolsSelect"
1797
- @cancel="showToolsMenu = false"
1798
- />
1799
- <!-- 磁吸工具按钮 -->
1800
- <VanFloatingBubble
1801
- axis="xy"
1802
- icon="more-o"
1803
- magnetic="x"
1804
- @click="handleToolsPopoverClick"
1805
- />
1806
- </div>
1807
- <div v-else-if="isError">
1808
- <VanEmpty :image="errorImage" :description="errorMsg">
1809
- <VanButton round type="primary" class="bottom-button" @click="back">
1810
- 返回上一页
1811
- </VanButton>
1812
- </VanEmpty>
1813
- </div>
1814
- <div v-else-if="isLoading">
1815
- <BeautifulLoading />
1816
- </div>
1817
- <VanActionSheet v-model:show="dialogVisible" class="actionSheetBox" :close-on-click-overlay="false" :close-on-click-action="true" :title="dialogTitle">
1818
- <div class="actionSheetContent">
1819
- <template v-if="operationType === 'submit' || operationType === 'skip'">
1820
- <div class="dialog-box">
1821
- <!-- 智能分支人员选择组件 - 只有非分支节点或最后一个分支才显示 -->
1822
- <template v-if="!isBranchLastNode.isBranchNode || isBranchLastNode.isLastBranch">
1823
- <PersonSelect />
1824
- <XFormItem
1825
- v-model="deadline"
1826
- mode="新增"
1827
- label="截止时间"
1828
- :attr="{ name: '截止时间', type: 'datePicker', rule: { required: 'true' } }"
1829
- />
1830
- </template>
1831
- </div>
1832
- </template>
1833
- <template v-else-if="operationType === 'back'">
1834
- <VanField
1835
- v-model="backNoteContent"
1836
- rows="3"
1837
- autosize
1838
- label="退回原因"
1839
- type="textarea"
1840
- maxlength="200"
1841
- placeholder="填写退回原因/备注"
1842
- show-word-limit
1843
- />
1844
- </template>
1845
- </div>
1846
- <div class="actionSheetFoot">
1847
- <VanButton type="default" @click="() => { dialogVisible = false }">
1848
- 取消
1849
- </VanButton>
1850
- <VanButton type="primary" @click="dialogClick">
1851
- 确认
1852
- </VanButton>
1853
- </div>
1854
- </VanActionSheet>
1855
- </template>
1856
-
1857
- <style scoped lang="less">
1858
- .x-form-container {
1859
- padding-bottom: 90px;
1860
- display: block;
1861
- max-height: initial;
1862
- }
1863
- .form-footer-fixed {
1864
- position: fixed;
1865
- bottom: 0;
1866
- left: 0;
1867
- width: 100%;
1868
-
1869
- padding: 12px;
1870
- box-sizing: border-box;
1871
-
1872
- /* 阴影效果 - 向上投射 */
1873
- box-shadow:
1874
- 0 -4px 12px rgba(0, 0, 0, 0.1),
1875
- 0 -2px 4px rgba(0, 0, 0, 0.06);
1876
-
1877
- /* 毛玻璃背景 (移动端高级效果) */
1878
- background-color: rgba(255, 255, 255, 0.85);
1879
- -webkit-backdrop-filter: blur(10px);
1880
- backdrop-filter: blur(10px);
1881
-
1882
- /* 边框增强立体感 */
1883
- border-top: 1px solid rgba(0, 0, 0, 0.05);
1884
- }
1885
- .button-container {
1886
- display: flex;
1887
- gap: 8px;
1888
-
1889
- .van-button {
1890
- flex: 1; /* 所有按钮等分宽度 */
1891
- min-width: 0; /* 防止文本溢出 */
1892
-
1893
- /* 按钮文字超出时显示省略号 */
1894
- overflow: hidden;
1895
- text-overflow: ellipsis;
1896
- white-space: nowrap;
1897
- }
1898
-
1899
- .more-button {
1900
- flex: 0 0 50px; /* 更多按钮固定宽度 */
1901
- padding: 0;
1902
- }
1903
-
1904
- .submit-btn {
1905
- background-color: #1890ff;
1906
- border: 0;
1907
- }
1908
-
1909
- .skip-btn {
1910
- background-color: #52c41a;
1911
- border: 0;
1912
- color: #fff;
1913
- }
1914
-
1915
- .prev-btn {
1916
- border: 0;
1917
- }
1918
-
1919
- .btn-icon {
1920
- padding-right: 6px;
1921
- }
1922
- }
1923
- .info-card {
1924
- display: flex;
1925
- align-items: flex-start;
1926
- background: #eaf6ff;
1927
- border-left: 3px solid #1677ff;
1928
- border-radius: 12px;
1929
- padding: 10px 16px 8px 14px;
1930
- margin: 16px 16px 0 16px;
1931
- font-size: 15px;
1932
- color: #1677ff;
1933
- min-height: 44px;
1934
- box-shadow: none;
1935
- border-top: none;
1936
- border-right: none;
1937
- border-bottom: none;
1938
- }
1939
- .info-icon {
1940
- font-size: 20px;
1941
- margin-right: 8px;
1942
- margin-top: 2px;
1943
- color: #1677ff;
1944
- flex-shrink: 0;
1945
- }
1946
- .info-content {
1947
- display: flex;
1948
- flex-direction: column;
1949
- justify-content: center;
1950
- }
1951
- .info-content > div:first-child {
1952
- font-weight: bold;
1953
- color: #1677ff;
1954
- font-size: 15px;
1955
- margin-bottom: 2px;
1956
- }
1957
- .info-content > div:last-child {
1958
- font-size: 14px;
1959
- color: #1677ff;
1960
- }
1961
- .actionSheetBox {
1962
- .actionSheetContent {
1963
- padding-bottom: 0;
1964
- }
1965
- .actionSheetFoot {
1966
- display: grid;
1967
- grid-template-columns: auto auto;
1968
- width: 100%;
1969
- margin-top: 20px;
1970
- :deep(.van-button) {
1971
- border-radius: 0 !important;
1972
- }
1973
- }
1974
- }
1975
- </style>
1
+ <script setup lang="ts">
2
+ import BeautifulLoading from '@af-mobile-client-vue3/components/core/BeautifulLoading/index.vue'
3
+
4
+ import FileItem from '@af-mobile-client-vue3/components/data/step/file-item.vue'
5
+ import HistoryFormData from '@af-mobile-client-vue3/components/data/step/history-form-data.vue'
6
+
7
+ import PersonSelect from '@af-mobile-client-vue3/components/data/step/person-select.vue'
8
+
9
+ import XForm from '@af-mobile-client-vue3/components/data/XForm/index.vue'
10
+
11
+ import XFormItem from '@af-mobile-client-vue3/components/data/XFormItem/index.vue'
12
+ import JSONObject from '@af-mobile-client-vue3/expression/instances/JSONObject'
13
+ import LogicRunner from '@af-mobile-client-vue3/logic/LogicRunner'
14
+ import { getConfigByName, runLogic } from '@af-mobile-client-vue3/services/api/common'
15
+ import useStepStore from '@af-mobile-client-vue3/stores/modules/step'
16
+ import useUserStore from '@af-mobile-client-vue3/stores/modules/user'
17
+ import * as util from '@af-mobile-client-vue3/utils/common'
18
+
19
+ import { getDictItemByValue } from '@af-mobile-client-vue3/utils/dictUtil'
20
+ import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
21
+
22
+ import { formatDate } from '@vueuse/core'
23
+ import {
24
+ closeDialog,
25
+ showConfirmDialog,
26
+ showToast,
27
+ ActionSheet as VanActionSheet,
28
+ Button as VanButton,
29
+ Cell as VanCell,
30
+ CellGroup as VanCellGroup,
31
+ Empty as VanEmpty,
32
+ Field as VanField,
33
+ FloatingBubble as VanFloatingBubble,
34
+ Icon as VanIcon,
35
+ } from 'vant'
36
+ import { computed, getCurrentInstance, onActivated, onBeforeMount, provide, ref } from 'vue'
37
+ import { useRoute, useRouter } from 'vue-router'
38
+
39
+ defineOptions({ name: 'StepDetailBase' })
40
+
41
+ const props = withDefaults(defineProps<Props>(), {
42
+ workflowId: undefined,
43
+ stepId: undefined,
44
+ serviceName: 'af-apply',
45
+ onSelectAddress: undefined,
46
+ onFormFunc: undefined,
47
+ })
48
+
49
+ interface Props {
50
+ workflowId?: string
51
+ stepId?: string
52
+ serviceName?: string
53
+ // 业务相关的处理函数
54
+ onSelectAddress?: () => void
55
+ onFormFunc?: (func: string) => void
56
+ }
57
+
58
+ const router = useRouter()
59
+ const route = useRoute()
60
+ const workflowId: string = props.workflowId || (route.query.workflowId as string)
61
+ const stepId: string = props.stepId || (route.query.stepId as string)
62
+
63
+ const userStore = useUserStore()
64
+
65
+ const stepStore = useStepStore()
66
+
67
+ const serviceName = ref(props.serviceName)
68
+
69
+ const isLoading = ref(false)
70
+ const isFinish = ref(false)
71
+ const isError = ref(false)
72
+ // 错误信息
73
+ const errorMsg = ref('')
74
+ // 错误图片类型
75
+ const errorImage = ref('default')
76
+ // 录入表单实例
77
+ const xAddForm = ref<any>(undefined)
78
+ // 展示类型 0 表单 1 只读资源
79
+ const showType = ref(0)
80
+ // 显示供用户填写的当前步骤表单
81
+ const showForm = ref(false)
82
+ // 当前步骤完成状态
83
+ const stepDone = ref(false)
84
+ // 工作流详情数据
85
+ const workflowDetailData = ref(undefined)
86
+ // 所有步骤定义
87
+ const stepDefines = ref([])
88
+ // 所有流程元数据
89
+ const stepMetaDefines = ref([])
90
+ // 当前步骤信息
91
+ const currentStep = ref(undefined)
92
+ // 流程表单定义
93
+ const stepFormDefine = ref(undefined)
94
+ const historyFormCompletedDataPreview = ref(undefined)
95
+ // 流程表单数据
96
+ const stepFormData = ref(undefined)
97
+ // 流程附件数据
98
+ const stepFilesData = ref(undefined)
99
+ // 流程图片数据
100
+ const stepImageData = ref(undefined)
101
+ // 所有节点联通数据
102
+ const directions = ref([])
103
+ // 当前步骤能通向的后续步骤
104
+ const currentDirections = ref([])
105
+ // 当前步骤是否是最后一步
106
+ const isLastStep = ref(false)
107
+ // 是否显示下一步按钮
108
+ const isShowNextStepBtn = ref(false)
109
+ // 是否显示回退按钮
110
+ const isShowPrevStepBtn = ref(false)
111
+ // 是否显示跳过按钮
112
+ const isShowSkipStepBtn = ref(false)
113
+ // 下一步按钮目标
114
+ const nextStepBtnToObj = ref(undefined)
115
+ // 回退按钮目标
116
+ const prevStepBtnToObj = ref(undefined)
117
+ // 跳过按钮目标
118
+ const skipStepBtnToObj = ref(undefined)
119
+ // 下一步按钮提示
120
+ const nextBtnPrompt = ref('')
121
+ // 回退按钮提示
122
+ const prevBtnPrompt = ref('')
123
+ // 跳过按钮提示
124
+ const skipBtnPrompt = ref('')
125
+ // 操作类型,提交到下一步或跳过
126
+ const operationType = ref('submit')
127
+ // 是否需要选择人员
128
+ const isNeedSelectPerson = ref(false)
129
+ // 已选择的下一环节处理人
130
+ const checkedNextStepPerson = ref(undefined)
131
+ // 已选择的下一环节处理人名称
132
+ const checkedNextStepPersonName = ref(undefined)
133
+ // 下一环节处理人选项列表
134
+ const nextStepPersonOptions = ref([])
135
+ // 下一环节人员信息
136
+ const nextStepPersonInfo = ref(undefined)
137
+ // 下一环节截止时间
138
+ const deadline = ref(undefined)
139
+ // 备注表单内容
140
+ const noteContent = ref('')
141
+ // 退回原因内容
142
+ const backNoteContent = ref('')
143
+ // 用于将已有数据,填回x-add表单中
144
+ const formCompletedData = ref({})
145
+ // 用于展示提交的数据
146
+ const formCompletedDataPreview = ref(undefined)
147
+ // 展示更多按钮下拉菜单
148
+ const showMoreMenu = ref(false)
149
+ // 所有操作按钮
150
+ const allButtons = ref([])
151
+ // 展示工具下拉菜单
152
+ const showToolsMenu = ref(false)
153
+ // 浮动按钮工具列表
154
+ const toolActions = ref([])
155
+ /** 分支节点相关数据 */
156
+ // 需要选择人员的分支节点列表
157
+ const branchNodes = ref([])
158
+ // 分支节点人员选择 格式:{stepId: personId}
159
+ const branchChargePersons = ref({})
160
+ // 是否需要为多个分支节点预先选择人员(包含WF_RESULT的条件分支或并行分支)
161
+ const needMultipleBranchSelection = ref(false)
162
+ // 前台计算出的目标节点
163
+ const calculatedTargetNode = ref(undefined)
164
+ // 缓存当前的分支动作,避免重复调用
165
+ const currentBranchActions = ref(undefined)
166
+ // xAddForm 默认值初始化
167
+ const xAddFormData = ref({})
168
+ // 是否展示Dialog
169
+ const dialogVisible = ref(false)
170
+ // Dialog标题
171
+ const dialogTitle = ref('')
172
+ // 当前环节 小工具中工具名称
173
+ const mobileToolNames = ref('')
174
+
175
+ // 是否可以提交
176
+ const canSubmit = computed(() => {
177
+ // 对于超级管理员直接认为可以提交
178
+ if (userStore.getUserInfo().username === '1') {
179
+ return true
180
+ }
181
+ if (!currentStep.value) {
182
+ return false
183
+ }
184
+ // 已完成节点,检查是否展示表单数据
185
+ if (currentStep.value.status !== 1 || workflowDetailData.value?.f_state === 1) {
186
+ // 历史节点为当前登录人操作则不做查看数据权限校验
187
+ if (currentStep.value.handler === userStore.getUserInfo().name) {
188
+ return true
189
+ }
190
+ const viewers = currentStep.value.properties?.otherProperty?.viewers ?? []
191
+ if (viewers.length > 0) {
192
+ // 使用some方法判断当前人员是否满足任一条件
193
+ return viewers.some((item) => {
194
+ if (item.type === 'role') {
195
+ // 检查rolestr是否存在并包含指定角色
196
+ return userStore.getUserInfo().rolestr && userStore.getUserInfo().rolestr.split(',').includes(item.name)
197
+ }
198
+ if (item.type === 'department') {
199
+ // 检查depname是否存在并包含指定部门
200
+ return userStore.getUserInfo().deps && userStore.getUserInfo().deps?.includes(item.name)
201
+ }
202
+ return false
203
+ })
204
+ }
205
+ else {
206
+ return true
207
+ }
208
+ }
209
+ else {
210
+ // 检查角色和部门权限
211
+ if (currentStep.value.properties && currentStep.value.properties.chargePerson) {
212
+ // 如果当前节点的负责人选项中有设置选择人员 当前节点就只能由上一步设置的负责人操作
213
+ if (currentStep.value.properties.chargePerson.needSelectPerson) {
214
+ return currentStep.value.handler === userStore.getUserInfo().name
215
+ }
216
+ if (currentStep.value.properties.chargePerson.personList && currentStep.value.properties.chargePerson.personList.length > 0) {
217
+ // 使用some方法判断当前人员是否满足任一条件
218
+ return currentStep.value.properties.chargePerson.personList.some((item) => {
219
+ if (item.type === 'role') {
220
+ // 检查rolestr是否存在并包含指定角色
221
+ return userStore.getUserInfo().rolestr && userStore.getUserInfo().rolestr.split(',').includes(item.name)
222
+ }
223
+ if (item.type === 'department') {
224
+ // 检查parentname是否存在并包含指定部门
225
+ return userStore.getUserInfo().parentname && userStore.getUserInfo().parentname.includes(item.name)
226
+ }
227
+ return false
228
+ })
229
+ }
230
+ }
231
+
232
+ // 检查handler是否包含当前用户
233
+ if (currentStep.value && currentStep.value.handler) {
234
+ return currentStep.value.handler.includes(userStore.getUserInfo().name)
235
+ }
236
+ }
237
+ return false
238
+ })
239
+
240
+ // 判断当前节点的分支状态
241
+ const isBranchLastNode = computed(() => {
242
+ // 默认返回值
243
+ const defaultResult = {
244
+ isBranchNode: false, // 是否为分支流程中的节点
245
+ isLastBranch: false, // 是否为最后一个未完成的分支(需要选择下一环节人员)
246
+ }
247
+
248
+ // 如果是最后一步或没有下一步或者不能提交,则不是分支节点
249
+ if (isLastStep.value || !nextStepBtnToObj.value || !canSubmit.value) {
250
+ return defaultResult
251
+ }
252
+
253
+ // 检查下一个节点是否为分支退出节点
254
+ const nextStep = stepMetaDefines.value.find(step => step.id === nextStepBtnToObj.value)
255
+
256
+ // 如果下一个节点的flowRole不是branchExit,说明不是分支的最后一个节点
257
+ if (!nextStep || nextStep.properties?.flowRole !== 'branchExit') {
258
+ return defaultResult
259
+ }
260
+
261
+ // 获取分支退出节点等待的所有分支步骤ID
262
+ const waitStepIds = nextStep.properties.waitStepIds || []
263
+ if (waitStepIds.length === 0) {
264
+ return {
265
+ isBranchNode: true,
266
+ isLastBranch: true, // 如果没有等待步骤配置,默认认为是最后一个
267
+ }
268
+ }
269
+
270
+ // 当前步骤必须在等待列表中才算是分支节点
271
+ if (!waitStepIds.includes(currentStep.value.id)) {
272
+ return defaultResult
273
+ }
274
+
275
+ // 检查除当前步骤外的其他分支步骤状态
276
+ const otherBranchSteps = waitStepIds.filter(stepId => stepId !== currentStep.value.id)
277
+
278
+ // 判断其他所有分支步骤是否都已完成(状态为2)
279
+ const allOtherBranchesCompleted = otherBranchSteps.every((stepId) => {
280
+ const step = stepDefines.value.find(s => s.id === stepId)
281
+ return step && step.status === 2 // 已处理
282
+ })
283
+
284
+ return {
285
+ isBranchNode: true, // 当前节点是分支流程中的节点
286
+ isLastBranch: allOtherBranchesCompleted, // 只有当其他所有分支都已完成时,当前分支才是最后一个
287
+ }
288
+ })
289
+
290
+ // 实际展示的按钮
291
+ const displayedButtons = computed(() => {
292
+ if (allButtons.value.length <= 3) {
293
+ return allButtons.value
294
+ }
295
+
296
+ // 当按钮超过3个时,显示前2个和一个"更多"按钮
297
+ const buttons = [...allButtons.value.slice(0, 2)]
298
+ buttons.push({
299
+ text: '更多',
300
+ isMoreButton: true,
301
+ action: 'showMore',
302
+ })
303
+ return buttons
304
+ })
305
+
306
+ // 扩展按钮(从第3个开始)
307
+ const actionSheetActions = computed(() => {
308
+ if (allButtons.value.length <= 3)
309
+ return []
310
+ return allButtons.value.slice(2).map((button) => {
311
+ if (button.show === true) {
312
+ return {
313
+ name: button.text,
314
+ action: button.action,
315
+ func: button.func,
316
+ }
317
+ }
318
+ else {
319
+ return undefined
320
+ }
321
+ }).filter(item => item !== undefined)
322
+ })
323
+
324
+ // 初始化组件
325
+ async function initComponent() {
326
+ isLoading.value = true;
327
+ // 设置标题
328
+ (window as any).microApp?.dispatch({ title: route.query.title })
329
+ // 获取流程定义集合和当前流程信息
330
+ const defineData: any = await runLogic('getStepNoteAndHandler', {
331
+ workflowId,
332
+ }, serviceName.value)
333
+ const stepIdValue = Number.parseInt(stepId as string)
334
+ stepDefines.value = defineData
335
+ // 当前节点状态信息
336
+ const currentStepState: any = await runLogic('getWorkFlowCurrentSubState', {
337
+ workflowId,
338
+ }, serviceName.value)
339
+ // 当前节点定义信息
340
+ const currentStepDefine: any = defineData.filter((item: any) => item.id === stepIdValue)[0]
341
+ const currentStepData: any = {}
342
+ Object.assign(currentStepData, currentStepDefine)
343
+ Object.assign(currentStepData, currentStepState)
344
+ currentStep.value = currentStepData
345
+ mobileToolNames.value = currentStep.value.properties?.otherProperty?.mobileToolsRoutes?.map(d => d.name).join('、') ?? ''
346
+ if (!currentStep.value) {
347
+ errorMsg.value = '工作流步骤不存在'
348
+ errorImage.value = 'default'
349
+ isError.value = true
350
+ return
351
+ }
352
+ if (!canSubmit.value) {
353
+ errorMsg.value = '您没有访问该步骤的权限'
354
+ errorImage.value = 'error'
355
+ isError.value = true
356
+ return
357
+ }
358
+ // 获取到当前步骤后复制下一步时间
359
+ deadline.value = getDefaultDeadline(currentStep.value.properties?.otherProperty?.nextNodeInterval)
360
+ // 获取当前步骤节点,连通的节点
361
+ await getDirection()
362
+ if (!currentStep.value.note) {
363
+ currentStep.value.note = '无'
364
+ }
365
+ // 获取工单基本信息
366
+ await getBaseInfo()
367
+ if (currentStep.value.status !== 1 || workflowDetailData.value?.f_state === 1) {
368
+ // 已完成流程获取流程表单数据
369
+ const formData: any = await runLogic('getWorkFlowCompletedStepData', {
370
+ workflowId,
371
+ stepId,
372
+ }, serviceName.value)
373
+ stepFormData.value = getFormData(formData.data)
374
+ stepFilesData.value = formData.files
375
+ stepImageData.value = formData.images
376
+ showType.value = 1
377
+ }
378
+ else {
379
+ stepFormDefine.value = currentStep.value.properties.form
380
+ addActionButtons()
381
+ appendCustomizeButtons()
382
+ showType.value = 0
383
+ }
384
+ // 当前环节配置的历史表单数据展示配置
385
+ await buildHistoryFormCompletedData()
386
+ isLoading.value = false
387
+ isFinish.value = true
388
+ }
389
+ async function buildHistoryFormCompletedData() {
390
+ historyFormCompletedDataPreview.value = null
391
+ const otherProperty = currentStep.value?.properties?.otherProperty
392
+ const historyFormDataConfig = otherProperty?.historyFormData
393
+
394
+ if (!Array.isArray(historyFormDataConfig))
395
+ return
396
+
397
+ // ===== 预处理 =====
398
+ const stepIdByName = new Map(
399
+ stepDefines.value.map(step => [step.name, step.id]),
400
+ )
401
+
402
+ const stepFormJsonByStepId = new Map(
403
+ stepDefines.value.map(step => [
404
+ step.id,
405
+ step?.properties?.form?.formJson,
406
+ ]),
407
+ )
408
+
409
+ // ===== 收集需要查询的 stepId =====
410
+ const stepIdSet = new Set()
411
+
412
+ for (const historyFormConfig of historyFormDataConfig) {
413
+ const targetStepId = stepIdByName.get(historyFormConfig.stepName)
414
+ if (targetStepId != null) {
415
+ stepIdSet.add(targetStepId)
416
+ }
417
+ }
418
+
419
+ const stepIdS = Array.from(stepIdSet)
420
+ if (stepIdS.length === 0)
421
+ return
422
+
423
+ // ===== 请求所有步骤数据 =====
424
+ const allStepData = await runLogic('geWorkFlowCompletedStepDataBySteps', {
425
+ workflowId,
426
+ stepIdS,
427
+ }, serviceName.value)
428
+
429
+ // ===== 组装数据 =====
430
+ const historyFormData = []
431
+ const stepAttachmentsMap = new Map()
432
+
433
+ for (const historyFormConfig of historyFormDataConfig) {
434
+ const stepId = stepIdByName.get(historyFormConfig.stepName)
435
+ if (stepId == null)
436
+ continue
437
+
438
+ const targetStepId = Number.parseInt(stepId)
439
+
440
+ const stepFormJson = stepFormJsonByStepId.get(targetStepId)
441
+ if (!Array.isArray(stepFormJson))
442
+ continue
443
+
444
+ const stepDataRaw
445
+ = allStepData?.[targetStepId]
446
+ ?? allStepData?.[String(targetStepId)]
447
+
448
+ if (!stepDataRaw)
449
+ continue
450
+
451
+ const isAttachment
452
+ = historyFormConfig.displayLabel === '附件'
453
+ && historyFormConfig.formLabel === '附件'
454
+
455
+ // ===== 附件 =====
456
+ if (isAttachment) {
457
+ if (!stepAttachmentsMap.has(targetStepId)) {
458
+ stepAttachmentsMap.set(targetStepId, {
459
+ stepId: targetStepId,
460
+ stepName: historyFormConfig.stepName,
461
+ data: [],
462
+ images: [],
463
+ files: [],
464
+ })
465
+ }
466
+
467
+ const attachment = stepAttachmentsMap.get(targetStepId)
468
+ attachment.images = Array.isArray(stepDataRaw.images)
469
+ ? stepDataRaw.images
470
+ : []
471
+ attachment.files = Array.isArray(stepDataRaw.files)
472
+ ? stepDataRaw.files
473
+ : []
474
+
475
+ continue
476
+ }
477
+
478
+ // ===== 普通字段 =====
479
+ const model = historyFormConfig.formLabel
480
+
481
+ const value = stepDataRaw.data?.[model]
482
+ if (value === undefined || value === null)
483
+ continue
484
+
485
+ historyFormData.push({
486
+ label: historyFormConfig.displayLabel,
487
+ value,
488
+ })
489
+ }
490
+
491
+ // ===== 汇总结果 =====
492
+ historyFormCompletedDataPreview.value = {
493
+ data: historyFormData,
494
+ stepAttachments: Array.from(stepAttachmentsMap.values()),
495
+ }
496
+ }
497
+
498
+ // 获取默认截止时间
499
+ function getDefaultDeadline(day = 1) {
500
+ const date = new Date()
501
+ date.setDate(date.getDate() + day)
502
+ date.setHours(date.getHours() + 2)
503
+ return formatDate(date, 'YYYY-MM-DD HH:mm')
504
+ }
505
+
506
+ // 获取表单数据
507
+ function getFormData(stepFormData: any) {
508
+ const formDataArr = []
509
+ const formConfig = currentStep.value?.properties?.form
510
+ const isKeyHandle = formConfig?.isKeyHandle || false
511
+ const formJson = formConfig?.formJson || []
512
+
513
+ for (const stepDefine of formJson) {
514
+ // 如果是特殊函数展示的字段,跳过
515
+ if (stepDefine.showFormItemFunc)
516
+ continue
517
+
518
+ const key = getRealKey(stepDefine.model, isKeyHandle)
519
+ if (!(key in stepFormData) || ['FilesId', 'Images'].includes(key) || ['image', 'signature'].includes(stepDefine.type))
520
+ continue
521
+
522
+ const value = stepFormData[key]
523
+ const item = { label: stepDefine.name, value }
524
+
525
+ // 如果是需要字典转换的 select
526
+ if (stepDefine.type === 'select' && stepDefine.keyName?.includes('config@')) {
527
+ const dictKey = stepDefine.keyName.split('@')[1]
528
+ convertDictValue(dictKey, value, (label) => {
529
+ item.value = label
530
+ })
531
+ }
532
+
533
+ formDataArr.push(item)
534
+ }
535
+
536
+ return formDataArr.length > 0 ? formDataArr : null
537
+ }
538
+
539
+ function getRealKey(key: string, isHandleFormKey: boolean) {
540
+ if (!key)
541
+ return ''
542
+ if (key === 'selected_id')
543
+ return key
544
+ return isHandleFormKey ? key.split('_').slice(1).join('_') : key
545
+ }
546
+
547
+ // 获取当前步骤节点,连通的节点
548
+ async function getDirection() {
549
+ // 获取流程定义
550
+ const res: any = await runLogic('getWorkFlowDefine', {
551
+ id: workflowId,
552
+ }, serviceName.value)
553
+ directions.value = []
554
+ stepMetaDefines.value = res.steps
555
+ await resolveDirections()
556
+ appendToolsViewOptions()
557
+ }
558
+
559
+ // 追加自定义按钮
560
+ function appendCustomizeButtons() {
561
+ // 获取当前步骤的按钮组配置
562
+ if (currentStep.value.properties?.buttonGroup?.actionArr) {
563
+ for (const item of currentStep.value.properties.buttonGroup.actionArr) {
564
+ allButtons.value.push({
565
+ text: item.text,
566
+ isMoreButton: false,
567
+ action: 'other',
568
+ func: item.func,
569
+ show: checkButtonVisible(item),
570
+ })
571
+ }
572
+ }
573
+ }
574
+
575
+ // 追加浮动按钮的工具选项
576
+ function appendToolsViewOptions() {
577
+ // 获取当前步骤的浮动按钮工具集配置
578
+ toolActions.value = []
579
+ if (currentStep.value.properties?.otherProperty?.mobileToolsRoutes) {
580
+ for (const item of currentStep.value.properties.otherProperty.mobileToolsRoutes) {
581
+ toolActions.value.push({
582
+ name: item.name,
583
+ value: item.value,
584
+ type: 'tools',
585
+ func: item.func,
586
+ })
587
+ }
588
+ }
589
+ toolActions.value.push({
590
+ name: '流程查看',
591
+ value: 'applyDetail',
592
+ type: 'applyDetail',
593
+ })
594
+ toolActions.value.push({
595
+ name: '返回首页',
596
+ value: 'home',
597
+ type: 'backHome',
598
+ })
599
+ }
600
+
601
+ // 浮动按钮点击操作
602
+ function handleToolsPopoverClick() {
603
+ showToolsMenu.value = true
604
+ }
605
+
606
+ // 分析当前节点,能通向的节点
607
+ async function resolveDirections() {
608
+ if (currentStep.value?.properties?.actions) {
609
+ currentDirections.value = currentStep.value.properties.actions
610
+ }
611
+ // 判断是否是最后ige节点
612
+ isLastStep.value = (currentStep.value?.properties?.actions || []).filter((item) => {
613
+ return item.type !== 'back'
614
+ }).length === 0
615
+
616
+ // 处理跳转按钮
617
+ workflowControl()
618
+
619
+ if (!isLastStep.value) {
620
+ // 分析分支节点并设置智能人员选择
621
+ await analyzeBranchNodes()
622
+ }
623
+ }
624
+
625
+ // 重置分支选择状态
626
+ function resetBranchSelection() {
627
+ branchNodes.value = []
628
+ branchChargePersons.value = {}
629
+ needMultipleBranchSelection.value = false
630
+ calculatedTargetNode.value = undefined
631
+ currentBranchActions.value = undefined
632
+ }
633
+
634
+ // 分析分支节点并设置选择框
635
+ async function analyzeBranchNodes() {
636
+ try {
637
+ // 初始化状态
638
+ resetBranchSelection()
639
+
640
+ // 获取所有类型的分支动作并缓存
641
+ currentBranchActions.value = getBranchActions()
642
+
643
+ // 如果没有任何分支,处理普通流程
644
+ if (!currentBranchActions.value.hasAnyBranch) {
645
+ await handleNormalFlow()
646
+ return
647
+ }
648
+
649
+ // 判断是否需要多分支选择
650
+ needMultipleBranchSelection.value = shouldUseMultipleBranchSelection(currentBranchActions.value)
651
+
652
+ if (needMultipleBranchSelection.value) {
653
+ // 需要为多个分支预先选择人员
654
+ await setupMultipleBranchSelection(currentBranchActions.value.allBranchActions)
655
+ }
656
+ else {
657
+ // 可以前台实时计算的条件分支
658
+ await calculateTargetNodeFromForm(currentBranchActions.value.conditionalActions)
659
+ }
660
+ }
661
+ catch (error) {
662
+ console.error('分析分支节点失败:', error)
663
+ // 降级处理:使用普通流程
664
+ await handleNormalFlow()
665
+ }
666
+ }
667
+
668
+ // 设置多分支人员选择
669
+ async function setupMultipleBranchSelection(branchActions) {
670
+ branchNodes.value = []
671
+ branchChargePersons.value = {}
672
+ let hasPersonSelection = false
673
+
674
+ for (const action of branchActions) {
675
+ const stepDefine = stepMetaDefines.value.find(step => step.id === action.to)
676
+ const stepPerson = stepDefine?.properties?.chargePerson
677
+
678
+ // 所有分支节点都加入branchNodes,保持数据结构完整
679
+ const nodeData = {
680
+ stepId: action.to,
681
+ stepName: getStepNameByStepId(action.to),
682
+ chargePerson: stepPerson,
683
+ needSelectPerson: stepPerson?.needSelectPerson || false,
684
+ chargePersonOptions: [],
685
+ }
686
+
687
+ if (stepPerson?.needSelectPerson) {
688
+ // 需要选择人员的分支
689
+ nodeData.chargePersonOptions = await getStepPersonOptionsForStep(action.to)
690
+
691
+ // 初始化选择值
692
+ branchChargePersons.value[action.to] = undefined
693
+ hasPersonSelection = true
694
+ }
695
+ else {
696
+ // 不需要选择人员的分支,直接存储配置
697
+ branchChargePersons.value[action.to] = stepPerson
698
+ }
699
+
700
+ branchNodes.value.push(nodeData)
701
+ }
702
+
703
+ // 只有当存在需要选择人员的分支时才显示选择区域
704
+ isNeedSelectPerson.value = hasPersonSelection
705
+ }
706
+
707
+ // 根据表单数据计算目标节点
708
+ async function calculateTargetNodeFromForm(conditionalActions = null) {
709
+ if (!conditionalActions) {
710
+ conditionalActions = currentDirections.value.filter(action => action.type === 'conditionalBranch')
711
+ }
712
+
713
+ if (conditionalActions.length === 0) {
714
+ // 如果当前节点不是在分支流程中,才清空人员选择
715
+ // 避免在并行分支节点中误清除人员选择框
716
+ const isInBranch = currentStep.value?.properties?.branchPath
717
+ if (!isInBranch) {
718
+ clearPersonSelection()
719
+ }
720
+ return
721
+ }
722
+
723
+ try {
724
+ const formData = xAddForm.value.form || {}
725
+
726
+ // 遍历条件找到匹配的
727
+ for (const action of conditionalActions) {
728
+ if (action.expression) {
729
+ const result = await evaluateExpression(action.expression, formData)
730
+ if (result) {
731
+ calculatedTargetNode.value = action.to
732
+ await setupSingleTargetPersonSelection(action.to)
733
+ return
734
+ }
735
+ }
736
+ }
737
+ // 如果没有匹配的条件,清空选择
738
+ clearPersonSelection()
739
+ }
740
+ catch (error) {
741
+ console.warn('计算目标节点失败:', error)
742
+ clearPersonSelection()
743
+ }
744
+ }
745
+
746
+ // 表达式评估 - 使用 LogicRunner
747
+ async function evaluateExpression(expression, formData) {
748
+ try {
749
+ // 构建参数对象,传递当前表单数据
750
+ const params = {
751
+ WF_FORM: { ...formData },
752
+ }
753
+
754
+ // 使用 LogicRunner 执行表达式
755
+ return await LogicRunner.runExpression(expression, new JSONObject(params))
756
+ }
757
+ catch (error) {
758
+ console.warn('表达式评估失败:', expression, error)
759
+ return false
760
+ }
761
+ }
762
+
763
+ // 判断是否应该使用多分支选择模式
764
+ function shouldUseMultipleBranchSelection(branchActions) {
765
+ // 情况1: 有并行分支
766
+ if (branchActions.parallelActions.length > 0) {
767
+ return true
768
+ }
769
+
770
+ // 情况2: 条件分支包含WF_RESULT(后台结果决定)
771
+ if (branchActions.conditionalActions.length > 0) {
772
+ const conditionalActions = currentDirections.value.filter(action => action.type === 'conditionalBranch')
773
+ if (conditionalActions.length === 0)
774
+ return false
775
+
776
+ return conditionalActions.some((action) => {
777
+ return action.expression && action.expression.includes('WF_RESULT')
778
+ })
779
+ }
780
+
781
+ return false
782
+ }
783
+
784
+ // 处理普通单线流程(没有分支的情况)
785
+ async function handleNormalFlow() {
786
+ const targetStepId = operationType.value === 'skip' ? skipStepBtnToObj.value : nextStepBtnToObj.value
787
+
788
+ calculatedTargetNode.value = targetStepId
789
+ const stepDefine = stepMetaDefines.value.find(step => step.id === targetStepId)
790
+
791
+ if (!stepDefine) {
792
+ console.warn('未找到目标步骤定义:', targetStepId)
793
+ clearPersonSelection()
794
+ return
795
+ }
796
+
797
+ const defineProperties = stepDefine.properties
798
+ nextStepPersonInfo.value = defineProperties?.chargePerson
799
+
800
+ // 兼容旧格式
801
+ if (defineProperties.chargePerson) {
802
+ defineProperties.chargePerson = normalizeChargePersonFormat(defineProperties.chargePerson)
803
+ }
804
+
805
+ if (defineProperties?.chargePerson?.needSelectPerson) {
806
+ await setupSingleTargetPersonSelection(targetStepId)
807
+ // 确保 branchChargePersons 中有该节点的数据结构
808
+ branchChargePersons.value[targetStepId] = {
809
+ handler: '',
810
+ handlerId: undefined,
811
+ ...defineProperties.chargePerson,
812
+ }
813
+ }
814
+ else {
815
+ // 不需要选择人员的情况,直接存储配置
816
+ branchChargePersons.value[targetStepId] = defineProperties?.chargePerson || {}
817
+ isNeedSelectPerson.value = false
818
+ nextStepPersonOptions.value = []
819
+ }
820
+ }
821
+
822
+ // 设置单个目标节点的人员选择
823
+ async function setupSingleTargetPersonSelection(stepId) {
824
+ const stepDefine = stepMetaDefines.value.find(step => step.id === stepId)
825
+
826
+ if (!stepDefine?.properties?.chargePerson?.needSelectPerson) {
827
+ isNeedSelectPerson.value = false
828
+ nextStepPersonOptions.value = []
829
+ return
830
+ }
831
+
832
+ // 设置人员选择
833
+ nextStepPersonInfo.value = stepDefine.properties.chargePerson
834
+ checkedNextStepPerson.value = undefined
835
+ checkedNextStepPersonName.value = undefined
836
+ nextStepPersonOptions.value = await getStepPersonOptionsForStep(stepId)
837
+ // 如果只有一个选项,自动选中
838
+ if (nextStepPersonOptions.value.length === 1) {
839
+ checkedNextStepPerson.value = nextStepPersonOptions.value[0].value
840
+ checkedNextStepPersonName.value = nextStepPersonOptions.value[0].label
841
+ }
842
+
843
+ isNeedSelectPerson.value = true
844
+ }
845
+
846
+ // 获取下一步操作人的人员列表
847
+ async function getStepPersonOptionsForStep(stepId) {
848
+ const define = stepMetaDefines.value.find(item => item.id === stepId)
849
+ if (!define?.properties?.chargePerson)
850
+ return []
851
+
852
+ const stepPerson = define.properties.chargePerson
853
+ nextStepPersonInfo.value = normalizeChargePersonFormat(stepPerson)
854
+
855
+ if (!stepPerson.needSelectPerson || !stepPerson.personList)
856
+ return []
857
+
858
+ // 获取所有用户信息
859
+ const allUser = await runLogic('getAllUserOptionList', {}, 'af-system')
860
+
861
+ // 根据配置筛选用户
862
+ const options = filterUsersByPersonConfig(stepPerson.personList, allUser)
863
+
864
+ // 去重处理
865
+ return Array.from(new Map(options.map(item => [item.value, item])).values())
866
+ }
867
+
868
+ // 根据人员配置筛选用户
869
+ function filterUsersByPersonConfig(personList, allUsers) {
870
+ return personList.reduce((acc, personItem) => {
871
+ let filteredUsers = []
872
+
873
+ if (personItem.type === 'role') {
874
+ filteredUsers = allUsers.filter(user =>
875
+ user.rolestr && user.rolestr.split(',').includes(personItem.name),
876
+ ).map(user => ({
877
+ label: user.label,
878
+ value: user.value,
879
+ }))
880
+ }
881
+ else if (personItem.type === 'department') {
882
+ filteredUsers = allUsers.filter(user =>
883
+ user.depname === personItem.name,
884
+ ).map(user => ({
885
+ label: user.label,
886
+ value: user.value,
887
+ }))
888
+ }
889
+
890
+ return [...acc, ...filteredUsers]
891
+ }, [])
892
+ }
893
+
894
+ // 标准化人员配置格式(兼容旧格式)
895
+ function normalizeChargePersonFormat(chargePerson) {
896
+ if (chargePerson.role || chargePerson.department) {
897
+ chargePerson.needSelectPerson = true
898
+ chargePerson.personList = [{
899
+ type: chargePerson.role ? 'role' : 'department',
900
+ name: chargePerson.role || chargePerson.department,
901
+ }]
902
+ }
903
+ return chargePerson
904
+ }
905
+
906
+ // 清空人员选择
907
+ function clearPersonSelection() {
908
+ calculatedTargetNode.value = undefined
909
+ isNeedSelectPerson.value = false
910
+ nextStepPersonOptions.value = []
911
+ checkedNextStepPerson.value = undefined
912
+ checkedNextStepPersonName.value = undefined
913
+ }
914
+
915
+ // 获取分支动作分类
916
+ function getBranchActions() {
917
+ const conditionalActions = currentDirections.value.filter(action => action.type === 'conditionalBranch')
918
+ const parallelActions = currentDirections.value.filter(action => action.type === 'parallelBranch')
919
+ const allBranchActions = [...conditionalActions, ...parallelActions]
920
+
921
+ return {
922
+ conditionalActions,
923
+ parallelActions,
924
+ allBranchActions,
925
+ hasAnyBranch: allBranchActions.length > 0,
926
+ }
927
+ }
928
+
929
+ // 根据步骤id获取步骤名称
930
+ function getStepNameByStepId(stepId) {
931
+ return stepMetaDefines.value.find(item => item.id === stepId)?.name
932
+ }
933
+
934
+ // 根据当前节点,判断之后流程,以及按钮的显示
935
+ function workflowControl() {
936
+ // 重置所有控制数据为默认值,避免之前状态的影响
937
+ isShowNextStepBtn.value = false
938
+ isShowPrevStepBtn.value = false
939
+ isShowSkipStepBtn.value = false
940
+ nextStepBtnToObj.value = undefined
941
+ prevStepBtnToObj.value = undefined
942
+ skipStepBtnToObj.value = undefined
943
+ nextBtnPrompt.value = ''
944
+ prevBtnPrompt.value = ''
945
+ skipBtnPrompt.value = ''
946
+
947
+ // 分类收集不同类型的actions
948
+ const submitActions = currentDirections.value.filter(item => item.type === 'submit')
949
+ const conditionalActions = currentDirections.value.filter(item => item.type === 'conditionalBranch')
950
+ const parallelActions = currentDirections.value.filter(item => item.type === 'parallelBranch')
951
+ const skipActions = currentDirections.value.filter(item => item.type === 'skip')
952
+ const backActions = currentDirections.value.filter(item => item.type === 'back')
953
+
954
+ // 处理提交按钮 - 优先级:submit > conditionalBranch > parallelBranch
955
+ if (submitActions.length > 0) {
956
+ isShowNextStepBtn.value = true
957
+ nextStepBtnToObj.value = submitActions[0].to // 如果有多个submit,取第一个作为主要跳转目标
958
+ if (submitActions.length === 1) {
959
+ nextBtnPrompt.value = `进行下一环节:${getStepNameByStepId(submitActions[0].to)}`
960
+ }
961
+ else {
962
+ nextBtnPrompt.value = `将进入以下环节:${submitActions.map(item => getStepNameByStepId(item.to)).join(' , ')}`
963
+ }
964
+ }
965
+ else if (conditionalActions.length > 0) {
966
+ isShowNextStepBtn.value = true
967
+ nextStepBtnToObj.value = conditionalActions[0].to // 条件分支的第一个作为主要跳转目标
968
+ const targetSteps = conditionalActions.map(item => getStepNameByStepId(item.to)).join(' , ')
969
+ nextBtnPrompt.value = `后台判断后跳转到:${targetSteps}`
970
+ }
971
+ else if (parallelActions.length > 0) {
972
+ isShowNextStepBtn.value = true
973
+ nextStepBtnToObj.value = parallelActions[0].to // 并行分支的第一个作为主要跳转目标
974
+ const targetSteps = parallelActions.map(item => getStepNameByStepId(item.to)).join(' , ')
975
+ nextBtnPrompt.value = `将并行进入以下分支:${targetSteps}`
976
+ }
977
+
978
+ // 处理跳过按钮
979
+ if (skipActions.length > 0) {
980
+ isShowSkipStepBtn.value = true
981
+ skipStepBtnToObj.value = skipActions[0].to
982
+ if (skipActions.length === 1) {
983
+ skipBtnPrompt.value = `跳至${getStepNameByStepId(skipActions[0].to)}`
984
+ }
985
+ else {
986
+ const targetSteps = skipActions.map(item => getStepNameByStepId(item.to)).join(' , ')
987
+ skipBtnPrompt.value = `跳至${targetSteps}`
988
+ }
989
+ }
990
+
991
+ // 处理退回按钮
992
+ if (backActions.length > 0) {
993
+ const backAction = backActions[0] // 通常只有一个back action
994
+ for (let i = currentStep.value.id - 1; i > 0; i--) {
995
+ if (backAction.to >= i && stepDefines.value.find(item => item.id === i)?.handler) {
996
+ prevStepBtnToObj.value = i
997
+ prevBtnPrompt.value = `退至${getStepNameByStepId(i)}`
998
+ isShowPrevStepBtn.value = true
999
+ break
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // 清理数据
1006
+ function clearData() {
1007
+ checkedNextStepPerson.value = undefined
1008
+ checkedNextStepPersonName.value = undefined
1009
+ directions.value = []
1010
+ currentDirections.value = []
1011
+ isShowNextStepBtn.value = false
1012
+ isShowSkipStepBtn.value = false
1013
+ isShowPrevStepBtn.value = false
1014
+ stepDone.value = false
1015
+ formCompletedData.value = {}
1016
+ formCompletedDataPreview.value = undefined
1017
+ operationType.value = 'submit'
1018
+
1019
+ // 清理分支节点相关数据
1020
+ branchNodes.value = []
1021
+ branchChargePersons.value = {}
1022
+ needMultipleBranchSelection.value = false
1023
+ calculatedTargetNode.value = undefined
1024
+ currentBranchActions.value = undefined
1025
+
1026
+ // 清理操作按钮
1027
+ allButtons.value = []
1028
+ }
1029
+
1030
+ // 分离出字典转换逻辑
1031
+ function convertDictValue(dictKey: string, value: string, callback: Function) {
1032
+ getDictItemByValue(dictKey, serviceName.value, value, (dictItem: any) => {
1033
+ if (dictItem) {
1034
+ callback(dictItem.label)
1035
+ }
1036
+ })
1037
+ }
1038
+
1039
+ // 获取工单基础信息
1040
+ async function getBaseInfo() {
1041
+ const res: any = await runLogic('getWorkFlowBasicInfo', {
1042
+ workflowId,
1043
+ }, serviceName.value)
1044
+ // 主动传入得优先级较高
1045
+ if (currentStep.value.id) {
1046
+ res.f_sub_state = stepDefines.value.find(item => item.id === currentStep.value.id)?.name
1047
+ }
1048
+ else {
1049
+ res.f_sub_state = stepDefines.value.find(item => item.id === res.f_step_id)?.name
1050
+ }
1051
+ workflowDetailData.value = res
1052
+ }
1053
+
1054
+ // 检查按钮是否显示
1055
+ function checkButtonVisible(button) {
1056
+ try {
1057
+ if (button.customFunction) {
1058
+ getBaseInfo()
1059
+ return executeStrFunctionByContext(getCurrentInstance(), button.customFunction, [workflowDetailData.value, xAddForm.value, util, runLogic, getConfigByName])
1060
+ }
1061
+ return true
1062
+ }
1063
+ catch (error) {
1064
+ console.error('执行按钮显示函数失败:', error)
1065
+ return false
1066
+ }
1067
+ }
1068
+
1069
+ // 处理动态按钮点击
1070
+ function handleCustomButtonClick(button) {
1071
+ try {
1072
+ // 执行自定义函数
1073
+ if (button.func) {
1074
+ getBaseInfo()
1075
+ const result = executeStrFunctionByContext(getCurrentInstance(), button.func, [workflowDetailData.value, xAddForm.value, util, runLogic, getConfigByName, 'mobile', userStore.getUserInfo(), showToast, showConfirmDialog])
1076
+ if (result) {
1077
+ // 如果返回true,执行对应的操作
1078
+ if (button.text === '提交') {
1079
+ handleButtonClick({
1080
+ action: 'submit',
1081
+ })
1082
+ }
1083
+ }
1084
+ }
1085
+ }
1086
+ catch (error) {
1087
+ console.error('执行自定义按钮函数失败:', error)
1088
+ showToast('执行操作失败')
1089
+ }
1090
+ }
1091
+
1092
+ // 表单提交的回调
1093
+ async function submitForm(obj) {
1094
+ const formData = obj.realForm
1095
+ const time = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
1096
+ return runLogic('saveWorkFlowStepFormData', {
1097
+ workflowId,
1098
+ stepId: currentStep.value.id,
1099
+ form: formData,
1100
+ data: time,
1101
+ handler: userStore.getUserInfo().name,
1102
+ note: noteContent.value.trim(),
1103
+ }, serviceName.value).then(() => {
1104
+ noteContent.value = ''
1105
+ showForm.value = false
1106
+ stepDone.value = true
1107
+ })
1108
+ }
1109
+
1110
+ // 保存工作流日志
1111
+ function saveWorkflowLog(operation, desc, extra) {
1112
+ runLogic('saveWorkFlowLog', {
1113
+ workflowId,
1114
+ operation,
1115
+ desc,
1116
+ operator: userStore.getUserInfo().name,
1117
+ notes: '',
1118
+ setHandler: '',
1119
+ setDeadline: '',
1120
+ ...extra,
1121
+ }, serviceName.value)
1122
+ }
1123
+
1124
+ // 业务提交统一处理
1125
+ async function handleButtonClick(button: any) {
1126
+ if (button.action === 'showMore') {
1127
+ showMoreMenu.value = true
1128
+ return
1129
+ }
1130
+
1131
+ if ((button.action === 'submit' || button.action === 'skip' || button.action === 'done')) {
1132
+ await xAddForm.value.validate()
1133
+ }
1134
+
1135
+ if (button.action === 'submit' || button.action === 'skip' || button.action === 'back') {
1136
+ operationType.value = button.action
1137
+ // 重新分析分支节点
1138
+ checkedNextStepPerson.value = undefined
1139
+ checkedNextStepPersonName.value = undefined
1140
+ branchChargePersons.value = {}
1141
+ await analyzeBranchNodes()
1142
+ }
1143
+
1144
+ // 根据按钮类型执行不同操作
1145
+ switch (button.action) {
1146
+ case 'submit': {
1147
+ dialogTitle.value = '提交环节'
1148
+ dialogVisible.value = true
1149
+ break
1150
+ }
1151
+ case 'skip': {
1152
+ dialogTitle.value = '跳过环节'
1153
+ dialogVisible.value = true
1154
+ break
1155
+ }
1156
+ case 'done': {
1157
+ await doneClick()
1158
+ break
1159
+ }
1160
+ case 'back': {
1161
+ dialogTitle.value = `${prevBtnPrompt.value}环节`
1162
+ dialogVisible.value = true
1163
+ break
1164
+ }
1165
+ case 'other': {
1166
+ handleCustomButtonClick(button)
1167
+ break
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ // 完工按钮
1173
+ async function doneClick() {
1174
+ xAddForm.value.asyncSubmit().then((res) => {
1175
+ submitForm(res).then((_) => {
1176
+ runLogic('afterWorkFlowFinalStepSubmit', {
1177
+ workflowId,
1178
+ stepId: currentStep.value.id,
1179
+ submitUser: userStore.getUserInfo().name,
1180
+ submitUserId: userStore.getUserInfo().id,
1181
+ }, serviceName.value).then((_res) => {
1182
+ saveWorkflowLog('确认完工', `最后一步: ${getStepNameByStepId(currentStep.value.id)}`, { notes: noteContent.value.trim() })
1183
+ showToast('已完工')
1184
+ clearData()
1185
+ back()
1186
+ }, () => {
1187
+ showToast('提交失败!')
1188
+ })
1189
+ })
1190
+ }).catch((err) => {
1191
+ if (err && err.message === 'Form validation failed') {
1192
+ showToast('请检查表单必填项!')
1193
+ }
1194
+ else {
1195
+ // 对于其他错误,继续向上抛出
1196
+ throw err
1197
+ }
1198
+ })
1199
+ }
1200
+
1201
+ function back() {
1202
+ history.back()
1203
+ }
1204
+
1205
+ // 获取提交步骤的额外数据
1206
+ function getApplyStepExtraData(stepId) {
1207
+ // 验证人员选择 - 只有在需要显示人员选择器的情况下才验证
1208
+ // 判断条件与模板中的 WorkflowPersonSelector 显示条件保持一致
1209
+ const shouldShowPersonSelector = !isBranchLastNode.value.isBranchNode || isBranchLastNode.value.isLastBranch
1210
+
1211
+ if (shouldShowPersonSelector && isNeedSelectPerson.value) {
1212
+ if (needMultipleBranchSelection.value) {
1213
+ // 多分支情况:检查是否所有需要的分支都选择了人员
1214
+ const missingSelections = branchNodes.value.filter(node => !branchChargePersons.value[node.stepId])
1215
+ if (missingSelections.length > 0) {
1216
+ const missingNames = missingSelections.map(node => node.stepName).join('、')
1217
+ showToast(`请设置以下节点的处理人:${missingNames}`)
1218
+ return false
1219
+ }
1220
+ }
1221
+ else {
1222
+ // 单分支情况:检查是否选择了处理人
1223
+ if (!checkedNextStepPerson.value) {
1224
+ showToast('请设置下一环节处理人')
1225
+ return false
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // 统一获取目标步骤IDs
1231
+ let stepIds: any[]
1232
+ if (needMultipleBranchSelection.value) {
1233
+ // 多分支选择:获取所有分支的目标stepId(包括不需要选择人员的)
1234
+ if (!currentBranchActions.value) {
1235
+ currentBranchActions.value = getBranchActions()
1236
+ }
1237
+ stepIds = currentBranchActions.value.allBranchActions.map(action => action.to)
1238
+ }
1239
+ else if (calculatedTargetNode.value) {
1240
+ // 条件判断节点:使用计算出的目标节点
1241
+ stepIds = [calculatedTargetNode.value]
1242
+ }
1243
+ else {
1244
+ // 普通节点:使用传入的stepId
1245
+ stepIds = [stepId]
1246
+ }
1247
+ // 处理bug 有的时候没带到 branchChargePersons 先循环在同一地方处理
1248
+ stepIds.forEach((stepItemId) => {
1249
+ if (!branchChargePersons.value[stepItemId]) {
1250
+ const chargePerson = stepDefines.value.find(item => item.id === stepItemId)?.properties?.chargePerson
1251
+ branchChargePersons.value[stepItemId] = normalizeChargePersonFormat(chargePerson)
1252
+ }
1253
+ })
1254
+
1255
+ return {
1256
+ form: xAddForm.value.getFormData(),
1257
+ workflowId,
1258
+ activeStepId: currentStep.value.id,
1259
+ stepId, // 当前步骤ID
1260
+ stepIds, // 目标步骤IDs数组(统一格式)
1261
+ name: getStepNameByStepId(stepId),
1262
+ handler: getStepHandler(),
1263
+ handlerId: checkedNextStepPerson.value,
1264
+ needSelectPerson: isNeedSelectPerson.value,
1265
+ personList: nextStepPersonInfo.value?.personList || [],
1266
+ deadline: deadline.value,
1267
+ submitUser: userStore.getUserInfo().name,
1268
+ submitUserId: userStore.getUserInfo().id,
1269
+ branchChargePersons: branchChargePersons.value,
1270
+ }
1271
+ }
1272
+
1273
+ // 获取当前选择的负责人
1274
+ function getStepHandler() {
1275
+ let stepHandler
1276
+
1277
+ if (needMultipleBranchSelection.value) {
1278
+ // 多分支情况:返回所有分支的人员信息
1279
+ const selectedHandlers = []
1280
+ for (const node of branchNodes.value) {
1281
+ const branchData = branchChargePersons.value[node.stepId]
1282
+
1283
+ if (node.needSelectPerson && branchData) {
1284
+ // 需要选择人员的分支:显示选中的人员
1285
+ const personName = getBranchPersonName(node.stepId)
1286
+ selectedHandlers.push(`${node.stepName}:${personName}`)
1287
+ }
1288
+ else if (!node.needSelectPerson && branchData) {
1289
+ // 不需要选择人员的分支:显示角色/部门配置
1290
+ if (branchData.personList && branchData.personList.length > 0) {
1291
+ const personNames = branchData.personList.map(item => item.name).join(',')
1292
+ selectedHandlers.push(`${node.stepName}:${personNames}`)
1293
+ }
1294
+ }
1295
+ }
1296
+ stepHandler = selectedHandlers.join(';')
1297
+ }
1298
+ else {
1299
+ // 单分支情况:原有逻辑
1300
+ if (checkedNextStepPerson.value) {
1301
+ // 使用 value 找到对应的 label
1302
+ stepHandler = nextStepPersonOptions.value.find(item => item.value === checkedNextStepPerson.value)?.label
1303
+ }
1304
+ else if (nextStepPersonInfo.value && nextStepPersonInfo.value.personList && nextStepPersonInfo.value.personList.length > 0) {
1305
+ stepHandler = nextStepPersonInfo.value.personList.map(item => item.name).join(',')
1306
+ }
1307
+ }
1308
+
1309
+ return stepHandler
1310
+ }
1311
+
1312
+ // 获取分支节点选择的人员姓名
1313
+ function getBranchPersonName(stepId) {
1314
+ const personId = branchChargePersons.value[stepId]
1315
+ if (!personId)
1316
+ return ''
1317
+
1318
+ const node = branchNodes.value.find(n => n.stepId === stepId)
1319
+ if (!node)
1320
+ return ''
1321
+
1322
+ const person = node.chargePersonOptions.find(p => p.value === personId)
1323
+ return person ? person.label : ''
1324
+ }
1325
+
1326
+ // 生成工作流日志步骤变化描述
1327
+ function generateStepChangeText(fromStepId, toStepId) {
1328
+ // 如果是多分支场景,toStepId可能是数组或字符串
1329
+ if (needMultipleBranchSelection.value && Array.isArray(toStepId)) {
1330
+ const targetSteps = toStepId.map(id => `第${id}步: ${getStepNameByStepId(id)}`).join('、')
1331
+ return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> ${targetSteps}`
1332
+ }
1333
+ else if (needMultipleBranchSelection.value && typeof toStepId === 'string') {
1334
+ // 如果传入的是目标步骤名称字符串
1335
+ return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> ${toStepId}`
1336
+ }
1337
+ else {
1338
+ // 单步骤场景
1339
+ return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> 第${toStepId}步: ${getStepNameByStepId(toStepId)}`
1340
+ }
1341
+ }
1342
+
1343
+ // 生成多分支场景的步骤变化描述
1344
+ function generateBranchStepChangeText(fromStepId) {
1345
+ if (!needMultipleBranchSelection.value) {
1346
+ return generateStepChangeText(fromStepId, nextStepBtnToObj.value)
1347
+ }
1348
+
1349
+ // 使用所有分支节点生成描述
1350
+ const targetSteps = branchNodes.value.map(node => `第${node.stepId}步: ${node.stepName}`).join('、')
1351
+ return `第${fromStepId}步: ${getStepNameByStepId(fromStepId)} --> ${targetSteps}`
1352
+ }
1353
+
1354
+ function showConfirmDialogPromise(title: string, message: string): Promise<void> {
1355
+ if (mobileToolNames.value.length > 0) {
1356
+ const mobileToolMessage = `注意:当前环节工具箱中支持添加【${mobileToolNames.value}】`
1357
+ message = `${message}<br><span style=\"color:#1677ff;\">${mobileToolMessage}</span>`
1358
+ }
1359
+ return new Promise((resolve, reject) => {
1360
+ showConfirmDialog({
1361
+ title,
1362
+ message,
1363
+ allowHtml: true,
1364
+ })
1365
+ .then(() => resolve())
1366
+ .catch(() => {
1367
+ closeDialog()
1368
+ // eslint-disable-next-line prefer-promise-reject-errors
1369
+ reject()
1370
+ })
1371
+ })
1372
+ }
1373
+
1374
+ async function dialogClick() {
1375
+ switch (operationType.value) {
1376
+ case 'back':
1377
+ await backStep()
1378
+ break
1379
+ case 'skip':
1380
+ await skipStep()
1381
+ break
1382
+ case 'submit':
1383
+ await submitStep()
1384
+ break
1385
+ }
1386
+ }
1387
+
1388
+ async function backStep() {
1389
+ const notes = backNoteContent.value.trim()
1390
+ if (!notes) {
1391
+ showToast('退回请在备注中填写理由')
1392
+ return
1393
+ }
1394
+ return runLogic('updateWorkFlowState', {
1395
+ stepId: prevStepBtnToObj.value,
1396
+ workflowId,
1397
+ type: 'back',
1398
+ }, serviceName.value)
1399
+ .then(
1400
+ () => {
1401
+ saveWorkflowLog('退回', generateStepChangeText(currentStep.value.id, prevStepBtnToObj.value), { notes })
1402
+ showToast('退回成功')
1403
+ clearData()
1404
+ back()
1405
+ },
1406
+ (err) => {
1407
+ showToast('退回失败!')
1408
+ console.log(err)
1409
+ },
1410
+ )
1411
+ }
1412
+
1413
+ async function submitStep() {
1414
+ // 检查是否为非最后分支节点
1415
+ const branchStatus = isBranchLastNode.value
1416
+ const isWaitingBranch = branchStatus.isBranchNode && !branchStatus.isLastBranch
1417
+
1418
+ const extraData = getApplyStepExtraData(nextStepBtnToObj.value)
1419
+ if (!extraData) {
1420
+ return
1421
+ }
1422
+
1423
+ // 确认对话框
1424
+ const confirmContent = isWaitingBranch
1425
+ ? '确定完成当前分支么?完成后将等待其他分支,不会立即流转到下一环节。'
1426
+ : '确定提交么?提交之后数据不可更改!'
1427
+
1428
+ await showConfirmDialogPromise('提交确认', confirmContent)
1429
+
1430
+ xAddForm.value.asyncSubmit().then((res) => {
1431
+ submitForm(res).then((_) => {
1432
+ extraData.form = res.realForm
1433
+ runLogic('submitToNextStep', extraData, serviceName.value)
1434
+ .then(
1435
+ () => {
1436
+ let successMessage = '提交成功'
1437
+
1438
+ // 只有非等待分支节点才保存工作流日志
1439
+ if (!isWaitingBranch) {
1440
+ const branchStatus = isBranchLastNode.value
1441
+ const operation = branchStatus.isBranchNode && branchStatus.isLastBranch ? '分支汇合' : '提交'
1442
+
1443
+ const extra = {
1444
+ setHandler: getStepHandler(),
1445
+ setDeadline: deadline.value,
1446
+ notes: noteContent.value.trim(),
1447
+ }
1448
+ saveWorkflowLog(operation, generateBranchStepChangeText(currentStep.value.id), extra)
1449
+ }
1450
+ else {
1451
+ successMessage = '分支完成,等待其他分支完成后自动汇合'
1452
+ }
1453
+ showToast(successMessage)
1454
+ clearData()
1455
+ back()
1456
+ },
1457
+ (err) => {
1458
+ showToast('提交失败!')
1459
+ console.log(err)
1460
+ },
1461
+ )
1462
+ })
1463
+ }).catch((err) => {
1464
+ if (err && err.message === 'Form validation failed') {
1465
+ showToast('请检查表单必填项!')
1466
+ }
1467
+ else {
1468
+ // 对于其他错误,继续向上抛出
1469
+ throw err
1470
+ }
1471
+ })
1472
+ }
1473
+
1474
+ async function skipStep() {
1475
+ const extraData = getApplyStepExtraData(skipStepBtnToObj.value)
1476
+ if (!extraData) {
1477
+ return
1478
+ }
1479
+ // 跳过前确认
1480
+ await showConfirmDialogPromise('跳过确认', '确定跳过当前步骤吗?跳过后将进入下一环节。')
1481
+ xAddForm.value.asyncSubmit().then((res) => {
1482
+ submitForm(res).then((_) => {
1483
+ runLogic('submitToNextStep', extraData, serviceName.value)
1484
+ .then(
1485
+ () => {
1486
+ const extra = {
1487
+ setHandler: getStepHandler(),
1488
+ setDeadline: deadline.value,
1489
+ notes: noteContent.value.trim(),
1490
+ }
1491
+ saveWorkflowLog('跳过', generateBranchStepChangeText(currentStep.value.id), extra)
1492
+ showToast('提交成功')
1493
+ clearData()
1494
+ back()
1495
+ },
1496
+ (err) => {
1497
+ showToast('提交失败!')
1498
+ console.log(err)
1499
+ },
1500
+ )
1501
+ })
1502
+ }).catch((err) => {
1503
+ if (err && err.message === 'Form validation failed') {
1504
+ showToast('请检查表单必填项!')
1505
+ }
1506
+ else {
1507
+ // 对于其他错误,继续向上抛出
1508
+ throw err
1509
+ }
1510
+ })
1511
+ }
1512
+
1513
+ // 增加操作按钮组
1514
+ function addActionButtons() {
1515
+ allButtons.value = []
1516
+ // 根据按钮类型执行不同操作
1517
+ if (operationType.value === 'submit' && !isLastStep.value) {
1518
+ allButtons.value.push({
1519
+ text: '提交申请',
1520
+ icon: 'checked',
1521
+ isMoreButton: false,
1522
+ action: 'submit',
1523
+ type: 'primary',
1524
+ class: 'submit-btn',
1525
+ show: true,
1526
+ })
1527
+ }
1528
+ if (isShowSkipStepBtn.value && !isLastStep.value) {
1529
+ allButtons.value.push({
1530
+ text: `${skipBtnPrompt.value}`,
1531
+ icon: 'share',
1532
+ isMoreButton: false,
1533
+ action: 'skip',
1534
+ class: 'skip-btn',
1535
+ show: true,
1536
+ })
1537
+ }
1538
+ if (isLastStep.value) {
1539
+ allButtons.value.push({
1540
+ text: '确认完成',
1541
+ isMoreButton: false,
1542
+ action: 'done',
1543
+ type: 'primary',
1544
+ show: true,
1545
+ })
1546
+ }
1547
+ if (canSubmit.value && isShowPrevStepBtn.value && workflowDetailData.value?.f_state !== 1) {
1548
+ allButtons.value.push({
1549
+ text: `${prevBtnPrompt.value}`,
1550
+ icon: 'edit',
1551
+ isMoreButton: false,
1552
+ action: 'back',
1553
+ type: 'warning',
1554
+ class: 'prev-btn',
1555
+ show: true,
1556
+ })
1557
+ }
1558
+ }
1559
+
1560
+ // 扩展按钮ActionSheet 选项选择
1561
+ async function onActionSheetSelect(item: any) {
1562
+ showMoreMenu.value = false
1563
+ await handleButtonClick(item)
1564
+ }
1565
+
1566
+ // 工具集ActionSheet 选项选择
1567
+ function onActionToolsSelect(item: any) {
1568
+ showToolsMenu.value = false
1569
+ if (item.type === 'tools') {
1570
+ const formData = xAddForm.value?.form || {}
1571
+ stepStore.setWorkflowId(workflowId)
1572
+ stepStore.setStepDefines(stepDefines.value)
1573
+ stepStore.setFormData(formData)
1574
+ stepStore.setCurrentStepData(currentStep.value)
1575
+ stepStore.setReadOnly(showType.value === 1)
1576
+ if (item.func) {
1577
+ stepStore.setCallbackFunction(async (data: any) => {
1578
+ return await executeStrFunctionByContext(
1579
+ getCurrentInstance(),
1580
+ item.func,
1581
+ [data, stepDefines.value, formData, currentStep.value, util, runLogic, getConfigByName],
1582
+ )
1583
+ })
1584
+ }
1585
+ router.push({
1586
+ name: item.value,
1587
+ })
1588
+ }
1589
+ else if (item.type === 'clearTempData') {
1590
+ showToast('清除临时数据成功')
1591
+ stepStore.setCallbackFunction(undefined)
1592
+ }
1593
+ else if (item.type === 'backHome') {
1594
+ stepStore.setCallbackFunction(undefined)
1595
+ router.push({
1596
+ path: '/',
1597
+ })
1598
+ }
1599
+ else if (item.type === 'applyDetail') { // 小工具 流程查看
1600
+ stepStore.setCallbackFunction(undefined)
1601
+ router.push({
1602
+ name: 'applyDetail',
1603
+ query: {
1604
+ workflowid: workflowId,
1605
+ },
1606
+ })
1607
+ }
1608
+ }
1609
+
1610
+ // 处理业务相关的表单函数
1611
+ function formFunc(func: string) {
1612
+ if (props.onFormFunc) {
1613
+ props.onFormFunc(func)
1614
+ }
1615
+ }
1616
+
1617
+ // 处理地址选择
1618
+ function selectAddressFunc() {
1619
+ if (props.onSelectAddress) {
1620
+ props.onSelectAddress()
1621
+ }
1622
+ }
1623
+
1624
+ provide('workflowHandleWrap', {
1625
+ branchChargePersons,
1626
+ calculatedTargetNode,
1627
+ isNeedSelectPerson,
1628
+ needMultipleBranchSelection,
1629
+ branchNodes,
1630
+ checkedNextStepPerson,
1631
+ checkedNextStepPersonName,
1632
+ nextStepPersonOptions,
1633
+ getStepNameByStepId,
1634
+ })
1635
+
1636
+ // 暴露方法给父组件
1637
+ defineExpose({
1638
+ xAddForm,
1639
+ setForm: (data) => {
1640
+ xAddForm.value?.setForm(data)
1641
+ },
1642
+ workflowDetailData,
1643
+ })
1644
+
1645
+ // 从缓存中还原之后 执行编辑路由回调函数
1646
+ async function callbackFunction() {
1647
+ if (stepStore.getCallbackFunction()) {
1648
+ try {
1649
+ const func = stepStore.getCallbackFunction()
1650
+ await func({ workflowId })
1651
+ }
1652
+ catch (error) {
1653
+ console.error('func 执行出错:', error)
1654
+ }
1655
+ finally {
1656
+ stepStore.setCallbackFunction(undefined)
1657
+ }
1658
+ }
1659
+ }
1660
+
1661
+ onActivated(async () => {
1662
+ await callbackFunction()
1663
+ })
1664
+
1665
+ onBeforeMount(async () => {
1666
+ if (!workflowId || !stepId) {
1667
+ router.push({
1668
+ path: '/',
1669
+ })
1670
+ }
1671
+ else {
1672
+ await initComponent()
1673
+ }
1674
+ })
1675
+ </script>
1676
+
1677
+ <template>
1678
+ <div v-if="isFinish" id="apply-step">
1679
+ <div v-if="currentStep.properties?.otherProperty?.toast" class="info-card">
1680
+ <VanIcon name="info-o" color="#1890ff" class="info-icon" />
1681
+ <div class="info-content">
1682
+ <div> {{ currentStep.properties?.otherProperty?.toast }} </div>
1683
+ </div>
1684
+ </div>
1685
+ <VanCellGroup title="步骤信息">
1686
+ <VanCell title="步骤名称" :value="currentStep.name" />
1687
+ <VanCell title="负责人" :value="currentStep.handler" />
1688
+ <VanCell v-if="currentStep.date" title="填报时间" :value="currentStep.date" />
1689
+ <VanCell title="截止时间" :value="currentStep.deadline" />
1690
+ </VanCellGroup>
1691
+ <VanCellGroup v-if="currentStep.back" title="退回信息">
1692
+ <VanCell title="说明" value="流程被退回,请重新填写信息发起提交" />
1693
+ <VanCell title="操作人" :value="currentStep.back.f_operator" />
1694
+ <VanCell title="操作时间" :value="currentStep.back.f_date" />
1695
+ <VanCell title="退回原因" :value="currentStep.back.f_notes" />
1696
+ </VanCellGroup>
1697
+ <template v-if="showType === 0">
1698
+ <HistoryFormData
1699
+ v-if="historyFormCompletedDataPreview"
1700
+ :data="historyFormCompletedDataPreview.data"
1701
+ :step-attachments="historyFormCompletedDataPreview.stepAttachments"
1702
+ />
1703
+ <XForm
1704
+ ref="xAddForm"
1705
+ mode="新增"
1706
+ group-title="填写表单"
1707
+ :is-group-form="true"
1708
+ :form-data="xAddFormData"
1709
+ :group-form-items="stepFormDefine"
1710
+ :submit-button="false"
1711
+ :service-name="serviceName"
1712
+ :is-handle-form-key="false"
1713
+ :param-logic-name-param="{ workflowId }"
1714
+ @select-address="selectAddressFunc"
1715
+ @x-form-item-emit-func="formFunc"
1716
+ >
1717
+ <template #extraCellGroup>
1718
+ <VanCellGroup title="工单信息">
1719
+ <!-- 备注信息 -->
1720
+ <VanField
1721
+ v-model="noteContent"
1722
+ rows="3"
1723
+ autosize
1724
+ label="备注"
1725
+ type="textarea"
1726
+ maxlength="200"
1727
+ placeholder="填写本环节备注事项"
1728
+ show-word-limit
1729
+ />
1730
+ </VanCellGroup>
1731
+ </template>
1732
+ <template #extraActionSpace>
1733
+ <div class="form-footer-fixed">
1734
+ <!-- 按钮容器 -->
1735
+ <div class="button-container">
1736
+ <!-- 显示的主按钮 (最多3个) -->
1737
+ <template v-for="(button, index) in displayedButtons" :key="index">
1738
+ <VanButton
1739
+ :type="button.type || 'default'"
1740
+ class="flex-1" :class="[
1741
+ button.class,
1742
+ { 'more-button': button.isMoreButton },
1743
+ ]"
1744
+ @click="handleButtonClick(button)"
1745
+ >
1746
+ <template v-if="button.icon">
1747
+ <VanIcon class="btn-icon" :name="button.icon" />
1748
+ </template>
1749
+ <template v-if="button.isMoreButton">
1750
+ <VanIcon name="ellipsis" />
1751
+ </template>
1752
+ <template v-else>
1753
+ {{ button.text }}
1754
+ </template>
1755
+ </VanButton>
1756
+ </template>
1757
+ </div>
1758
+ </div>
1759
+ </template>
1760
+ </XForm>
1761
+ <!-- 扩展操作弹出区域 -->
1762
+ <VanActionSheet
1763
+ v-model:show="showMoreMenu"
1764
+ :actions="actionSheetActions"
1765
+ cancel-text="取消"
1766
+ @select="onActionSheetSelect"
1767
+ @cancel="showMoreMenu = false"
1768
+ />
1769
+ </template>
1770
+ <template v-else-if="showType === 1">
1771
+ <VanCellGroup title="填写历史">
1772
+ <template v-if="stepFormData && stepFormData.length > 0">
1773
+ <template v-for="(item, i) in stepFormData" :key="`${stepFormData.label} - ${i}`">
1774
+ <VanCell :title="item.label" :value="item.value" />
1775
+ </template>
1776
+ </template>
1777
+ <template v-else>
1778
+ <VanEmpty description="该步骤暂无数据" />
1779
+ </template>
1780
+ </VanCellGroup>
1781
+ <VanCellGroup title="备注">
1782
+ <VanCell title="备注信息" :value="currentStep.note" />
1783
+ </VanCellGroup>
1784
+ <VanCellGroup v-if="stepFilesData.length > 0" title="附件">
1785
+ <FileItem :files="stepFilesData" />
1786
+ </VanCellGroup>
1787
+ <VanCellGroup v-if="stepImageData.length > 0" title="图片">
1788
+ <FileItem :files="stepImageData" />
1789
+ </VanCellGroup>
1790
+ </template>
1791
+ <!-- 工具集弹出区域 -->
1792
+ <VanActionSheet
1793
+ v-model:show="showToolsMenu"
1794
+ :actions="toolActions"
1795
+ cancel-text="取消"
1796
+ @select="onActionToolsSelect"
1797
+ @cancel="showToolsMenu = false"
1798
+ />
1799
+ <!-- 磁吸工具按钮 -->
1800
+ <VanFloatingBubble
1801
+ axis="xy"
1802
+ icon="more-o"
1803
+ magnetic="x"
1804
+ @click="handleToolsPopoverClick"
1805
+ />
1806
+ </div>
1807
+ <div v-else-if="isError">
1808
+ <VanEmpty :image="errorImage" :description="errorMsg">
1809
+ <VanButton round type="primary" class="bottom-button" @click="back">
1810
+ 返回上一页
1811
+ </VanButton>
1812
+ </VanEmpty>
1813
+ </div>
1814
+ <div v-else-if="isLoading">
1815
+ <BeautifulLoading />
1816
+ </div>
1817
+ <VanActionSheet v-model:show="dialogVisible" class="actionSheetBox" :close-on-click-overlay="false" :close-on-click-action="true" :title="dialogTitle">
1818
+ <div class="actionSheetContent">
1819
+ <template v-if="operationType === 'submit' || operationType === 'skip'">
1820
+ <div class="dialog-box">
1821
+ <!-- 智能分支人员选择组件 - 只有非分支节点或最后一个分支才显示 -->
1822
+ <template v-if="!isBranchLastNode.isBranchNode || isBranchLastNode.isLastBranch">
1823
+ <PersonSelect />
1824
+ <XFormItem
1825
+ v-model="deadline"
1826
+ mode="新增"
1827
+ label="截止时间"
1828
+ :attr="{ name: '截止时间', type: 'datePicker', rule: { required: 'true' } }"
1829
+ />
1830
+ </template>
1831
+ </div>
1832
+ </template>
1833
+ <template v-else-if="operationType === 'back'">
1834
+ <VanField
1835
+ v-model="backNoteContent"
1836
+ rows="3"
1837
+ autosize
1838
+ label="退回原因"
1839
+ type="textarea"
1840
+ maxlength="200"
1841
+ placeholder="填写退回原因/备注"
1842
+ show-word-limit
1843
+ />
1844
+ </template>
1845
+ </div>
1846
+ <div class="actionSheetFoot">
1847
+ <VanButton type="default" @click="() => { dialogVisible = false }">
1848
+ 取消
1849
+ </VanButton>
1850
+ <VanButton type="primary" @click="dialogClick">
1851
+ 确认
1852
+ </VanButton>
1853
+ </div>
1854
+ </VanActionSheet>
1855
+ </template>
1856
+
1857
+ <style scoped lang="less">
1858
+ .x-form-container {
1859
+ padding-bottom: 90px;
1860
+ display: block;
1861
+ max-height: initial;
1862
+ }
1863
+ .form-footer-fixed {
1864
+ position: fixed;
1865
+ bottom: 0;
1866
+ left: 0;
1867
+ width: 100%;
1868
+
1869
+ padding: 12px;
1870
+ box-sizing: border-box;
1871
+
1872
+ /* 阴影效果 - 向上投射 */
1873
+ box-shadow:
1874
+ 0 -4px 12px rgba(0, 0, 0, 0.1),
1875
+ 0 -2px 4px rgba(0, 0, 0, 0.06);
1876
+
1877
+ /* 毛玻璃背景 (移动端高级效果) */
1878
+ background-color: rgba(255, 255, 255, 0.85);
1879
+ -webkit-backdrop-filter: blur(10px);
1880
+ backdrop-filter: blur(10px);
1881
+
1882
+ /* 边框增强立体感 */
1883
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
1884
+ }
1885
+ .button-container {
1886
+ display: flex;
1887
+ gap: 8px;
1888
+
1889
+ .van-button {
1890
+ flex: 1; /* 所有按钮等分宽度 */
1891
+ min-width: 0; /* 防止文本溢出 */
1892
+
1893
+ /* 按钮文字超出时显示省略号 */
1894
+ overflow: hidden;
1895
+ text-overflow: ellipsis;
1896
+ white-space: nowrap;
1897
+ }
1898
+
1899
+ .more-button {
1900
+ flex: 0 0 50px; /* 更多按钮固定宽度 */
1901
+ padding: 0;
1902
+ }
1903
+
1904
+ .submit-btn {
1905
+ background-color: #1890ff;
1906
+ border: 0;
1907
+ }
1908
+
1909
+ .skip-btn {
1910
+ background-color: #52c41a;
1911
+ border: 0;
1912
+ color: #fff;
1913
+ }
1914
+
1915
+ .prev-btn {
1916
+ border: 0;
1917
+ }
1918
+
1919
+ .btn-icon {
1920
+ padding-right: 6px;
1921
+ }
1922
+ }
1923
+ .info-card {
1924
+ display: flex;
1925
+ align-items: flex-start;
1926
+ background: #eaf6ff;
1927
+ border-left: 3px solid #1677ff;
1928
+ border-radius: 12px;
1929
+ padding: 10px 16px 8px 14px;
1930
+ margin: 16px 16px 0 16px;
1931
+ font-size: 15px;
1932
+ color: #1677ff;
1933
+ min-height: 44px;
1934
+ box-shadow: none;
1935
+ border-top: none;
1936
+ border-right: none;
1937
+ border-bottom: none;
1938
+ }
1939
+ .info-icon {
1940
+ font-size: 20px;
1941
+ margin-right: 8px;
1942
+ margin-top: 2px;
1943
+ color: #1677ff;
1944
+ flex-shrink: 0;
1945
+ }
1946
+ .info-content {
1947
+ display: flex;
1948
+ flex-direction: column;
1949
+ justify-content: center;
1950
+ }
1951
+ .info-content > div:first-child {
1952
+ font-weight: bold;
1953
+ color: #1677ff;
1954
+ font-size: 15px;
1955
+ margin-bottom: 2px;
1956
+ }
1957
+ .info-content > div:last-child {
1958
+ font-size: 14px;
1959
+ color: #1677ff;
1960
+ }
1961
+ .actionSheetBox {
1962
+ .actionSheetContent {
1963
+ padding-bottom: 0;
1964
+ }
1965
+ .actionSheetFoot {
1966
+ display: grid;
1967
+ grid-template-columns: auto auto;
1968
+ width: 100%;
1969
+ margin-top: 20px;
1970
+ :deep(.van-button) {
1971
+ border-radius: 0 !important;
1972
+ }
1973
+ }
1974
+ }
1975
+ </style>