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

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.
@@ -0,0 +1,1186 @@
1
+ <template>
2
+ <div>
3
+ <van-tabs ref="tabs" style="padding-bottom: 60px" v-model="active"
4
+ @click="onTabClick"
5
+ sticky :offset-top="offsetTop"
6
+ animated>
7
+ <slot name="business-content"/>
8
+ <van-tab title="流程信息">
9
+ <div style="padding: 0 10px">
10
+ <div v-if="!isDetail && showAuditOpinion">
11
+ <div class="title-info">
12
+ <van-icon class="info-icon" style="background-color: #0d88ff" name="cluster-o"/>
13
+ 审批信息
14
+ </div>
15
+ <div class="approvalForm-section">
16
+ <van-cell-group>
17
+ <van-field
18
+ v-if="handleButtons && handleButtons.includes('auditOpinion')"
19
+ v-model="approvalForm.auditOpinion"
20
+ rows="3"
21
+ autosize
22
+ @focus="handleInputFocus"
23
+ :error="approvalError"
24
+ required
25
+ label="审批意见"
26
+ type="textarea"
27
+ maxlength="50"
28
+ placeholder="请输入"
29
+ show-word-limit
30
+ />
31
+ <div v-if="approvalForm.nextNode!==''">
32
+ <van-cell title="下一环节" :value="approvalForm.nextNode"/>
33
+ <div v-if="handleButtons && handleButtons.includes('appointHandler')">
34
+ <van-cell @click="nextNodePopupShow = true" title="办理人员" value-class="approvalForm-value"
35
+ :value="approvalForm.handlingPersonnel">
36
+ <template #right-icon>
37
+ <van-icon name="friends-o" size="16" style="line-height: inherit;padding-left: 5px"/>
38
+ </template>
39
+ </van-cell>
40
+ </div>
41
+ <div class="van-hairline--bottom"></div>
42
+ </div>
43
+ <div v-if="handleButtons && handleButtons.includes('attachmentFile')">
44
+ <upload-file :multiple="true"
45
+ :oss-server-context="config.smartFlowServerContext"
46
+ :oss-file-put-url="config.ossFilePutUrl"
47
+ :oss-file-get-url="config.ossFileGetUrl"
48
+ :fileList="fileList"
49
+ @upload-result="uploadFile">
50
+ </upload-file>
51
+ </div>
52
+
53
+ </van-cell-group>
54
+ </div>
55
+ </div>
56
+ <div>
57
+ <div class="title-info">
58
+ <van-icon class="info-icon" style="background-color: #ff7f02" name="orders-o"/>
59
+ 审批记录
60
+ </div>
61
+ <div class="approvalForm-records">
62
+ <van-collapse v-model="activeNames">
63
+ <van-collapse-item v-for="(items,index) in processHistory" :key="index" :title="items[0].taskName"
64
+ :name="index">
65
+ <div v-for="(item,index) in items" :key="index" class="record-item">
66
+ <approval-node-cell :node-detail="item" :folding-approval-comments="foldingApprovalComments" />
67
+ <div v-if="index !== items.length - 1" class="van-hairline--bottom"></div>
68
+ </div>
69
+ </van-collapse-item>
70
+ </van-collapse>
71
+ </div>
72
+ </div>
73
+ <div v-if="handleButtons && handleButtons.includes('attachmentFile')">
74
+ <div class="title-info">
75
+ <van-icon class="info-icon" style="background-color: #029ea0" name="link-o"/>
76
+ 查看附件
77
+ </div>
78
+ <div v-if="files.length > 0">
79
+ <div class="attachments">
80
+ <div v-for="item in files" :key="item.name">
81
+ <van-cell style="margin-bottom: 20px;"
82
+ center>
83
+ <template v-slot:icon>
84
+ <div class="file-icon" :style="{ backgroundColor: getBackgroundColor(item.fileName) }">
85
+ {{ getFileIconText(item.fileName) }}
86
+ </div>
87
+ </template>
88
+ <template v-slot:title>
89
+ <div style="font-size: 15px;font-weight: bold;padding:5px 0;
90
+ word-wrap: break-word;word-break: break-all; white-space: normal;">
91
+ {{ item.fileName }}
92
+ </div>
93
+ <div style="color: #969799;font-size: 14px;">
94
+ {{ item.uploadUserName + ' ' + '|' + ' ' + item.taskName }}
95
+ </div>
96
+ </template>
97
+ <template v-slot:label>
98
+ <div>上传时间</div>
99
+ </template>
100
+ <template v-slot:default>
101
+ <span @click="getAttach(item)" class="iconfont icon-cloud-download"
102
+ style="font-size: 20px;color: #027efe"></span>
103
+ </template>
104
+ </van-cell>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ <div v-else>
109
+ <van-empty description="暂无附件"/>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </van-tab>
114
+ </van-tabs>
115
+ <!-- 底部按钮 等其他内容 -->
116
+ <div>
117
+ <van-popup v-model="popupShow" style="padding-top: 40px" closeable round position="bottom">
118
+ <div v-if="handleButtons && handleButtons.includes('auditTo70')" class="popup-option" @click="audit('70')">
119
+ {{ getAuditButtonStatus(70) }}
120
+ </div>
121
+ <div v-if="handleButtons && handleButtons.includes('auditTo40')" class="popup-option highlighted"
122
+ @click="audit('40')">
123
+ {{ getAuditButtonStatus(40) }}
124
+ </div>
125
+ <div v-if="handleButtons && handleButtons.includes('auditTo90')" class="popup-option" @click="audit('90')">
126
+ {{ getAuditButtonStatus(90) }}
127
+ </div>
128
+ <div v-if="handleButtons && handleButtons.includes('auditTo80')" class="popup-option" @click="audit('80')">
129
+ {{ getAuditButtonStatus(80) }}
130
+ </div>
131
+ <div v-if="handleButtons && handleButtons.includes('auditTo82')" class="popup-option" @click="audit('82')">
132
+ {{ getAuditButtonStatus(82) }}
133
+ </div>
134
+ <div v-if="handleButtons && handleButtons.includes('auditTo50')" class="popup-option" @click="audit('50')">
135
+ {{ getAuditButtonStatus(50) }}
136
+ </div>
137
+ </van-popup>
138
+ <div class="custom-bottom-bar">
139
+ <div v-if="!isDetail && getVisibleButtonsCount() > 1" class="bar-item" @click="onMore">更多</div>
140
+ <div v-else-if="!isDetail && getVisibleButtonsCount() === 1" class="bar-item" @click="handleSingleButton()">
141
+ {{ getSingleButtonText() }}
142
+ </div>
143
+ <div class="bar-item" @click="onTrack">流程跟踪</div>
144
+ <div v-if="!isDetail" class="bar-item approve" @click="audit('30')">{{ getAuditButtonStatus(30) }}</div>
145
+ </div>
146
+ </div>
147
+ <van-popup @click-close-icon="handleSelect('cancel')" v-model="nextNodePopupShow"
148
+ style="height:70%; background:#f7f8fa;padding-bottom:60px" closeable round
149
+ position="bottom">
150
+ <div class="van-nav-bar__content" style="height: 55px">
151
+ <div class="van-nav-bar__title"> 指定下一环节 办理人员</div>
152
+ </div>
153
+ <van-cell-group v-for="(item,index) in nextNodeProcessedData" :key="index"
154
+ style="margin-bottom: 10px;" inset>
155
+ <van-cell title="节点名称" :value="item.name"></van-cell>
156
+ <van-field name="radio" label="人员类型" input-align="right">
157
+ <template #input>
158
+ <van-radio-group v-model="item.personType" direction="horizontal">
159
+ <van-radio name="assignee">办理人</van-radio>
160
+ <van-radio name="candidate">候选人</van-radio>
161
+ </van-radio-group>
162
+ </template>
163
+ </van-field>
164
+ <van-cell v-if="item.personType === 'assignee'" title="办理人"
165
+ :value="(item.assignee.name ? item.assignee.name : '未指定') + ':' + (item.assignee.id ? item.assignee.id : '未指定')"
166
+ @click="nextNodeHandleIndex=index;selectHandlePopupShow=true;">
167
+ </van-cell>
168
+ <van-cell v-if="item.personType === 'candidate'" title="候选人" @click="toggleDetails(index)"
169
+ :is-link="true" :arrow-direction="detailsVisible[index] ? 'down' : ''">
170
+ </van-cell>
171
+ <div v-if="detailsVisible[index] && item.personType === 'candidate'">
172
+ <van-field @click="handleSelectUserPopupShow(index)" label="候选用户" readonly placeholder="点击选择"
173
+ :value="(item.candidateGroups?.users ?? []).map(user => user.name).join(',')">
174
+ </van-field>
175
+ <van-field @click="handleSelectPositionPopupShow(index)" label="候选岗位" readonly placeholder="点击选择"
176
+ :value="(item.candidateGroups?.positions ?? []).map(position => position.name).join(',')">
177
+ </van-field>
178
+ <van-field @click="handleSelectRolePopupShow(index)" label="候选角色" readonly placeholder="点击选择"
179
+ :value="(item.candidateGroups?.roles ?? []).map(role => role.name).join(',')">
180
+ </van-field>
181
+ <van-field @click="handleSelectOrganizePopupShow(index)" label="候选组织" readonly placeholder="点击选择"
182
+ :value="(item.candidateGroups?.organs ?? []).map(organ => organ.name).join(',')">
183
+ </van-field>
184
+ </div>
185
+ <div v-if="handleButtons && handleButtons.includes('appointTimeoutTime')">
186
+ <van-cell center title="停留时间">
187
+ <template #default>
188
+ <div style="display: flex; align-items: center;">
189
+ <div style="display: flex; align-items: center; margin-right: 16px;">
190
+ <van-stepper disable-input button-size="22" min="0" v-model="item.remainDay"/>
191
+ <span style="margin-left: 4px;">天</span>
192
+ </div>
193
+ <div style="display: flex; align-items: center;">
194
+ <van-stepper disable-input button-size="22" min="0" v-model="item.remainTime"/>
195
+ <span style="margin-left: 4px;">小时</span>
196
+ </div>
197
+ </div>
198
+ </template>
199
+ </van-cell>
200
+ <van-cell title="任务提前">
201
+ <template #default>
202
+ <div style="display: flex; align-items: center;">
203
+ <div style="display: flex; align-items: center; margin-right: 16px;">
204
+ <van-stepper disable-input button-size="22" min="0" v-model="item.inAdvanceDay"/>
205
+ <span style="margin-left: 4px;">天</span>
206
+ </div>
207
+ <div style="display: flex; align-items: center;">
208
+ <van-stepper disable-input button-size="22" min="0" v-model="item.inAdvanceTime"/>
209
+ <span style="margin-left: 4px;">小时</span>
210
+ </div>
211
+ </div>
212
+ </template>
213
+ </van-cell>
214
+ <van-field
215
+ readonly
216
+ clickable
217
+ input-align="right"
218
+ name="picker"
219
+ :value="(handleTypeList.find(handleType => handleType.value === item.processing) || {}).label"
220
+ label="处理方式"
221
+ placeholder="点击选择处理方式"
222
+ @click="$set(showPicker, index, true)"
223
+ />
224
+ </div>
225
+ <van-popup v-model="showPicker[index]" position="bottom">
226
+ <van-picker
227
+ show-toolbar
228
+ :columns="handleTypeList.map(handleType => handleType.label)"
229
+ @confirm="(value, selectedIndex) => onHandleTypeConfirm(value, selectedIndex, index)"
230
+ @cancel="$set(showPicker, index, false)"
231
+ />
232
+ </van-popup>
233
+ </van-cell-group>
234
+ <div class="custom-bottom-bar">
235
+ <div class="bar-item" @click="handleSelect('cancel')">重置</div>
236
+ <div class="bar-item approve" @click="handleSelect('select')">确认</div>
237
+ </div>
238
+ </van-popup>
239
+ <van-popup v-if="selectHandlePopupShow" v-model="selectHandlePopupShow" closeable round position="bottom"
240
+ :style="{ height: '80%' }" style="background: #f7f8fa">
241
+ <select-handle title="指定办理人" :multi-select="false" :procType="procType" @selectHandle="handleSelectResult"></select-handle>
242
+ </van-popup>
243
+ <van-popup v-if="selectOrganizePopupShow" v-model="selectOrganizePopupShow" closeable round position="bottom"
244
+ :style="{ height: '80%' }">
245
+ <select-organize ref="selectOrganize" :all-organize="false" :organize-id-list="permissionsScope.O" @selectOrganizeHandle="selectOrganizeHandle" ></select-organize>
246
+ </van-popup>
247
+ <van-popup v-if="selectUserPopupShow" v-model="selectUserPopupShow" closeable round position="bottom"
248
+ :style="{ height: '80%' }">
249
+ <select-handle title="选择用户" :multi-select="true" :procType="procType" @selectHandle="selectUserHandle" ></select-handle>
250
+ </van-popup>
251
+ <van-popup v-if="selectPositionPopupShow" v-model="selectPositionPopupShow" closeable round position="bottom"
252
+ :style="{ height: '80%' }">
253
+ <select-normal-list title="选择岗位" :id-list="permissionsScope.P" @selectNormalListHandle="selectPositionHandle" :parse-function="parsePositionFunction"></select-normal-list>
254
+ </van-popup>
255
+ <van-popup v-if="selectRolePopupShow" v-model="selectRolePopupShow" closeable round position="bottom"
256
+ :style="{ height: '80%' }">
257
+ <select-normal-list title="选择角色" :id-list="permissionsScope.R" @selectNormalListHandle="selectRoleHandle" :parse-function="parseRoleFunction"></select-normal-list>
258
+ </van-popup>
259
+ <van-dialog v-model="nodeListDialogShow" @confirm="processJumpSpecifiedNode()" @close="nodeListDialogShowClose()"
260
+ :title="getAuditButtonStatus(auditResult)" show-cancel-button>
261
+ <van-radio-group v-model="radio">
262
+ <van-cell-group>
263
+ <div class="table-header">
264
+ <div>节点名称</div>
265
+ <div>节点状态</div>
266
+ <div>选择</div>
267
+ </div>
268
+ <div class="table-row"
269
+ v-for="(item, index) in nodeList" :key="index"
270
+ @click="radio = index">
271
+ <div>{{ item.taskName }}</div>
272
+ <div>
273
+ <van-tag v-if="item.auditResult" :type="getAuditStatus(item.auditResult).type">
274
+ {{ getAuditStatus(item.auditResult).text }}
275
+ </van-tag>
276
+ <van-tag type="warning" v-else>
277
+ 待审批
278
+ </van-tag>
279
+ </div>
280
+ <div>
281
+ <van-radio :name='index'/>
282
+ </div>
283
+ </div>
284
+ </van-cell-group>
285
+ </van-radio-group>
286
+ </van-dialog>
287
+ </div>
288
+ </template>
289
+
290
+ <script>
291
+ import {
292
+ audit,
293
+ config,
294
+ getAllPreNodes,
295
+ getAttachmentList,
296
+ getNextNodes,
297
+ getNodesBehind,
298
+ getPosition,
299
+ getPreNode,
300
+ getHandleButtons,
301
+ getProcessHis,
302
+ getProcessType, getRole
303
+ } from "../api";
304
+ import {Dialog, Toast} from "vant";
305
+ import UploadFile from '@lambo-design-mobile/upload-file';
306
+ import {flutterUtil} from "./utils/flutterUtil";
307
+ import SelectHandle from "./SelectHandle.vue";
308
+ import Tree from "./tree/Tree.vue";
309
+ import SelectOrganize from "./SelectOrganize.vue";
310
+ import SelectNormalList from "./SelectNormalList.vue";
311
+ import ApprovalNodeCell from "./ApprovalNodeCell.vue";
312
+ import {getAuditStatus} from "./js/global";
313
+
314
+
315
+ export default {
316
+ computed: {
317
+ config() {
318
+ return config
319
+ },
320
+ showAuditOpinion(){
321
+ return this.handleButtons && (this.handleButtons.includes('auditOpinion') || this.handleButtons.includes('appointHandler') || this.handleButtons.includes('attachmentFile'))
322
+ }
323
+ },
324
+ components: {ApprovalNodeCell, SelectNormalList, SelectOrganize, Tree, SelectHandle, UploadFile},
325
+ props: {
326
+ //业务表单保存方法
327
+ businessFormSave: {
328
+ type: Function,
329
+ required: false
330
+ },
331
+ //按钮执行完毕回调方法
332
+ executionCompleted: {
333
+ type: Function,
334
+ required: false,
335
+ default: () => {
336
+ } // 默认是一个空函数
337
+ },
338
+ // 是否是流程详情页面
339
+ isDetail: {
340
+ type: Boolean,
341
+ required: false,
342
+ default: false
343
+ },
344
+ processTraceRouterName: {
345
+ type: String,
346
+ required: true,
347
+ },
348
+ activeFirstTab: {
349
+ type: Boolean,
350
+ default: false,
351
+ },
352
+ offsetTop: {
353
+ type: Number,
354
+ default: 0
355
+ },
356
+ foldingApprovalComments: {
357
+ type: Boolean,
358
+ default: false
359
+ }
360
+ },
361
+ data() {
362
+ return {
363
+ auditResult: '',
364
+ //初始化路由数据
365
+ taskNode: this.$route.query.taskNode,
366
+ procId: this.$route.query.procId,
367
+ procType: this.$route.query.procType,
368
+ instanceId: this.$route.query.instanceId,
369
+ applyId: this.$route.query.applyId,
370
+ formUrl: this.$route.query.formUrl,
371
+ taskId: this.$route.query.taskId,
372
+ handleButtons: this.$route.query.handleButtons,
373
+
374
+ approvalForm: {
375
+ auditOpinion: '',
376
+ nextNode: '',
377
+ handlingPersonnel: '',
378
+ nodeConfigMaps: '',
379
+ },
380
+ approvalError: false,
381
+
382
+ processHistory: [],
383
+ tableData: [],
384
+
385
+ bpmnViewer: null,
386
+
387
+ activeNames: [],
388
+ popupShow: false,
389
+
390
+ nextNodePopupShow: false,
391
+ nextNodeHandleIndex: 0,
392
+ nextNodeProcessedData: [], // 用于存储处理下一节点的数据
393
+ detailsVisible: {}, // 用于跟踪每个项目的详细内容是否可见
394
+ handleTypeList: [
395
+ {
396
+ value: '00',
397
+ label: '只预警不处理'
398
+ },
399
+ {
400
+ value: '10',
401
+ label: '自动同意'
402
+ },
403
+ {
404
+ value: '20',
405
+ label: '直接终止流程'
406
+ },
407
+ {
408
+ value: '90',
409
+ label: '自动驳回'
410
+ }
411
+ ],
412
+ handleTypeValue: '',
413
+ showPicker: {},
414
+
415
+ nodeListDialogShow: false,
416
+ nodeList: [],
417
+ radio: '',
418
+
419
+ selectHandlePopupShow: false,
420
+
421
+ active: 0,
422
+ //给附件列表展示
423
+ files: [],
424
+ //文件上传列表
425
+ fileList: [],
426
+
427
+ permissionsScope: {},
428
+ selectOrganizePopupShow: false,
429
+ selectUserPopupShow: false,
430
+ selectRolePopupShow: false,
431
+ selectPositionPopupShow: false,
432
+ };
433
+ },
434
+ methods: {
435
+ getAuditStatus,
436
+ initData() {
437
+ if (!this.handleButtons){
438
+ this.getHandleButtons();
439
+ }
440
+ this.getProcessHistory();
441
+ this.getNextNodes();
442
+ this.getAttachmentList();
443
+ this.getProcessTypeList();
444
+ //设置提示持续时间默认为500ms
445
+ Toast.setDefaultOptions({duration: 500});
446
+ },
447
+
448
+ // 计算有多少个可见的handleButtons
449
+ getVisibleButtonsCount() {
450
+ return ['auditTo70', 'auditTo40', 'auditTo90', 'auditTo80', 'auditTo82', 'auditTo50']
451
+ .filter(btn => this.handleButtons && this.handleButtons.includes(btn))
452
+ .length;
453
+ },
454
+
455
+ // 获取唯一按钮的文本
456
+ getSingleButtonText() {
457
+ const buttonIds = ['auditTo70', 'auditTo40', 'auditTo90', 'auditTo80', 'auditTo82', 'auditTo50'];
458
+ const singleButtonId = buttonIds.find(btn => this.handleButtons && this.handleButtons.includes(btn));
459
+ if (singleButtonId) {
460
+ const statusId = parseInt(singleButtonId.replace('auditTo', ''));
461
+ return this.getAuditButtonStatus(statusId);
462
+ }
463
+ return '';
464
+ },
465
+
466
+ // 处理唯一按钮的点击
467
+ handleSingleButton() {
468
+ const buttonIds = ['auditTo70', 'auditTo40', 'auditTo90', 'auditTo80', 'auditTo82', 'auditTo50'];
469
+ const singleButtonId = buttonIds.find(btn => this.handleButtons && this.handleButtons.includes(btn));
470
+ if (singleButtonId) {
471
+ const statusId = singleButtonId.replace('auditTo', '');
472
+ this.audit(statusId);
473
+ }
474
+ },
475
+
476
+ getHandleButtons() {
477
+ getHandleButtons(this.procId, this.taskNode).then(resp => {
478
+ if (resp.data.code === '200') {
479
+ this.handleButtons = resp.data.data[0]?.handleButtons;
480
+ }
481
+ })
482
+ },
483
+ getProcessHistory() {
484
+ getProcessHis(this.applyId, this.instanceId, this.procId, this.taskId).then(resp => {
485
+ if (resp.data.code === '200') {
486
+ this.processHistory = resp.data.data
487
+ this.activeNames = this.processHistory.map((_, index) => index);
488
+ }
489
+ })
490
+ },
491
+ getNextNodes() {
492
+ getNextNodes(this.procId, this.taskNode).then(resp => {
493
+ const result = resp.data;
494
+ if (result.code === '200') {
495
+ this.nextNodeProcessedData = this.processData(result.data);
496
+ this.approvalForm.nextNode = result.data.map(node => node.name).join(',');
497
+ this.approvalForm.handlingPersonnel = result.data
498
+ .map(node => node.assignee?.name || '候选人')
499
+ .join(',');
500
+ }
501
+ })
502
+ },
503
+ getProcessTypeList() {
504
+ getProcessType(this.procType).then(res => {
505
+ let permScope = res.data.data.rows[0].permScope;
506
+ this.permissionsScope = this.extractPermissions(permScope);
507
+ });
508
+ },
509
+ extractPermissions(permScope) {
510
+ const permissions = {};
511
+
512
+ // 使用正则分别匹配 O、U、P 和 R
513
+ const oMatch = permScope.match(/O:([^,]*)/);
514
+ const uMatch = permScope.match(/U:([^,]*)/);
515
+ const pMatch = permScope.match(/P:([^,]*)/);
516
+ const rMatch = permScope.match(/R:([^,]*)/);
517
+
518
+ // 分别解析 O、U、P 和 R 权限
519
+ permissions.O = oMatch && oMatch[1] ? oMatch[1].split(';') : [];
520
+ permissions.U = uMatch && uMatch[1] ? uMatch[1].split(';') : [];
521
+ permissions.P = pMatch && pMatch[1] ? pMatch[1].split(';') : [];
522
+ permissions.R = rMatch && rMatch[1] ? rMatch[1].split(';') : [];
523
+
524
+ return permissions;
525
+ },
526
+ async parsePositionFunction(idList) {
527
+ try {
528
+ // 并行调用 getPosition 方法
529
+ return await Promise.all(
530
+ idList.map(async (id) => {
531
+ const response = await getPosition(id);
532
+ const result = response.data; // 假设响应的主体包含在 data 中
533
+
534
+ // 返回 { id, name } 对象
535
+ return {
536
+ id: id,
537
+ name: result.data.positionName || `Unknown Name for ${id}`, // 使用返回数据中的 name,若无则提供默认值
538
+ };
539
+ })
540
+ ); // 返回解析结果数组
541
+ } catch (error) {
542
+ console.error('Error fetching positions:', error);
543
+ // 在错误情况下返回默认结构
544
+ return idList.map((id) => ({
545
+ id: id,
546
+ name: `Unknown Name for ${id}`,
547
+ }));
548
+ }
549
+ },
550
+ async parseRoleFunction(idList) {
551
+ try {
552
+ // 并行调用 getPosition 方法
553
+ return await Promise.all(
554
+ idList.map(async (id) => {
555
+ const response = await getRole(id);
556
+ const result = response.data; // 假设响应的主体包含在 data 中
557
+
558
+ // 返回 { id, name } 对象
559
+ return {
560
+ id: id,
561
+ name: result.data.name || `Unknown Name for ${id}`, // 使用返回数据中的 name,若无则提供默认值
562
+ };
563
+ })
564
+ ); // 返回解析结果数组
565
+ } catch (error) {
566
+ console.error('Error fetching positions:', error);
567
+ // 在错误情况下返回默认结构
568
+ return idList.map((id) => ({
569
+ id: id,
570
+ name: `Unknown Name for ${id}`,
571
+ }));
572
+ }
573
+ },
574
+ processData(data) {
575
+ return data.map(node => {
576
+ node.personType = node.assignee ? "assignee" : "candidate";
577
+ if (node.assignee) {
578
+ node.candidateGroups = {
579
+ organs: [],
580
+ roles: [],
581
+ positions: [],
582
+ users: [],
583
+ }
584
+ } else {
585
+ node.assignee = {
586
+ name: "",
587
+ id: ""
588
+ }
589
+ }
590
+ if (node.timeLimit) {
591
+ let expireTime = node.timeLimit.split(";")[0].split(":")[1];
592
+ let warningTime = node.timeLimit.split(";")[1].split(":")[1];
593
+ let handleType = node.timeLimit.split(";")[2].split(":")[1];
594
+ let days = expireTime.slice(0, expireTime.indexOf("D")); //过期天
595
+ let hourOfDay = expireTime.slice(expireTime.indexOf("D") + 1, expireTime.indexOf("H")); //过期小时
596
+ let daysWarn = warningTime.slice(0, warningTime.indexOf("D")); //警告天
597
+ let hourOfDayWarn = warningTime.slice(warningTime.indexOf("D") + 1, warningTime.indexOf("H")); //警告小时
598
+ node.remainDay = parseInt(days);
599
+ node.remainTime = parseInt(hourOfDay);
600
+ node.inAdvanceDay = parseInt(daysWarn);
601
+ node.inAdvanceTime = parseInt(hourOfDayWarn);
602
+ node.processing = handleType;
603
+ } else {
604
+ node.remainDay = 0;
605
+ node.remainTime = 0;
606
+ node.inAdvanceDay = 0;
607
+ node.inAdvanceTime = 0;
608
+ node.processing = "00";
609
+ }
610
+ return node;
611
+ });
612
+ },
613
+ toggleDetails(index) {
614
+ this.$set(this.detailsVisible, index, !this.detailsVisible[index]);
615
+ },
616
+ onHandleTypeConfirm(selectedLabel, selectedIndex, index) {
617
+ // 更新 nextNodeProcessedData 为对应的 value
618
+ this.nextNodeProcessedData[index].processing = this.handleTypeList[selectedIndex].value;
619
+ // 关闭选择器
620
+ this.showPicker[index] = false;
621
+ },
622
+ clickCandidateGroup() {
623
+ Toast("请在电脑端指定节点候选人")
624
+ },
625
+ formattedCandidateGroupData(candidateGroups) {
626
+ // 获取ID 并通过分号连接
627
+ const organIds = candidateGroups.organs.map(organ => organ.id).join(';');
628
+ const userIds = candidateGroups.users.map(user => user.id).join(';');
629
+ const positionIds = candidateGroups.positions.map(position => position.id).join(';');
630
+ const roleIds = candidateGroups.roles.map(role => role.id).join(';');
631
+ // 拼接成目标格式
632
+ return `O:${organIds},U:${userIds},P:${positionIds},R:${roleIds},RP:,T:`;
633
+ },
634
+ handleSelect(handle) {
635
+ if (handle === 'select') {
636
+ //处理审批参数
637
+ let params = this.nextNodeProcessedData.reduce((acc, item) => {
638
+ acc[item.id] = {
639
+ needUpdate: 'true',
640
+ }
641
+ if (item.personType === "assignee") {
642
+ acc[item.id].assignee = item.assignee.id
643
+ }
644
+ if (item.personType === "candidate") {
645
+ acc[item.id].candidateGroup = this.formattedCandidateGroupData(item.candidateGroups)
646
+ }
647
+ let timeLimit = 'expireTime:0D0H;warningTime:0D0H;handleType:00'
648
+ if (!(item.remainDay === 0 && item.remainTime === 0 && item.inAdvanceDay === 0 && item.inAdvanceTime === 0)) {
649
+ timeLimit = 'expireTime:' + parseInt(item.remainDay) + "D" + parseInt(item.remainTime) + "H" + ";warningTime:" + parseInt(item.inAdvanceDay) + "D" + parseInt(item.inAdvanceTime) + "H;" + "handleType:" + item.processing;
650
+ acc[item.id].timeLimit = timeLimit
651
+ } else {
652
+ acc[item.id].timeLimit = ''
653
+ }
654
+ return acc;
655
+ }, {});
656
+ this.approvalForm.nodeConfigMaps = JSON.stringify(params)
657
+ this.approvalForm.handlingPersonnel = this.nextNodeProcessedData
658
+ .map(node => node.personType === "assignee" ? node.assignee?.name : '候选人')
659
+ .join(',');
660
+ this.nextNodePopupShow = false;
661
+ } else {
662
+ this.getNextNodes();
663
+ this.approvalForm.nodeConfigMaps = '';
664
+ this.nextNodePopupShow = false;
665
+ }
666
+ },
667
+ handleSelectOrganizePopupShow(index) {
668
+ this.nextNodeHandleIndex = index;
669
+ this.selectOrganizePopupShow=true;
670
+ },
671
+ handleSelectUserPopupShow(index) {
672
+ this.nextNodeHandleIndex = index;
673
+ this.selectUserPopupShow=true;
674
+ },
675
+ handleSelectRolePopupShow(index) {
676
+ this.nextNodeHandleIndex = index;
677
+ this.selectRolePopupShow = true;
678
+ },
679
+ handleSelectPositionPopupShow(index) {
680
+ this.nextNodeHandleIndex = index;
681
+ this.selectPositionPopupShow = true;
682
+ },
683
+ getAttachmentList() {
684
+ getAttachmentList(this.procId, this.applyId).then(resp => {
685
+ if (resp.data.code === '200') {
686
+ // 将 API 返回的数据赋值给 files
687
+ this.files = resp.data.data.rows;
688
+ }
689
+ });
690
+ },
691
+ onTabClick() {
692
+
693
+
694
+ },
695
+ getBackgroundColor(name) {
696
+ const ext = name.split('.').pop().toLowerCase();
697
+ switch (ext) {
698
+ case 'doc':
699
+ return '#027efe';
700
+ case 'docx':
701
+ return '#027efe';
702
+ case 'xls':
703
+ return '#02d1a6';
704
+ case 'xlsx':
705
+ return '#02d1a6';
706
+ case 'pdf':
707
+ return '#e55d56';
708
+ default:
709
+ return 'rgb(180,169,169)';
710
+ }
711
+ },
712
+ getFileIconText(name) {
713
+ const ext = name.split('.').pop().toLowerCase();
714
+ if (ext.startsWith('doc')) return 'W';
715
+ if (ext.startsWith('xls')) return 'X';
716
+ if (ext === 'pdf') return 'P';
717
+ return ext.charAt(0).toUpperCase();
718
+ },
719
+
720
+ getAuditButtonStatus(auditResult) {
721
+ let statusMap = {
722
+ '30': '通过',
723
+ '40': '驳回上一节点',
724
+ '50': '直接结束流程',
725
+ '70': '驳回到原点',
726
+ '80': '跳转指定节点',
727
+ '82': '指定他人处理',
728
+ '90': '驳回指定节点',
729
+ };
730
+ return statusMap[auditResult];
731
+ },
732
+
733
+ onMore() {
734
+ console.log("点击了更多");
735
+ this.popupShow = true
736
+ },
737
+ onTrack() {
738
+ const routeName = this.processTraceRouterName;
739
+ this.$router.push({
740
+ name: routeName,
741
+ query: {
742
+ applyId: this.applyId,
743
+ procId: this.procId,
744
+ instanceId: this.instanceId,
745
+ taskNode: this.taskNode,
746
+ }
747
+ })
748
+ },
749
+ audit(auditResult) {
750
+ console.log('点击了审批:', auditResult);
751
+
752
+ if (this.approvalForm.auditOpinion === '' || this.approvalForm.auditOpinion == null) {
753
+ if (!this.handleButtons || this.handleButtons.includes('auditOpinion')) {
754
+ this.updateActiveTab()
755
+ this.approvalError = true
756
+ Toast({message: '请输入审批意见', duration: '500'});
757
+ return
758
+ } else {
759
+ this.auditParams.auditOpinion = this.getAuditButtonStatus(this.auditResult);
760
+ }
761
+ }
762
+
763
+ // on confirm
764
+ this.auditResult = auditResult
765
+ this.submit()
766
+
767
+ // 关闭弹窗等
768
+ this.popupShow = false;
769
+
770
+ },
771
+ handleInputFocus() {
772
+ this.approvalError = false;
773
+ },
774
+ submit() {
775
+ let self = this;
776
+ self.auditParams = {
777
+ procId: self.procId,
778
+ applyId: self.applyId,
779
+ taskId: self.taskId,
780
+ auditOpinion: self.approvalForm.auditOpinion,
781
+ auditResult: self.auditResult,
782
+ nodeConfigMaps: self.approvalForm.nodeConfigMaps,
783
+
784
+ fileListStr: JSON.stringify(self.fileList),
785
+ //字段不清楚 暂不传递
786
+ params: null,
787
+ }
788
+ self.businessFormSave ? self.businessFormSave(self.handleSaveResult) : self.handleSaveResult(true)
789
+ },
790
+ handleSaveResult(saveResult) {
791
+ if (saveResult) {
792
+ this.executeButtonAction();
793
+ } else {
794
+ console.error('保存失败');
795
+ }
796
+ },
797
+
798
+ executeButtonAction() {
799
+ const self = this;
800
+ if (self.auditResult === '82') {
801
+ // 处理转办逻辑
802
+ this.selectHandlePopupShow = true;
803
+ } else if (self.auditResult === '80') {
804
+ // 处理调转到指定节点逻辑
805
+ self.getNodesBehind()
806
+ } else if (self.auditResult === '90') {
807
+ // 处理驳回到指定节点逻辑
808
+ self.getAllPreNodes()
809
+ } else if (self.auditResult === '40') {
810
+ Dialog.confirm({
811
+ message: '确定执行:' + self.getAuditButtonStatus(self.auditResult) + ' ?',
812
+ }).then(() => {
813
+ // 处理驳回上一节点逻辑
814
+ getPreNode(self.auditParams).then(function (resp) {
815
+ let result = resp.data
816
+ if (result.code === '30010') {
817
+ Dialog.confirm({
818
+ title: "提示",
819
+ message: result.message,
820
+ }).then(() => {
821
+ // on confirm
822
+ self.auditParams.auditResult = '70'
823
+ self.auditRequest(self.auditParams)
824
+ }).catch(() => {
825
+ })
826
+ } else {
827
+ self.auditRequest(self.auditParams)
828
+ }
829
+ })
830
+ }).catch(() => {
831
+ // on cancel
832
+ });
833
+ } else {
834
+ Dialog.confirm({
835
+ message: '确定执行:' + self.getAuditButtonStatus(self.auditResult) + ' ?',
836
+ }).then(() => {
837
+ self.auditRequest(self.auditParams)
838
+ }).catch(() => {
839
+ // on cancel
840
+ });
841
+ }
842
+ },
843
+ showToastLoading(message) {
844
+ //展示审批加载提示
845
+ return Toast.loading({
846
+ duration: 0, // 持续展示 toast
847
+ forbidClick: true,
848
+ message: message,
849
+ })
850
+ },
851
+ auditRequest(auditParams) {
852
+ let self = this
853
+ let auditResult = {
854
+ code: self.auditResult,
855
+ name: self.getAuditStatus(self.auditResult).text
856
+ }
857
+ self.showToastLoading('处理审批中');
858
+ //确认执行审批逻辑
859
+ audit(auditParams).then((resp) => { // 使用箭头函数
860
+ let result = resp.data
861
+ if (result.code === '200') {
862
+ if (result.data) {
863
+ if (Array.isArray(result.data)) {
864
+ let taskIds = result.data.map(item => item.id).join(',');
865
+ this.executionCompleted(true, result.data[0].processInstanceId, taskIds, auditResult, self.taskId);
866
+ } else if (result.data && typeof result.data === 'object') {
867
+ let taskIds = result.data.id; // 处理单个对象的情况
868
+ this.executionCompleted(true, result.data.processInstanceId, taskIds, auditResult, self.taskId);
869
+ }
870
+ } else {
871
+ this.executionCompleted(true, null, null, auditResult, self.taskId);
872
+ }
873
+ Toast.success(result.message);
874
+ } else if (result.code === '20002') {
875
+ // 流程结束
876
+ this.executionCompleted(true, '流程已结束', '流程已结束', auditResult, self.taskId)
877
+ Toast.success(result.message);
878
+ } else {
879
+ this.executionCompleted(false, null, null, auditResult, self.taskId)
880
+ Toast.fail(result.message);
881
+ }
882
+ })
883
+ },
884
+ getNodesBehind() {
885
+ getNodesBehind(this.procId, this.taskId).then(resp => {
886
+ if (resp.data.code === '200') {
887
+ if (resp.data.data.length > 0) {
888
+ this.nodeList = resp.data.data
889
+ this.nodeListDialogShow = true
890
+ } else {
891
+ Toast.fail('当前流程无后续节点')
892
+ }
893
+ } else {
894
+ Toast.fail(resp.data.message)
895
+ }
896
+ })
897
+ },
898
+ getAllPreNodes() {
899
+ getAllPreNodes(this.procId, this.taskId).then(resp => {
900
+ if (resp.data.code === '200') {
901
+ if (resp.data.data.length > 0) {
902
+ this.nodeList = resp.data.data
903
+ this.nodeListDialogShow = true
904
+ } else {
905
+ Toast.fail('当前流程无前序节点')
906
+ }
907
+ } else {
908
+ Toast.fail(resp.data.message)
909
+ }
910
+ })
911
+ },
912
+ nodeListDialogShowClose() {
913
+ //重置
914
+ this.nodeListDialogShow = false
915
+ this.auditResult = '';
916
+ this.radio = '';
917
+ },
918
+ processJumpSpecifiedNode() {
919
+ const self = this;
920
+ self.auditParams.targetTaskNode = self.nodeList[self.radio].taskNode;
921
+ self.auditRequest(self.auditParams)
922
+ },
923
+
924
+ showAuditDetail(auditComment) {
925
+ Dialog.alert({
926
+ title: '审批意见',
927
+ message: auditComment,
928
+ }).then(() => {
929
+ // on close
930
+ });
931
+ },
932
+ uploadFile(fileList) {
933
+ fileList.forEach(item => {
934
+ console.log(item)
935
+ })
936
+ },
937
+ getAttach(item) {
938
+ let fjUrl = window.location.origin +
939
+ // api/smart-flow-server + /manage/oss/file/get/ + xxx
940
+ config.smartFlowServerContext + "/manage/oss/file/get/" + item.fileId
941
+ let fjName = item.fileName
942
+ flutterUtil.openFile(fjUrl, fjName);
943
+ //window.open(config.smartFlowServerContext + "/manage/oss/file/get/" + item.fileId, "_blank");
944
+ },
945
+ updateActiveTab() {
946
+ this.$nextTick(() => {
947
+ const tabsElement = this.$refs.tabs;
948
+ if (tabsElement) {
949
+ const numberOfTabs = tabsElement.$el.querySelectorAll('.van-tab').length;
950
+ this.active = numberOfTabs - 1;
951
+ }
952
+ });
953
+ },
954
+ handleSelectResult(checkResult) {
955
+ console.log('Selected result:', checkResult);
956
+ const self = this;
957
+ if (this.nextNodePopupShow === true) {
958
+ console.log("这里是选择办理人")
959
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].assignee.id = checkResult[0].userId;
960
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].assignee.name = checkResult[0].userName;
961
+ } else {
962
+ console.log("这里是选择转办人")
963
+ self.auditParams.selectedUserId = checkResult[0].userId;
964
+ self.auditRequest(self.auditParams);
965
+ }
966
+ self.selectHandlePopupShow = false;
967
+ },
968
+ selectOrganizeHandle(handle, checkedNodes) {
969
+ if (handle === 'select') {
970
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].candidateGroups.organs =
971
+ checkedNodes.map(item => ({
972
+ name: item.node.title,
973
+ id: item.node.id,
974
+ }));
975
+ }
976
+ this.selectOrganizePopupShow = false
977
+ },
978
+ selectUserHandle(checkResult) {
979
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].candidateGroups.users =
980
+ checkResult.map(item => ({
981
+ name: item.userName,
982
+ id: item.userId,
983
+ }));
984
+ this.selectUserPopupShow = false;
985
+ },
986
+ selectPositionHandle(handle, result) {
987
+ if (handle === 'select') {
988
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].candidateGroups.positions =
989
+ result.map(item => ({
990
+ name: item.name,
991
+ id: item.id,
992
+ }));
993
+ }
994
+ this.selectPositionPopupShow = false
995
+ },
996
+ selectRoleHandle(handle, result) {
997
+ if (handle === 'select') {
998
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].candidateGroups.roles =
999
+ result.map(item => ({
1000
+ name: item.name,
1001
+ id: item.id,
1002
+ }));
1003
+ }
1004
+ this.selectRolePopupShow = false
1005
+ },
1006
+ },
1007
+ activated() {
1008
+ if (!this.activeFirstTab) {
1009
+ this.updateActiveTab();
1010
+ }
1011
+ },
1012
+ mounted() {
1013
+ this.initData()
1014
+ // 默认选中最后一个tab页
1015
+ if (!this.activeFirstTab) {
1016
+ this.updateActiveTab();
1017
+ }
1018
+ }
1019
+
1020
+ };
1021
+ </script>
1022
+
1023
+ <style scoped>
1024
+ @import 'styles/global.css';
1025
+
1026
+ ::v-deep .van-tabs__content {
1027
+ background: #f7f8fa;
1028
+ }
1029
+
1030
+ ::v-deep .van-collapse-item__content {
1031
+ padding: 0 0;
1032
+ }
1033
+
1034
+ ::v-deep .van-uploader__file-name {
1035
+ margin-top: 0;
1036
+ }
1037
+
1038
+ ::v-deep .van-uploader__upload {
1039
+ margin: 0;
1040
+ }
1041
+
1042
+ /* 自定义标签页文字的样式 */
1043
+ ::v-deep .van-tab--active .van-tab__text {
1044
+ color: #3478f6; /* 设置激活状态下的文字颜色为蓝色 */
1045
+ font-size: 16px; /* 增大激活状态下的字体大小 */
1046
+ font-weight: bold;
1047
+ }
1048
+
1049
+ ::v-deep .van-tabs__line {
1050
+ display: none;
1051
+ }
1052
+
1053
+ .custom-bottom-bar {
1054
+ position: fixed;
1055
+ bottom: 0;
1056
+ width: 100%;
1057
+ display: flex;
1058
+ justify-content: space-around;
1059
+ background-color: #fff;
1060
+ padding: 10px 0;
1061
+ border-top: 1px solid #eaeaea;
1062
+ box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.05);
1063
+ z-index: 1000;
1064
+ }
1065
+
1066
+ .bar-item {
1067
+ flex: 1;
1068
+ text-align: center;
1069
+ padding: 10px 0;
1070
+ font-size: 16px;
1071
+ border-radius: 4px;
1072
+ }
1073
+
1074
+ .bar-item:active {
1075
+ background-color: #e6e6e6; /* 点击时的背景颜色 */
1076
+ }
1077
+
1078
+ .approve {
1079
+ color: #fff;
1080
+ background: linear-gradient(90deg, #0096FF, #1677FF);
1081
+ border-radius: 9px;
1082
+ margin: 0 20px;
1083
+ }
1084
+
1085
+ .approve:active {
1086
+ background-color: #1989fa;
1087
+ }
1088
+
1089
+ .title-info {
1090
+ font-weight: bold;
1091
+ font-size: 14px;
1092
+ padding: 20px 10px;
1093
+ }
1094
+
1095
+ .info-icon {
1096
+ display: inline-block;
1097
+ color: white; /* 自定义文字颜色 */
1098
+ padding: 3px 3px; /* 标签内边距 */
1099
+ border-radius: 4px; /* 标签圆角 */
1100
+ font-size: 13px; /* 字体大小 */
1101
+ margin-right: 5px;
1102
+ }
1103
+
1104
+ .file-icon {
1105
+ display: flex;
1106
+ justify-content: center;
1107
+ align-items: center;
1108
+ width: 40px;
1109
+ height: 40px;
1110
+
1111
+ font-weight: bold;
1112
+ color: white; /* 自定义文字颜色 */
1113
+ padding: 3px 3px; /* 标签内边距 */
1114
+ border-radius: 8px; /* 标签圆角 */
1115
+ font-size: 25px; /* 字体大小 */
1116
+ margin-right: 15px;
1117
+ }
1118
+
1119
+ .van-cell__value {
1120
+ flex: 0 auto;
1121
+ }
1122
+
1123
+ .popup-option {
1124
+ padding: 16px;
1125
+ text-align: center;
1126
+ border-bottom: 1px solid #ebebeb;
1127
+ color: #333;
1128
+ font-size: 16px;
1129
+ cursor: pointer;
1130
+ }
1131
+
1132
+ .popup-option:last-child {
1133
+ border-bottom: none;
1134
+ }
1135
+
1136
+ .popup-option.highlighted {
1137
+ color: #0d88ff;
1138
+ font-weight: bold;
1139
+ }
1140
+
1141
+ .approvalForm-value {
1142
+ flex: 1;
1143
+ white-space: nowrap; /* 防止文本换行 */
1144
+ overflow: hidden; /* 超出部分隐藏 */
1145
+ text-overflow: ellipsis; /* 用省略号表示被隐藏的文本 */
1146
+ }
1147
+
1148
+ .popup-content {
1149
+ padding: 16px;
1150
+ font-size: 14px;
1151
+ color: #333;
1152
+ white-space: pre-wrap; /* 保留换行和空格 */
1153
+ }
1154
+
1155
+ .table-header {
1156
+ display: flex;
1157
+ background-color: #f8f8f8;
1158
+ font-size: 15px;
1159
+ }
1160
+
1161
+ .table-row {
1162
+ display: flex;
1163
+ border-bottom: 1px solid #ebedf0;
1164
+ font-size: 14px;
1165
+ }
1166
+
1167
+ .table-header > div,
1168
+ .table-row > div {
1169
+ flex: 1;
1170
+ text-align: center;
1171
+ padding: 10px;
1172
+ }
1173
+
1174
+ .table-row:last-child {
1175
+ border-bottom: none;
1176
+ }
1177
+
1178
+ .van-radio {
1179
+ justify-content: center;
1180
+ }
1181
+
1182
+ ::v-deep .van-dialog__header {
1183
+ padding: 10px 0
1184
+ }
1185
+
1186
+ </style>