@lambo-design-mobile/workflow-approve 1.0.0-beta.23 → 1.0.0-beta.25

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.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,19 @@
1
1
  # Changelog
2
+ ## [1.0.0-beta.25](http://git.inspur.com/ecbh/lambo-design/lambo-design/-/compare/@lambo-design-mobile/workflow-approve@1.0.0-beta.24...@lambo-design-mobile/workflow-approve@1.0.0-beta.25) (2026-03-18)
3
+
4
+
5
+ ### ✨ Features | 新功能
6
+
7
+ * **审批组件:** 选人框查询问题修复 ([7712bab](http://git.inspur.com/ecbh/lambo-design/lambo-design/-/commit/7712babcead1d9838ef17672370fe2a1a37f828d))
8
+ * **审批组件:** 增加驳回判断优化 ([90c270b](http://git.inspur.com/ecbh/lambo-design/lambo-design/-/commit/90c270b13f13a44d83502c0081d885a8083be75e))
9
+
10
+ ## [1.0.0-beta.24](http://git.inspur.com/ecbh/lambo-design/lambo-design/-/compare/@lambo-design-mobile/workflow-approve@1.0.0-beta.23...@lambo-design-mobile/workflow-approve@1.0.0-beta.24) (2026-03-03)
11
+
12
+
13
+ ### ✨ Features | 新功能
14
+
15
+ * **审批组件:** 增加驳回至会签指定节点功能;流程跟踪增加预测路线功能 ([a5a85b5](http://git.inspur.com/ecbh/lambo-design/lambo-design/-/commit/a5a85b582a051cb2f88259a0e8680e6f033f6254))
16
+
2
17
  ## [1.0.0-beta.23](http://git.inspur.com/ecbh/lambo-design/lambo-design/-/compare/@lambo-design-mobile/workflow-approve@1.0.0-beta.22...@lambo-design-mobile/workflow-approve@1.0.0-beta.23) (2026-02-26)
3
18
 
4
19
 
package/api.js CHANGED
@@ -140,6 +140,19 @@ export const printData = (applyId, instanceId, procId) => {
140
140
  })
141
141
  }
142
142
 
143
+ // 渲染未来节点(预测路线)
144
+ export const renderFutureNode = (processInstanceId, currentTaskId) => {
145
+ const params = {
146
+ processInstanceId,
147
+ currentTaskId
148
+ };
149
+ return ajax.request({
150
+ url: config.smartFlowServerContext + "/manage/processTodo/renderFutureNode",
151
+ method: 'get',
152
+ params: params,
153
+ })
154
+ }
155
+
143
156
  export const getPrintData = (applyId, procId) => {
144
157
  const params = {
145
158
  applyId: applyId,
@@ -404,3 +417,15 @@ export const listAll = (procId, applyId, taskNode, taskId) => {
404
417
  params: params,
405
418
  });
406
419
  }
420
+
421
+ export const getDoneDetail = (taskNode, applyId) => {
422
+ const params = {
423
+ taskNode: taskNode,
424
+ applyId: applyId,
425
+ };
426
+ return ajax.request({
427
+ url: config.smartFlowServerContext + "/manage/processDone/getDoneDetail",
428
+ method: 'get',
429
+ params: params,
430
+ });
431
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambo-design-mobile/workflow-approve",
3
- "version": "1.0.0-beta.23",
3
+ "version": "1.0.0-beta.25",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "lambo",
@@ -15,8 +15,8 @@
15
15
  "devDependencies": {
16
16
  "standard-version": "^9.5.0",
17
17
  "@lambo-design-mobile/lambo-scan-code": "^1.0.0-beta.1",
18
- "@lambo-design-mobile/shared": "^1.0.0-beta.21",
19
- "@lambo-design-mobile/upload-file": "^1.0.0-beta.16"
18
+ "@lambo-design-mobile/upload-file": "^1.0.0-beta.16",
19
+ "@lambo-design-mobile/shared": "^1.0.0-beta.21"
20
20
  },
21
21
  "scripts": {
22
22
  "release": "pnpm release-beta && git push --follow-tags && pnpm re-publish",
@@ -115,23 +115,16 @@
115
115
  <!-- 底部按钮 等其他内容 -->
116
116
  <div>
117
117
  <van-popup v-model="popupShow" style="padding-top: 40px" closeable round position="bottom">
118
- <template v-for="(button, index) of operationButtons">
118
+ <template v-for="(button, index) of operationButtons.moreOperations">
119
119
  <div :class="button.class" @click="button.action">
120
120
  {{ button.label }}
121
121
  </div>
122
122
  </template>
123
123
  </van-popup>
124
124
  <div class="custom-bottom-bar">
125
- <div v-if="!isDetail && operationButtons.length > 1" class="bar-item" @click="onMore">更多</div>
126
- <div v-else-if="!isDetail && operationButtons.length === 1" class="bar-item" @click="handleSingleButton()">
127
- {{ getSingleButtonText() }}
125
+ <div v-for="(button, index) of operationButtons.bottomButtons" :class="button.class" @click="button.action">
126
+ {{ button.label }}
128
127
  </div>
129
- <div class="bar-item" @click="onTrack">流程跟踪</div>
130
- <div v-if="!isDetail && !revokeDelegateTask && !appointTask && !coSignVotingTask" class="bar-item approve" @click="audit('30')">{{ getAuditButtonStatus(30) }}</div>
131
- <div v-if="!isDetail && revokeDelegateTask" class="bar-item approve" @click="audit('62')">{{ getAuditButtonStatus(62) }}</div>
132
- <div v-if="!isDetail && appointTask" class="bar-item approve" @click="audit('61')">{{ getAuditButtonStatus(61) }}</div>
133
- <div v-if="coSignVotingTask" class="bar-item disagree" @click="audit('32')">{{ getAuditButtonStatus(32) }}</div>
134
- <div v-if="coSignVotingTask" class="bar-item approve" @click="audit('31')">{{ getAuditButtonStatus(31) }}</div>
135
128
  </div>
136
129
  </div>
137
130
  <van-popup @click-close-icon="handleSelect('cancel')" v-model="nextNodePopupShow"
@@ -292,6 +285,9 @@
292
285
  <van-radio :name='index'/>
293
286
  </div>
294
287
  </div>
288
+ <div v-if="showRejectToSelected" class="table-row">
289
+ <van-field @click="handleSelectRejectUser" label="选择驳回人员" readonly placeholder="点击选择" :value="rejectUsers.name"></van-field>
290
+ </div>
295
291
  <div v-if="showProcessControl" class="reject-attribute">
296
292
  <div>驳回的节点通过后:</div>
297
293
  <van-radio-group v-model="rejectAttribute">
@@ -302,13 +298,21 @@
302
298
  </van-radio-group>
303
299
  </van-dialog>
304
300
  <van-dialog v-model="rejectAttributesBoxShow" @confirm="processRejectNode()" @close="rejectAttributesDialogShowClose()"
305
- title="驳回的节点通过后" show-cancel-button>
306
- <div class="reject-attribute">
301
+ title="驳回设置" show-cancel-button>
302
+ <div v-if="showProcessControl" class="reject-attribute">
303
+ <div>驳回的节点通过后:</div>
307
304
  <van-radio-group v-model="rejectAttribute">
308
305
  <van-radio v-for="(item, index) in rejectAttributeList" :key="index" :name="item.type">{{ item.name }}</van-radio>
309
306
  </van-radio-group>
310
307
  </div>
308
+ <div v-if="showRejectToSelected" class="reject-attribute">
309
+ <van-field @click="handleSelectRejectUser" label="选择驳回人员" readonly placeholder="点击选择" :value="rejectUsers.name"></van-field>
310
+ </div>
311
311
  </van-dialog>
312
+ <van-popup v-if="selectRejectUserPopupShow" v-model="selectRejectUserPopupShow" closeable round position="bottom"
313
+ :style="{ height: '80%' }">
314
+ <select-handle title="选择驳回人员" :multi-select="true" :procType="procType" :user-list="rejectUserList" @selectHandle="selectRejectUserHandle"></select-handle>
315
+ </van-popup>
312
316
  </div>
313
317
  </template>
314
318
 
@@ -330,7 +334,8 @@ import {
330
334
  getUnapprovedListOfMultiNode,
331
335
  getProcessTodoList,
332
336
  getProcessAttributes,
333
- listAll
337
+ listAll,
338
+ getDoneDetail
334
339
  } from "../api";
335
340
  import {Dialog, Toast} from "vant";
336
341
  import UploadFile from '@lambo-design-mobile/upload-file';
@@ -353,9 +358,59 @@ export default {
353
358
  showAuditOpinion(){
354
359
  return this.handleButtons && (this.handleButtons.includes('auditOpinion') || this.handleButtons.includes('appointHandler') || this.handleButtons.includes('attachmentFile'))
355
360
  },
356
- operationButtons(){
357
- const showButtons = !this.appointTask && !this.rejectedTask && !this.revokeDelegateTask && !this.coSignVotingTask
358
- const handleButtons = [
361
+ operationButtons() {
362
+ // 流程跟踪按钮
363
+ const processTraceButton = {
364
+ id: 'processTrace',
365
+ label: '流程跟踪',
366
+ condition: this.handleButtons?.includes('processTrace'),
367
+ class: 'bar-item',
368
+ action: () => this.onTrack(),
369
+ }
370
+
371
+ // 审批按钮
372
+ const auditButtonDefinitions = [
373
+ {
374
+ id: 'auditTo30',
375
+ label: this.getAuditButtonStatus(30),
376
+ condition: this.handleButtons?.includes('auditTo30') && !this.isDetail && !this.revokeDelegateTask && !this.appointTask && !this.coSignVotingTask,
377
+ class: 'bar-item approve',
378
+ action: () => this.audit('30'),
379
+ },
380
+ {
381
+ id: 'auditTo62',
382
+ label: this.getAuditButtonStatus(62),
383
+ condition: !this.isDetail && this.revokeDelegateTask,
384
+ class: 'bar-item approve',
385
+ action: () => this.audit('62'),
386
+ },
387
+ {
388
+ id: 'auditTo61',
389
+ label: this.getAuditButtonStatus(61),
390
+ condition: !this.isDetail && this.appointTask,
391
+ class: 'bar-item approve',
392
+ action: () => this.audit('61'),
393
+ },
394
+ {
395
+ id: 'auditTo32',
396
+ label: this.getAuditButtonStatus(32),
397
+ condition: this.coSignVotingTask,
398
+ class: 'bar-item disagree',
399
+ action: () => this.audit('32'),
400
+ },
401
+ {
402
+ id: 'auditTo31',
403
+ label: this.getAuditButtonStatus(31),
404
+ condition: this.coSignVotingTask,
405
+ class: 'bar-item approve',
406
+ action: () => this.audit('31'),
407
+ },
408
+ ]
409
+
410
+ // 实际可以展示的审批按钮
411
+ const validAuditButtons = auditButtonDefinitions.filter(item => item.condition)
412
+ const showButtons = !this.appointTask && !this.rejectedTask && !this.revokeDelegateTask && !this.coSignVotingTask && !this.isDetail
413
+ const handleButtonsDefinitions = [
359
414
  {
360
415
  id: 'delegateTask',
361
416
  label: this.getAuditButtonStatus(84),
@@ -363,6 +418,7 @@ export default {
363
418
  this.handleButtons?.includes('delegateTask') &&
364
419
  showButtons,
365
420
  class: 'popup-option',
421
+ barClass: 'bar-item approve',
366
422
  action: () => this.audit('84'),
367
423
  },
368
424
  {
@@ -372,6 +428,7 @@ export default {
372
428
  this.handleButtons?.includes('addMultitaskInstance') &&
373
429
  showButtons,
374
430
  class: 'popup-option',
431
+ barClass: 'bar-item approve',
375
432
  action: () => this.showUpdateMultitaskInstanceModal('81'),
376
433
  },
377
434
  {
@@ -381,6 +438,7 @@ export default {
381
438
  this.handleButtons?.includes('reductionMultitaskInstance') &&
382
439
  showButtons,
383
440
  class: 'popup-option',
441
+ barClass: 'bar-item disagree',
384
442
  action: () => this.showUpdateMultitaskInstanceModal('83'),
385
443
  },
386
444
  {
@@ -388,6 +446,7 @@ export default {
388
446
  label: this.getAuditButtonStatus(70),
389
447
  condition: this.handleButtons && this.handleButtons.includes('auditTo70') && showButtons,
390
448
  class: 'popup-option',
449
+ barClass: 'bar-item disagree',
391
450
  action: () => this.audit('70'),
392
451
  },
393
452
  {
@@ -395,6 +454,7 @@ export default {
395
454
  label: this.getAuditButtonStatus(40),
396
455
  condition: this.handleButtons && this.handleButtons.includes('auditTo40') && showButtons,
397
456
  class: 'popup-option highlighted',
457
+ barClass: 'bar-item disagree',
398
458
  action: () => this.audit('40'),
399
459
  },
400
460
  {
@@ -402,6 +462,7 @@ export default {
402
462
  label: this.getAuditButtonStatus(90),
403
463
  condition: this.handleButtons && this.handleButtons.includes('auditTo90') && showButtons,
404
464
  class: 'popup-option',
465
+ barClass: 'bar-item disagree',
405
466
  action: () => this.audit('90'),
406
467
  },
407
468
  {
@@ -409,6 +470,7 @@ export default {
409
470
  label: this.getAuditButtonStatus(80),
410
471
  condition: this.handleButtons && this.handleButtons.includes('auditTo80') && showButtons,
411
472
  class: 'popup-option',
473
+ barClass: 'bar-item approve',
412
474
  action: () => this.audit('80'),
413
475
  },
414
476
  {
@@ -416,6 +478,7 @@ export default {
416
478
  label: this.getAuditButtonStatus(82),
417
479
  condition: this.handleButtons && this.handleButtons.includes('auditTo82') && showButtons,
418
480
  class: 'popup-option',
481
+ barClass: 'bar-item approve',
419
482
  action: () => this.audit('82'),
420
483
  },
421
484
  {
@@ -423,10 +486,46 @@ export default {
423
486
  label: this.getAuditButtonStatus(50),
424
487
  condition: this.handleButtons && this.handleButtons.includes('auditTo50') && showButtons,
425
488
  class: 'popup-option',
489
+ barClass: 'bar-item disagree',
426
490
  action: () => this.audit('50'),
427
491
  },
428
492
  ]
429
- return handleButtons.filter(item => item.condition)
493
+
494
+ // 实际更多操作按钮
495
+ let moreOperations = handleButtonsDefinitions.filter(item => item.condition)
496
+
497
+ let moreButtons = []
498
+ let borrowedButton = null;
499
+ // 如果审批按钮为空,且更多操作里有按钮,则借调最后一个
500
+ if (validAuditButtons.length === 0 && moreOperations.length > 0) {
501
+ borrowedButton = moreOperations.pop();
502
+ borrowedButton.class = borrowedButton.barClass
503
+ }
504
+ if (!this.isDetail) {
505
+ if (moreOperations.length > 1) {
506
+ moreButtons.push({
507
+ id: 'onMore',
508
+ label: '更多',
509
+ condition: true,
510
+ class: 'bar-item',
511
+ action: () => this.onMore(),
512
+ })
513
+ } else if (moreOperations.length === 1) {
514
+ moreButtons.push({
515
+ id: moreOperations[0].id,
516
+ label: moreOperations[0].label,
517
+ condition: true,
518
+ class: 'bar-item',
519
+ action: () => moreOperations[0].action(),
520
+ })
521
+ }
522
+ }
523
+ const baseList = [...moreButtons, processTraceButton]
524
+ if (borrowedButton) {
525
+ baseList.splice(2, 0, borrowedButton);
526
+ }
527
+ const bottomButtons = [...baseList, ...validAuditButtons]
528
+ return { bottomButtons, moreOperations }
430
529
  }
431
530
  },
432
531
  components: {ApprovalNodeCell, SelectNormalList, SelectOrganize, Tree, SelectHandle, UploadFile, SelectHandleCard},
@@ -562,6 +661,7 @@ export default {
562
661
  permissionsScope: {},
563
662
  selectOrganizePopupShow: false,
564
663
  selectCandUserPopupShow: false,
664
+ selectRejectUserPopupShow: false,
565
665
  selectUserPopupShow: false,
566
666
  selectRolePopupShow: false,
567
667
  selectReductionUserPopupShow: false,
@@ -575,20 +675,24 @@ export default {
575
675
  organTreeType: '',
576
676
  action: (checkResult) => { console.log('未定义回调方法') },
577
677
  },
678
+ showRejectToSelected: false,
578
679
  showProcessControl: false,
579
680
  rejectAttributesBoxShow: false,
580
681
  rejectAttribute: 'inSequence',
581
682
  rejectAttributeList: [],
582
683
  taskAuditUserList: [],
684
+ rejectUserList: [],
583
685
  targetTaskNodeProcessControl: '',
686
+ rejectUsers: {
687
+ name: '全部办理人',
688
+ id: 'all'
689
+ },
584
690
  };
585
691
  },
586
692
  methods: {
587
693
  getAuditStatus,
588
694
  initData() {
589
- if (!this.handleButtons){
590
- this.getHandleButtons();
591
- }
695
+ this.getHandleButtons();
592
696
  if (!this.isDetail) this.getTaskStatus();
593
697
  this.getProcessHistory();
594
698
  this.getNextNodes();
@@ -639,10 +743,10 @@ export default {
639
743
  if (resp.data.code === '200') {
640
744
  const todoData = resp.data.data.rows[0]
641
745
  // 委派任务状态
642
- self.revokeDelegateTask = todoData.delegateStatus ? todoData.delegateStatus === '10' : false
643
- self.appointTask = todoData.delegateStatus ? todoData.delegateStatus === '20' : false
746
+ self.revokeDelegateTask = todoData?.delegateStatus ? todoData.delegateStatus === '10' : false
747
+ self.appointTask = todoData?.delegateStatus ? todoData.delegateStatus === '20' : false
644
748
  // 判断节点是否是被驳回的节点
645
- const taskExtensionAttributes = todoData.taskExtensionAttributes ? JSON.parse(todoData.taskExtensionAttributes) : ''
749
+ const taskExtensionAttributes = todoData?.taskExtensionAttributes ? JSON.parse(todoData.taskExtensionAttributes) : ''
646
750
  if (taskExtensionAttributes && taskExtensionAttributes.rejectAttributes) {
647
751
  const rejectAttributes = taskExtensionAttributes.rejectAttributes
648
752
  if (rejectAttributes && rejectAttributes.type && rejectAttributes.type != 'inSequence') self.rejectedTask = true
@@ -654,8 +758,8 @@ export default {
654
758
  getNodeData(this.procId, this.taskNode).then(resp => {
655
759
  if (resp.data.code === '200') {
656
760
  const data = resp.data.data[0]
657
- this.handleButtons = data.handleButtons ? data.handleButtons : ["processTrace", "auditHistory", "auditOpinion", "attachmentFile", "auditTo30", "auditTo70", "auditTo40", "auditTo90", "auditTo80", "auditTo82", "auditTo50"];
658
- this.coSignVotingTask = data.handleButtons && data.handleButtons.includes('coSignVoting')
761
+ this.handleButtons = this.handleButtons ? this.handleButtons : data.handleButtons ? data.handleButtons : ["processTrace", "auditHistory", "auditOpinion", "attachmentFile", "auditTo30", "auditTo70", "auditTo40", "auditTo90", "auditTo80", "auditTo82", "auditTo50"];
762
+ this.coSignVotingTask = this.handleButtons && this.handleButtons.includes('coSignVoting')
659
763
  this.nextAssigneeSelectionType = data.nextAssigneeSelectionType;
660
764
  this.passName = data.handleName;
661
765
  this.rejectName = data.rejectName;
@@ -1011,6 +1115,8 @@ export default {
1011
1115
  procId: this.procId,
1012
1116
  instanceId: this.instanceId,
1013
1117
  taskNode: this.taskNode,
1118
+ taskId: this.taskId,
1119
+ predictButtonEnabled: this.handleButtons && this.handleButtons.includes('predictiveRoute'),
1014
1120
  }
1015
1121
  })
1016
1122
  },
@@ -1051,13 +1157,13 @@ export default {
1051
1157
  console.log('点击了审批:', auditResult);
1052
1158
 
1053
1159
  if (this.approvalForm.auditOpinion === '' || this.approvalForm.auditOpinion == null) {
1054
- if (!this.handleButtons || (this.handleButtons.includes('auditOpinion') && this.auditResult !== '30' && this.auditResult !== '31')) {
1160
+ if (!this.handleButtons || (this.handleButtons.includes('auditOpinion') && auditResult != '30' && auditResult != '31')) {
1055
1161
  this.updateActiveTab()
1056
1162
  this.approvalError = true
1057
1163
  Toast({message: '请输入审批意见', duration: '500'});
1058
1164
  return
1059
1165
  } else {
1060
- this.auditParams.auditOpinion = this.getAuditButtonStatus(auditResult);
1166
+ this.approvalForm.auditOpinion = this.getAuditButtonStatus(auditResult);
1061
1167
  }
1062
1168
  }
1063
1169
 
@@ -1155,7 +1261,15 @@ export default {
1155
1261
  self.executionCompleted(true, null, null, auditResult, self.taskId)
1156
1262
  }, 1000)
1157
1263
  } else {
1158
- self.handleButtons && self.handleButtons.includes('rejectProcessControl') ? self.getProcessAttributes() : self.auditRequest(self.auditParams)
1264
+ // 前序节点只有一个会签节点才可选择驳回人员
1265
+ self.showRejectToSelected = result.data.length === 1 && result.data[0].handleButtons?.includes('rejectToSelected')
1266
+ self.nodeList = Array.isArray(result.data) ? result.data : []
1267
+ self.radio = 0
1268
+ if (self.showRejectToSelected && !self.handleButtons?.includes('rejectProcessControl')) {
1269
+ self.rejectAttributesBoxShow = true
1270
+ } else {
1271
+ self.handleButtons && self.handleButtons.includes('rejectProcessControl') ? self.getProcessAttributes() : self.auditRequest(self.auditParams)
1272
+ }
1159
1273
  }
1160
1274
  })
1161
1275
  }).catch(() => {
@@ -1192,6 +1306,10 @@ export default {
1192
1306
  getProcessAttributes(self.procId, self.taskNode).then(resp => {
1193
1307
  if (resp.data.code === '200') {
1194
1308
  if ((self.auditParams.auditResult == '70' && !resp.data.data.jumpFirstNode) || (self.auditParams.auditResult == '40' && (resp.data.data.multiNode || resp.data.data.preNodeGateway))) {
1309
+ if (self.showRejectToSelected && self.nodeList.length > 0) {
1310
+ self.rejectAttributesBoxShow = true
1311
+ return
1312
+ }
1195
1313
  self.auditRequest(self.auditParams)
1196
1314
  return
1197
1315
  } else {
@@ -1237,10 +1355,9 @@ export default {
1237
1355
  )
1238
1356
  }
1239
1357
  }
1358
+ self.showProcessControl = self.rejectAttributeList.length > 0 && self.handleButtons && self.handleButtons.includes('rejectProcessControl')
1240
1359
  if (self.auditParams.auditResult == '70' || self.auditParams.auditResult == '40') {
1241
1360
  self.rejectAttributesBoxShow = self.rejectAttributeList.length > 0 && self.handleButtons && self.handleButtons.includes('rejectProcessControl')
1242
- } else if (self.auditParams.auditResult == '90'){
1243
- self.showProcessControl = self.rejectAttributeList.length > 0 && self.handleButtons && self.handleButtons.includes('rejectProcessControl')
1244
1361
  }
1245
1362
  }
1246
1363
  }).catch((err) => {
@@ -1261,6 +1378,9 @@ export default {
1261
1378
  code: self.auditResult,
1262
1379
  name: self.getAuditStatus(self.auditResult).text
1263
1380
  }
1381
+ if (self.rejectUsers.id !== 'all'){
1382
+ auditParams.rejectUsers = self.rejectUsers.id
1383
+ }
1264
1384
  self.showToastLoading('处理审批中');
1265
1385
  //确认执行审批逻辑
1266
1386
  audit(auditParams).then((resp) => { // 使用箭头函数
@@ -1339,9 +1459,10 @@ export default {
1339
1459
  }
1340
1460
  },
1341
1461
  selectNode(currentRow, index) {
1342
- console.log(currentRow);
1343
1462
  this.radio = index
1344
1463
  this.targetTaskNodeProcessControl = currentRow.processControl
1464
+ this.rejectUsers = { name: '全部办理人', id: 'all' }
1465
+ this.showRejectToSelected = currentRow.rejectToSelected
1345
1466
  },
1346
1467
  processJumpSpecifiedNode() {
1347
1468
  const self = this;
@@ -1355,8 +1476,42 @@ export default {
1355
1476
  self.auditParams.rejectAttribute = self.rejectAttribute ? self.rejectAttribute : 'inSequence'
1356
1477
  self.auditRequest(self.auditParams)
1357
1478
  },
1479
+ handleSelectRejectUser() {
1480
+ let self = this
1481
+ getDoneDetail(self.nodeList[self.radio].taskNode, self.applyId).then(function (resp) {
1482
+ if (resp.data.code === '200') {
1483
+ const data = resp.data.data.rows
1484
+ const latestAudit = data.reduce((max, current) => {
1485
+ return current.auditDate > max.auditDate ? current : max;
1486
+ });
1487
+ const seenNames = new Set();
1488
+ self.rejectUserList = data.filter(item => {
1489
+ if (item.parentExecutionId && latestAudit.parentExecutionId && item.parentExecutionId !== latestAudit.parentExecutionId) {
1490
+ return false;
1491
+ }
1492
+ if (seenNames.has(item.auditName)) {
1493
+ return false;
1494
+ }
1495
+ seenNames.add(item.auditName);
1496
+ return true;
1497
+ }).map(item => {
1498
+ return {
1499
+ ...item,
1500
+ userName: item.auditName,
1501
+ userId: item.auditId,
1502
+ organName: item.auditUserOrgan?.split(':')[1]
1503
+ }
1504
+ });
1505
+ self.selectRejectUserPopupShow = true
1506
+ } else {
1507
+ Toast.fail(resp.data.message)
1508
+ }
1509
+ })
1510
+ },
1358
1511
  rejectAttributesDialogShowClose(){
1359
1512
  //重置
1513
+ this.showRejectToSelected = false
1514
+ this.showProcessControl = false
1360
1515
  this.rejectAttributesBoxShow = false
1361
1516
  this.auditResult = '';
1362
1517
  this.radio = '';
@@ -1451,6 +1606,12 @@ export default {
1451
1606
  this.handleSelect('select')
1452
1607
  }
1453
1608
  this.selectUserPopupShow = false;
1609
+ this.selectCandUserPopupShow = false;
1610
+ },
1611
+ selectRejectUserHandle(checkResult) {
1612
+ this.rejectUsers.name = checkResult.map(item => item.userName).join(',')
1613
+ this.rejectUsers.id = checkResult.map(item => item.userId).join(',')
1614
+ this.selectRejectUserPopupShow = false;
1454
1615
  },
1455
1616
  selectPositionHandle(handle, result) {
1456
1617
  if (handle === 'select') {
@@ -144,11 +144,15 @@ export default {
144
144
  this.personList.rows = this.userRangeList
145
145
  this.personList.total = this.userRangeList.length
146
146
  return
147
- }
148
- this.useUserTransferRange = false
149
- if (this.orgList && this.orgList.length > 0){
150
- this.organizeIdList = this.orgList.map(item => item.orgId)
151
- this.orgTreeType = this.organTreeType;
147
+ }
148
+ this.useUserTransferRange = false
149
+ this.personList.total = 0;
150
+ this.personList.rows = [];
151
+ this.finished = false;
152
+ this.loading = false;
153
+ if (this.orgList && this.orgList.length > 0){
154
+ this.organizeIdList = this.orgList.map(item => item.orgId)
155
+ this.orgTreeType = this.organTreeType;
152
156
  this.searchForm = {
153
157
  ...this.searchForm,
154
158
  orgTreeType: this.organTreeType,
@@ -180,33 +184,43 @@ export default {
180
184
  })
181
185
  }
182
186
  },
183
- handleLoad() {
184
- console.log("触发加载")
185
- this.loading = true;
186
- if (this.useUserTransferRange){
187
- this.personList.rows = this.searchForm.userName ? this.userRangeList.map(item => item.userName === this.searchForm.userName) : this.userRangeList
188
- this.personList.total = this.personList.rows.length;
189
- this.loading = false;
190
- return
191
- }
187
+ handleLoad() {
188
+ if (this.loading || this.finished) {
189
+ return
190
+ }
191
+ console.log("触发加载")
192
+ this.loading = true;
193
+ if (this.useUserTransferRange){
194
+ this.personList.rows = this.searchForm.userName ? this.userRangeList.map(item => item.userName === this.searchForm.userName) : this.userRangeList
195
+ this.personList.total = this.personList.rows.length;
196
+ this.finished = true;
197
+ this.loading = false;
198
+ return
199
+ }
192
200
  const offset = this.personList.rows.length;
193
201
  const limit = 10;
194
202
 
195
- getUserList(offset, limit, this.searchForm).then(res => {
196
- const result = res.data;
197
- if (result.code === "1") {
198
- //返回的数据添加到 personList 中
199
- this.personList.rows = this.personList.rows.concat(result.data.rows);
200
- this.personList.total = result.data.total;
201
- }
202
-
203
- this.loading = false;
204
- this.finished = this.personList.rows.length >= this.personList.total;
205
-
206
- }).catch(error => {
207
- console.error('Error fetching data:', error);
208
- this.loading = false;
209
- });
203
+ getUserList(offset, limit, this.searchForm).then(res => {
204
+ const result = res.data;
205
+ if (result.code === "1") {
206
+ //返回的数据添加到 personList 中
207
+ const newRows = Array.isArray(result.data.rows) ? result.data.rows : [];
208
+ this.personList.rows = this.personList.rows.concat(newRows);
209
+ const total = Number(result.data.total);
210
+ if (!Number.isNaN(total)) {
211
+ this.personList.total = total;
212
+ }
213
+ const noMoreByPage = newRows.length < limit;
214
+ const noMoreByTotal = this.personList.total > 0 && this.personList.rows.length >= this.personList.total;
215
+ this.finished = noMoreByPage || noMoreByTotal;
216
+ }
217
+
218
+ this.loading = false;
219
+
220
+ }).catch(error => {
221
+ console.error('Error fetching data:', error);
222
+ this.loading = false;
223
+ });
210
224
  },
211
225
 
212
226
  extractUsers(permScope) {
@@ -240,11 +254,13 @@ export default {
240
254
  resetSearch() {
241
255
  this.initSearch()
242
256
  },
243
- resetAndLoadPersonList() {
244
- this.personList.total = 0;
245
- this.personList.rows = [];
246
- this.handleLoad();
247
- },
257
+ resetAndLoadPersonList() {
258
+ this.personList.total = 0;
259
+ this.personList.rows = [];
260
+ this.finished = false;
261
+ this.loading = false;
262
+ this.handleLoad();
263
+ },
248
264
  handleSelect(handle) {
249
265
  if (handle === 'select') {
250
266
  // 触发自定义事件 'selectHandle',并传递 this.checkResult
@@ -10,6 +10,16 @@
10
10
  <div class="containers">
11
11
  <div ref="canvas" class="canvas"></div>
12
12
  </div>
13
+ <div v-if="predictButtonEnabled" class="predict-toggle">
14
+ <van-button
15
+ type="primary"
16
+ size="small"
17
+ :loading="loadingFutureNodes"
18
+ @click="toggleNodeDisplay"
19
+ >
20
+ {{ showAllNodes ? '显示预测路线' : '显示设计路线' }}
21
+ </van-button>
22
+ </div>
13
23
 
14
24
  <!--审批流程追踪-->
15
25
  <div class="title-info">
@@ -74,7 +84,8 @@
74
84
  </template>
75
85
  <script>
76
86
  import Viewer from "bpmn-js/lib/Viewer";
77
- import {getHisAudit, getPrintData, printData} from "../api";
87
+ import { Toast } from "vant";
88
+ import {getHisAudit, getPrintData, printData, renderFutureNode, getNodeData} from "../api";
78
89
  import ApprovalNodeCell from "./ApprovalNodeCell.vue";
79
90
  import { getAuditStatus } from './js/global';
80
91
  import FlowNodeCell from "./FlowNodeCell.vue"; // 根据路径修改
@@ -90,6 +101,11 @@ export default {
90
101
  type: String,
91
102
  required: false,
92
103
  },
104
+ taskId: {
105
+ type: String,
106
+ required: false,
107
+ default: '',
108
+ },
93
109
  taskNode: {
94
110
  type: String,
95
111
  required: false,
@@ -98,18 +114,56 @@ export default {
98
114
  type: String,
99
115
  required: true,
100
116
  },
117
+ predictButtonEnabled: {
118
+ type: Boolean,
119
+ required: false,
120
+ },
101
121
  },
102
122
  mounted() {
123
+ if (this.predictButtonEnabled == null && this.taskNode){
124
+ getNodeData(this.procId, this.taskNode).then(resp => {
125
+ if (resp.data.code === '200') {
126
+ const data = resp.data.data[0]
127
+ this.predictButtonEnabled = data.handleButtons && data.handleButtons.includes("predictiveRoute")
128
+ }
129
+ })
130
+ }
103
131
  this.getPrintData()
104
132
  // this.getHisAudit()
105
133
  this.onTrack()
106
134
  },
135
+ beforeDestroy() {
136
+ this.removeTouchGesture();
137
+ if (this.bpmnViewer) {
138
+ this.bpmnViewer.destroy();
139
+ this.bpmnViewer = null;
140
+ }
141
+ },
107
142
  data() {
108
143
  return {
109
144
  showTaskNodeDetail: false,
110
145
  nodeDetailsList: [],
111
146
  currentIndex: 0,
112
147
  auditData: [],
148
+ tableData: [],
149
+
150
+ bpmnViewer: null,
151
+ diagramReady: false,
152
+
153
+ // 预测路线相关状态
154
+ showAllNodes: true,
155
+ loadingFutureNodes: false,
156
+ futureNodeData: null,
157
+
158
+ // 已办/待办集合(用于预测路线显示时保留已执行路径)
159
+ doneLightSet: new Set(),
160
+ doneTaskSet: new Set(),
161
+ donePointIdSet: new Set(),
162
+ doneNodeIdSet: new Set(),
163
+ todoPointIdSet: new Set(),
164
+
165
+ // 触摸手势事件句柄(用于重复初始化时清理)
166
+ touchGestureHandlers: null,
113
167
  }
114
168
  },
115
169
  computed: {
@@ -125,8 +179,9 @@ export default {
125
179
  getPrintData() {
126
180
  getPrintData(this.applyId, this.procId).then(resp => {
127
181
  if (resp.data.code === '200') {
128
- this.tableData = resp.data.data
129
- this.auditData = resp.data.data
182
+ const data = Array.isArray(resp.data.data) ? resp.data.data : [];
183
+ this.tableData = data;
184
+ this.auditData = data;
130
185
  }
131
186
  })
132
187
  },
@@ -137,16 +192,317 @@ export default {
137
192
  }
138
193
  })
139
194
  },
140
- onTrack() {
195
+
196
+ // 切换“设计路线 / 预测路线”显示
197
+ async toggleNodeDisplay() {
198
+ if (this.showAllNodes) {
199
+ const success = await this.loadFutureNodes();
200
+ if (!success) return;
201
+ this.showAllNodes = false;
202
+ this.$emit('toggle-predictive', {showPredictive: true, futureNodeData: this.futureNodeData});
203
+ } else {
204
+ this.showAllNodesInDiagram();
205
+ this.showAllNodes = true;
206
+ this.$emit('toggle-predictive', {showPredictive: false, futureNodeData: null});
207
+ }
208
+ },
209
+
210
+ // 加载未来节点(预测路线)数据
211
+ async loadFutureNodes() {
212
+ if (!this.instanceId || !this.taskId) {
213
+ Toast('缺少必要参数:流程实例ID或当前任务ID');
214
+ return false;
215
+ }
216
+ if (!this.diagramReady) {
217
+ Toast('流程图尚未加载完成,请稍后重试');
218
+ return false;
219
+ }
220
+
221
+ this.loadingFutureNodes = true;
222
+ try {
223
+ const resp = await renderFutureNode(this.instanceId, this.taskId);
224
+ if (resp.data.code === '200' && resp.data.data) {
225
+ const futureData = resp.data.data;
226
+ const currentNodeId = futureData.currentNodeId;
227
+ const elementRegistry = this.bpmnViewer && this.bpmnViewer.get('elementRegistry');
228
+ if (currentNodeId && elementRegistry) {
229
+ const currentElement = elementRegistry.get(currentNodeId);
230
+ if (currentElement && this.isElementInSubProcess(currentElement)) {
231
+ Toast('预测路线暂不支持子流程节点,请查看设计路线');
232
+ return false;
233
+ }
234
+ }
235
+ this.futureNodeData = futureData;
236
+ this.showFutureNodesOnly();
237
+ return true;
238
+ }
239
+ Toast.fail(`加载预测路线失败:${resp.data.message || '未知错误'}`);
240
+ return false;
241
+ } catch (error) {
242
+ Toast.fail(`加载预测路线失败:${error.message || '未知错误'}`);
243
+ return false;
244
+ } finally {
245
+ this.loadingFutureNodes = false;
246
+ }
247
+ },
248
+
249
+ // 只显示预测路线相关节点(并保留已办路径)
250
+ showFutureNodesOnly() {
251
+ if (!this.futureNodeData || !this.bpmnViewer) return;
252
+
253
+ const elementRegistry = this.bpmnViewer.get('elementRegistry');
254
+
255
+ // 获取所有元素
256
+ const allElements = elementRegistry.getAll();
257
+
258
+ // 从后端数据中提取节点和边信息
259
+ const {nodes = [], edges = [], futureNodeIds = []} = this.futureNodeData;
260
+
261
+ // 构建节点ID到节点数据的映射
262
+ const nodeMap = new Map();
263
+ nodes.forEach(node => {
264
+ nodeMap.set(node.id, node);
265
+ });
266
+
267
+ // 构建边ID到边数据的映射
268
+ const edgeMap = new Map();
269
+ edges.forEach(edge => {
270
+ edgeMap.set(edge.id, edge);
271
+ });
272
+
273
+ // 已办连线和节点集合(使用 Set 便于判断)
274
+ const doneLightSet = this.doneLightSet instanceof Set ? this.doneLightSet : new Set(this.doneLightSet || []);
275
+ const doneNodeSet = this.doneNodeIdSet instanceof Set ? this.doneNodeIdSet : new Set(this.doneNodeIdSet || []);
276
+ const hiddenNodeSet = new Set(this.futureNodeData.hiddenNodeIds || []);
277
+ const hiddenEdgeSet = new Set(this.futureNodeData.hiddenEdgeIds || []);
278
+
279
+ const futureNodeIdSet = new Set(futureNodeIds || []);
280
+ void futureNodeIdSet;
281
+
282
+ const visibleSubProcessIds = new Set(this.futureNodeData.visibleSubProcessIds || []);
283
+
284
+ // 遍历所有元素
285
+ allElements.forEach(element => {
286
+ const gfx = elementRegistry.getGraphics(element);
287
+ if (!gfx) return;
288
+
289
+ // 开始事件和结束事件
290
+ if (element.type === 'bpmn:StartEvent' || element.type === 'bpmn:EndEvent') {
291
+ const nodeData = nodeMap.get(element.id) || {};
292
+ const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
293
+ ? nodeData.futureVisible
294
+ : !hiddenNodeSet.has(element.id);
295
+ const parentSubProcess = element.parent && element.parent.type === 'bpmn:SubProcess' ? element.parent : null;
296
+ const parentVisible = parentSubProcess ? visibleSubProcessIds.has(parentSubProcess.id) : true;
297
+ const parentDone = parentSubProcess && doneNodeSet.has(parentSubProcess.id);
298
+ if (parentSubProcess && !parentVisible && !parentDone) {
299
+ gfx.style.display = 'none';
300
+ return;
301
+ }
302
+ const shouldDisplay = nodeFutureVisible || doneNodeSet.has(element.id);
303
+ if (!shouldDisplay) {
304
+ gfx.style.display = 'none';
305
+ return;
306
+ }
307
+ gfx.style.display = '';
308
+ gfx.style.opacity = '1';
309
+ return;
310
+ }
311
+
312
+ // 处理任务节点
313
+ if (element.type === 'bpmn:UserTask' ||
314
+ element.type === 'bpmn:ServiceTask' ||
315
+ element.type === 'bpmn:Task' ||
316
+ element.type === 'bpmn:CallActivity') {
317
+
318
+ const nodeData = nodeMap.get(element.id) || {};
319
+ const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
320
+ ? nodeData.futureVisible
321
+ : !hiddenNodeSet.has(element.id);
322
+ const parentId = nodeData.parentSubProcessId;
323
+ const parentIsDone = parentId && doneNodeSet.has(parentId);
324
+ const isDoneTask = doneNodeSet.has(element.id) || parentIsDone;
325
+ const shouldDisplay = nodeFutureVisible || isDoneTask;
326
+
327
+ if (!shouldDisplay) {
328
+ gfx.style.display = 'none';
329
+ return;
330
+ }
331
+
332
+ gfx.style.display = '';
333
+ gfx.style.opacity = '1';
334
+
335
+ if (nodeData.futureHighlight) {
336
+ const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
337
+ if (visualElement) {
338
+ visualElement.style.stroke = '#FFD700';
339
+ visualElement.style.strokeWidth = '2px';
340
+ }
341
+ }
342
+ }
343
+
344
+ // 处理连线
345
+ if (element.type === 'bpmn:SequenceFlow') {
346
+ const edgeData = edgeMap.get(element.id) || {};
347
+
348
+ // 修改:严格判断连线是否已办,只有连线本身在 doneLightSet 中才视为已办
349
+ const isDoneLine = doneLightSet.has(element.id);
350
+
351
+ const edgeFutureVisible = edgeData.hasOwnProperty('futureVisible')
352
+ ? edgeData.futureVisible
353
+ : !hiddenEdgeSet.has(element.id);
354
+
355
+ if (!(edgeFutureVisible || isDoneLine)) {
356
+ gfx.style.display = 'none';
357
+ return;
358
+ }
359
+
360
+ gfx.style.display = '';
361
+ gfx.style.opacity = '1';
362
+
363
+ const shouldHighlight = edgeData.futureHighlight === true;
364
+ if (shouldHighlight) {
365
+ const canvas = this.bpmnViewer.get('canvas');
366
+ if (canvas && canvas.hasMarker(element.id, 'done-line')) {
367
+ canvas.removeMarker(element.id, 'done-line');
368
+ }
369
+ const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
370
+ if (visualElement) {
371
+ visualElement.style.stroke = '#FFD700';
372
+ visualElement.style.strokeWidth = '2px';
373
+ }
374
+ }
375
+ }
376
+
377
+ // 处理网关节点
378
+ if (typeof element.type === 'string' && element.type.includes('Gateway')) {
379
+ const nodeData = nodeMap.get(element.id) || {};
380
+ const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
381
+ ? nodeData.futureVisible
382
+ : !hiddenNodeSet.has(element.id);
383
+
384
+ // 检查是否同时连接到已办节点和预测路径上的节点
385
+ const hasIncomingDone = element.incoming?.some(conn => {
386
+ const source = conn.source;
387
+ return doneLightSet.has(conn.id) || (source && doneNodeSet.has(source.id));
388
+ });
389
+
390
+ const hasOutgoingFuture = element.outgoing?.some(conn => {
391
+ const target = conn.target;
392
+ const connData = edgeMap.get(conn.id) || {};
393
+ return connData.futureVisible || (target && nodeMap.get(target.id)?.futureVisible);
394
+ });
395
+
396
+ // 严格网关显示条件:预测可见 / 已办 / 同时连接已办与预测(避免孤立网关)
397
+ const shouldDisplay = nodeFutureVisible || doneNodeSet.has(element.id) || (hasIncomingDone && hasOutgoingFuture);
398
+ if (!shouldDisplay) {
399
+ gfx.style.display = 'none';
400
+ return;
401
+ }
402
+ gfx.style.display = '';
403
+ gfx.style.opacity = '1';
404
+ const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
405
+ if (visualElement) {
406
+ if (nodeData.futureHighlight) {
407
+ visualElement.style.stroke = '#FFD700';
408
+ visualElement.style.strokeWidth = '2px';
409
+ } else {
410
+ visualElement.style.stroke = '#000000';
411
+ visualElement.style.strokeWidth = '2px';
412
+ }
413
+ }
414
+ }
415
+
416
+ // 处理子流程节点(SubProcess)
417
+ if (element.type === 'bpmn:SubProcess') {
418
+ const nodeData = nodeMap.get(element.id) || {};
419
+ const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
420
+ ? nodeData.futureVisible
421
+ : visibleSubProcessIds.has(element.id);
422
+ const shouldDisplay = nodeFutureVisible || doneNodeSet.has(element.id);
423
+ if (!shouldDisplay) {
424
+ gfx.style.display = 'none';
425
+ return;
426
+ }
427
+ gfx.style.display = '';
428
+ gfx.style.opacity = '1';
429
+ const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
430
+ if (visualElement) {
431
+ if (nodeData.futureHighlight) {
432
+ visualElement.style.stroke = '#FFD700';
433
+ visualElement.style.strokeWidth = '2px';
434
+ } else {
435
+ visualElement.style.stroke = '#000000';
436
+ visualElement.style.strokeWidth = '2px';
437
+ }
438
+ }
439
+ }
440
+
441
+ // 处理 label(如连线名称)
442
+ if (element.type === 'label' && element.labelTarget) {
443
+ const target = element.labelTarget;
444
+ const targetEdgeData = edgeMap.get(target.id) || {};
445
+ const futureLabelVisible = targetEdgeData.hasOwnProperty('futureLabelVisible')
446
+ ? targetEdgeData.futureLabelVisible
447
+ : false;
448
+ const targetIsDoneLine = doneLightSet.has(target.id);
449
+ gfx.style.display = (futureLabelVisible || targetIsDoneLine) ? '' : 'none';
450
+ }
451
+ });
452
+ },
453
+
454
+ // 恢复显示全部节点(设计路线)
455
+ showAllNodesInDiagram() {
456
+ if (!this.bpmnViewer) return;
457
+ this.futureNodeData = null;
458
+ this.showTaskNodeDetail = false;
459
+ this.nodeDetailsList = [];
460
+ this.currentIndex = 0;
461
+
462
+ // 重新加载流程图是最可靠的恢复方式(避免样式残留)
463
+ this.onTrack();
464
+ },
465
+
466
+ isElementInSubProcess(element) {
467
+ let parent = element && element.parent;
468
+ while (parent) {
469
+ if (parent.type === 'bpmn:SubProcess') {
470
+ return true;
471
+ }
472
+ parent = parent.parent;
473
+ }
474
+ return false;
475
+ },
476
+
477
+ removeTouchGesture() {
478
+ if (!this.touchGestureHandlers) return;
479
+ const {container, onTouchStart, onTouchMove, onTouchEnd} = this.touchGestureHandlers;
480
+ if (container) {
481
+ container.removeEventListener('touchstart', onTouchStart);
482
+ container.removeEventListener('touchmove', onTouchMove);
483
+ container.removeEventListener('touchend', onTouchEnd);
484
+ }
485
+ this.touchGestureHandlers = null;
486
+ },
487
+
488
+ async onTrack() {
489
+ this.removeTouchGesture();
490
+ if (this.bpmnViewer) {
491
+ this.bpmnViewer.destroy();
492
+ this.bpmnViewer = null;
493
+ }
494
+ this.diagramReady = false;
495
+
141
496
  // 初始化 Viewer
142
497
  this.bpmnViewer = new Viewer({
143
498
  container: this.$refs.canvas
144
499
  });
145
500
 
146
- // 获取并加载流程数据
147
- printData(this.applyId, this.instanceId, this.procId).then(async resp => {
148
- let result = resp.data
149
- if (result.code === '200') {
501
+ try {
502
+ // 获取并加载流程数据
503
+ const resp = await printData(this.applyId, this.instanceId, this.procId);
504
+ const result = resp && resp.data;
505
+ if (result && result.code === '200') {
150
506
  const bpmnXmlStr = result.data.processXml;
151
507
  await this.bpmnViewer.importXML(bpmnXmlStr);
152
508
 
@@ -154,48 +510,58 @@ export default {
154
510
  canvas.zoom('fit-viewport', 'auto');
155
511
  canvas.zoom(0.6); // 放大视图
156
512
 
157
- //给流程节点添加固定样式
513
+ // 给流程节点添加固定样式
158
514
  this.doPrint(result.data.historicData);
515
+ this.diagramReady = true;
159
516
 
160
517
  // 添加手势缩放和移动功能
161
518
  this.addTouchGesture();
162
519
  // 监听元素点击事件
163
520
  this.addElementClickListener();
164
521
 
522
+ return;
165
523
  }
166
- }).catch(err => {
167
- console.error("Error fetching BPMN data:", err);
168
- });
169
524
 
525
+ this.diagramReady = false;
526
+ Toast.fail(`加载流程图失败:${(result && result.message) || '未知错误'}`);
527
+ } catch (err) {
528
+ this.diagramReady = false;
529
+ console.error("Error fetching BPMN data:", err);
530
+ Toast.fail(`加载流程图失败:${err.message || '未知错误'}`);
531
+ }
170
532
  },
171
533
  doPrint(printData) {
172
- const {doneLightSet, donePointSet, doneTaskSet, todoPointSet} =
173
- printData;
534
+ const {doneLightSet, donePointSet, doneTaskSet, todoPointSet} = printData || {};
535
+
536
+ // 保存已办连线和节点集合,供预测路线显示时使用
537
+ this.doneLightSet = new Set(doneLightSet || []);
538
+ this.doneTaskSet = new Set(doneTaskSet || []);
539
+ this.donePointIdSet = new Set(donePointSet || []);
540
+ this.doneNodeIdSet = new Set([
541
+ ...this.doneTaskSet,
542
+ ...this.donePointIdSet
543
+ ]);
544
+ this.todoPointIdSet = new Set(todoPointSet || []);
545
+
174
546
  const canvas = this.bpmnViewer.get("canvas");
175
547
  const elementRegistry = this.bpmnViewer.get('elementRegistry');
176
548
  elementRegistry.filter((item) => item.type === 'bpmn:UserTask');
177
- for (let k in doneLightSet) {
178
- if (doneLightSet[k]) {
179
- canvas.addMarker(doneLightSet[k], "done-line");
180
- }
549
+ for (const id of this.doneLightSet) {
550
+ if (id) canvas.addMarker(id, "done-line");
181
551
  }
182
- for (let k in donePointSet) {
183
- if (donePointSet[k]) {
184
- canvas.addMarker(donePointSet[k], "done-point");
185
- }
552
+ for (const id of this.donePointIdSet) {
553
+ if (id) canvas.addMarker(id, "done-point");
186
554
  }
187
- for (let k in doneTaskSet) {
188
- if (doneTaskSet[k]) {
189
- canvas.addMarker(doneTaskSet[k], "done-task");
190
- }
555
+ for (const id of this.doneTaskSet) {
556
+ if (id) canvas.addMarker(id, "done-task");
191
557
  }
192
- for (let k in todoPointSet) {
193
- if (todoPointSet[k]) {
194
- canvas.addMarker(todoPointSet[k], "todo-point");
195
- }
558
+ for (const id of this.todoPointIdSet) {
559
+ if (id) canvas.addMarker(id, "todo-point");
196
560
  }
197
561
  },
198
562
  addTouchGesture() {
563
+ if (!this.bpmnViewer) return;
564
+ this.removeTouchGesture();
199
565
  const canvas = this.bpmnViewer.get('canvas');
200
566
  const container = this.$refs.canvas;
201
567
  let initialDistance = null;
@@ -208,7 +574,7 @@ export default {
208
574
  return Math.sqrt(dx * dx + dy * dy);
209
575
  };
210
576
 
211
- container.addEventListener('touchstart', (event) => {
577
+ const onTouchStart = (event) => {
212
578
  if (event.touches.length === 2) {
213
579
  initialDistance = calculateDistance(event.touches[0], event.touches[1]);
214
580
  } else if (event.touches.length === 1) {
@@ -218,9 +584,9 @@ export default {
218
584
  };
219
585
  isPanning = true;
220
586
  }
221
- });
587
+ };
222
588
 
223
- container.addEventListener('touchmove', (event) => {
589
+ const onTouchMove = (event) => {
224
590
  if (event.touches.length === 2 && initialDistance !== null) {
225
591
  const currentDistance = calculateDistance(event.touches[0], event.touches[1]);
226
592
  const zoomFactor = currentDistance / initialDistance;
@@ -250,18 +616,25 @@ export default {
250
616
 
251
617
  event.preventDefault(); // 阻止默认的触摸事件
252
618
  }
253
- });
619
+ };
254
620
 
255
- container.addEventListener('touchend', () => {
621
+ const onTouchEnd = () => {
256
622
  initialDistance = null; // 重置初始距离
257
623
  isPanning = false; // 停止平移
258
- });
624
+ };
625
+
626
+ container.addEventListener('touchstart', onTouchStart);
627
+ container.addEventListener('touchmove', onTouchMove);
628
+ container.addEventListener('touchend', onTouchEnd);
629
+
630
+ this.touchGestureHandlers = {container, onTouchStart, onTouchMove, onTouchEnd};
259
631
  },
260
632
  addElementClickListener() {
261
633
  const eventBus = this.bpmnViewer.get('eventBus');
262
634
  eventBus.on('element.click', (event) => {
263
635
  const elementId = event.element.id;
264
- let matchedRecords = this.tableData.filter(item => item.taskNode === elementId);
636
+ const tableData = Array.isArray(this.tableData) ? this.tableData : [];
637
+ let matchedRecords = tableData.filter(item => item.taskNode === elementId);
265
638
 
266
639
  if (matchedRecords.length > 0) {
267
640
  const getTime = (record) => {
@@ -293,6 +666,12 @@ export default {
293
666
  <style scoped>
294
667
  @import 'styles/global.css';
295
668
 
669
+ .predict-toggle {
670
+ display: flex;
671
+ justify-content: center;
672
+ margin-top: 12px;
673
+ }
674
+
296
675
  .canvas {
297
676
  width: 100%;
298
677
  height: 250px;