@lambo-design-mobile/workflow-approve 1.0.0-beta.3 → 1.0.0-beta.30

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.
@@ -1,10 +1,13 @@
1
1
  <template>
2
2
  <div>
3
- <van-tabs ref="tabs" style="padding-bottom: 60px" v-model="active" @click="onTabClick" animated>
3
+ <van-tabs ref="tabs" style="padding-bottom: 60px" v-model="active"
4
+ @click="onTabClick"
5
+ sticky :offset-top="offsetTop"
6
+ animated>
4
7
  <slot name="business-content"/>
5
8
  <van-tab title="流程信息">
6
9
  <div style="padding: 0 10px">
7
- <div v-if="!isDetail">
10
+ <div v-if="!isDetail && showAuditOpinion">
8
11
  <div class="title-info">
9
12
  <van-icon class="info-icon" style="background-color: #0d88ff" name="cluster-o"/>
10
13
  审批信息
@@ -12,9 +15,12 @@
12
15
  <div class="approvalForm-section">
13
16
  <van-cell-group>
14
17
  <van-field
18
+ v-if="handleButtons && handleButtons.includes('auditOpinion')"
15
19
  v-model="approvalForm.auditOpinion"
16
20
  rows="3"
17
21
  autosize
22
+ @focus="handleInputFocus"
23
+ :error="approvalError"
18
24
  required
19
25
  label="审批意见"
20
26
  type="textarea"
@@ -24,19 +30,17 @@
24
30
  />
25
31
  <div v-if="approvalForm.nextNode!==''">
26
32
  <van-cell title="下一环节" :value="approvalForm.nextNode"/>
27
- <van-cell @click="nextNodePopupShow = true" title="办理人员" value-class="approvalForm-value"
28
- :value="approvalForm.handlingPersonnel">
29
- <template #right-icon>
30
- <van-icon name="friends-o" size="16" style="line-height: inherit;padding-left: 5px"/>
31
- </template>
32
- </van-cell>
33
- <div class="van-hairline--bottom"></div>
34
- </div>
35
- <div v-else>
36
- <van-cell title="下一环节" value="无下一环节"/>
33
+ <div v-if="handleButtons && handleButtons.includes('appointHandler')">
34
+ <van-cell @click="handleNextNodePopupShow" 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>
37
41
  <div class="van-hairline--bottom"></div>
38
42
  </div>
39
- <div v-if="handleButtons.includes('attachmentFile')">
43
+ <div v-if="handleButtons && handleButtons.includes('attachmentFile')">
40
44
  <upload-file :multiple="true"
41
45
  :oss-server-context="config.smartFlowServerContext"
42
46
  :oss-file-put-url="config.ossFilePutUrl"
@@ -59,47 +63,14 @@
59
63
  <van-collapse-item v-for="(items,index) in processHistory" :key="index" :title="items[0].taskName"
60
64
  :name="index">
61
65
  <div v-for="(item,index) in items" :key="index" class="record-item">
62
- <van-cell center>
63
- <template #icon>
64
- <div
65
- v-if="['通过', '跳转指定节点'].includes(getAuditStatus(item.auditResult).text)"
66
- class="completed-icon">
67
- <van-icon class="iconfont" class-prefix='icon' :name=getAuditStatus(item.auditResult).icon
68
- size="30px" color="#fff"/>
69
- </div>
70
- <div v-else class="pending-icon">
71
- <van-icon class="iconfont" class-prefix='icon' :name=getAuditStatus(item.auditResult).icon
72
- size="30px" color="#fff"/>
73
- </div>
74
- </template>
75
- <template #title>
76
- <div :style="{ color: getAuditStatus(item.auditResult).color }"
77
- style="font-size: 16px;font-weight: bold;padding:5px 0;">
78
- {{ item.auditName[0] }}
79
- </div>
80
- <div>{{ item.auditOrganName[0] }}
81
- <span v-if="item.auditComment" @click="showAuditDetail(item.auditComment)">
82
- | 审批意见 <van-icon name="comment-circle-o"/>
83
- </span>
84
- </div>
85
- </template>
86
- <template #label>
87
- <div>{{ item.startDate }}{{ item.auditDate }}</div>
88
- </template>
89
- <template #default>
90
- <span :style="{ color: getAuditStatus(item.auditResult).color }">{{
91
- getAuditStatus(item.auditResult).text
92
- }}</span>
93
- </template>
94
- </van-cell>
66
+ <approval-node-cell :node-detail="item" :folding-approval-comments="foldingApprovalComments" />
95
67
  <div v-if="index !== items.length - 1" class="van-hairline--bottom"></div>
96
68
  </div>
97
69
  </van-collapse-item>
98
-
99
70
  </van-collapse>
100
71
  </div>
101
72
  </div>
102
- <div v-if="handleButtons.includes('attachmentFile')">
73
+ <div v-if="handleButtons && handleButtons.includes('attachmentFile')">
103
74
  <div class="title-info">
104
75
  <van-icon class="info-icon" style="background-color: #029ea0" name="link-o"/>
105
76
  查看附件
@@ -144,30 +115,16 @@
144
115
  <!-- 底部按钮 等其他内容 -->
145
116
  <div>
146
117
  <van-popup v-model="popupShow" style="padding-top: 40px" closeable round position="bottom">
147
- <div v-if="handleButtons.includes('auditTo70')" class="popup-option" @click="audit('70')">
148
- {{ getAuditButtonStatus(70) }}
149
- </div>
150
- <div v-if="handleButtons.includes('auditTo40')" class="popup-option highlighted"
151
- @click="audit('40')">
152
- {{ getAuditButtonStatus(40) }}
153
- </div>
154
- <div v-if="handleButtons.includes('auditTo90')" class="popup-option" @click="audit('90')">
155
- {{ getAuditButtonStatus(90) }}
156
- </div>
157
- <div v-if="handleButtons.includes('auditTo80')" class="popup-option" @click="audit('80')">
158
- {{ getAuditButtonStatus(80) }}
159
- </div>
160
- <div v-if="handleButtons.includes('auditTo82')" class="popup-option" @click="audit('82')">
161
- {{ getAuditButtonStatus(82) }}
162
- </div>
163
- <div v-if="handleButtons.includes('auditTo50')" class="popup-option" @click="audit('50')">
164
- {{ getAuditButtonStatus(50) }}
165
- </div>
118
+ <template v-for="(button, index) of operationButtons.moreOperations">
119
+ <div :class="button.class" @click="button.action">
120
+ {{ button.label }}
121
+ </div>
122
+ </template>
166
123
  </van-popup>
167
124
  <div class="custom-bottom-bar">
168
- <div v-if="!isDetail" class="bar-item" @click="onMore">更多</div>
169
- <div class="bar-item" @click="onTrack">流程跟踪</div>
170
- <div v-if="!isDetail" class="bar-item approve" @click="audit('30')">{{ getAuditButtonStatus(30) }}</div>
125
+ <div v-for="(button, index) of operationButtons.bottomButtons" :class="button.class" @click="button.action">
126
+ {{ button.label }}
127
+ </div>
171
128
  </div>
172
129
  </div>
173
130
  <van-popup @click-close-icon="handleSelect('cancel')" v-model="nextNodePopupShow"
@@ -182,70 +139,75 @@
182
139
  <van-field name="radio" label="人员类型" input-align="right">
183
140
  <template #input>
184
141
  <van-radio-group v-model="item.personType" direction="horizontal">
185
- <van-radio name="assignee">办理人</van-radio>
186
- <van-radio name="candidate">候选人</van-radio>
142
+ <van-radio v-if="!nextAssigneeSelectionType || nextAssigneeSelectionType === 'assignee'" name="assignee">办理人</van-radio>
143
+ <van-radio v-if="!nextAssigneeSelectionType || nextAssigneeSelectionType === 'candidate'" name="candidate">候选人</van-radio>
187
144
  </van-radio-group>
188
145
  </template>
189
146
  </van-field>
190
147
  <van-cell v-if="item.personType === 'assignee'" title="办理人"
191
148
  :value="(item.assignee.name ? item.assignee.name : '未指定') + ':' + (item.assignee.id ? item.assignee.id : '未指定')"
192
- @click="nextNodeHandleIndex=index;selectHandlePopupShow=true;">
149
+ @click="handleNextNodeAssigneeSelect(index, 'appointHandler')">
193
150
  </van-cell>
194
151
  <van-cell v-if="item.personType === 'candidate'" title="候选人" @click="toggleDetails(index)"
195
152
  :is-link="true" :arrow-direction="detailsVisible[index] ? 'down' : ''">
196
153
  </van-cell>
197
154
  <div v-if="detailsVisible[index] && item.personType === 'candidate'">
198
- <van-field @click="handleSelectUserPopupShow(index)" label="候选用户" readonly placeholder="点击选择"
155
+ <van-field v-if="useTransferUser" @click="handleNextNodeUserSelect(index, 'appointHandlerWithCandGroups')" label="候选用户" readonly placeholder="点击选择"
156
+ :value="(item.candidateGroups?.users ?? []).map(user => user.name).join(',')">
157
+ </van-field>
158
+ <van-field v-if="!useTransferUser && !useTransferOrgan" @click="handleCandUserSelect(index)" label="候选用户" readonly placeholder="点击选择"
199
159
  :value="(item.candidateGroups?.users ?? []).map(user => user.name).join(',')">
200
160
  </van-field>
201
- <van-field @click="handleSelectPositionPopupShow(index)" label="候选岗位" readonly placeholder="点击选择"
161
+ <van-field v-if="!useTransferUser && !useTransferOrgan" @click="handleSelectPositionPopupShow(index)" label="候选岗位" readonly placeholder="点击选择"
202
162
  :value="(item.candidateGroups?.positions ?? []).map(position => position.name).join(',')">
203
163
  </van-field>
204
- <van-field @click="handleSelectRolePopupShow(index)" label="候选角色" readonly placeholder="点击选择"
164
+ <van-field v-if="!useTransferUser && !useTransferOrgan" @click="handleSelectRolePopupShow(index)" label="候选角色" readonly placeholder="点击选择"
205
165
  :value="(item.candidateGroups?.roles ?? []).map(role => role.name).join(',')">
206
166
  </van-field>
207
- <van-field @click="handleSelectOrganizePopupShow(index)" label="候选组织" readonly placeholder="点击选择"
167
+ <van-field v-if="!useTransferUser" @click="handleSelectOrganizePopupShow(index)" label="候选组织" readonly placeholder="点击选择"
208
168
  :value="(item.candidateGroups?.organs ?? []).map(organ => organ.name).join(',')">
209
169
  </van-field>
210
170
  </div>
211
- <van-cell center title="停留时间">
212
- <template #default>
213
- <div style="display: flex; align-items: center;">
214
- <div style="display: flex; align-items: center; margin-right: 16px;">
215
- <van-stepper disable-input button-size="22" min="0" v-model="item.remainDay"/>
216
- <span style="margin-left: 4px;">天</span>
217
- </div>
171
+ <div v-if="handleButtons && handleButtons.includes('appointTimeoutTime')">
172
+ <van-cell center title="停留时间">
173
+ <template #default>
218
174
  <div style="display: flex; align-items: center;">
219
- <van-stepper disable-input button-size="22" min="0" v-model="item.remainTime"/>
220
- <span style="margin-left: 4px;">小时</span>
221
- </div>
222
- </div>
223
- </template>
224
- </van-cell>
225
- <van-cell title="任务提前">
226
- <template #default>
227
- <div style="display: flex; align-items: center;">
228
- <div style="display: flex; align-items: center; margin-right: 16px;">
229
- <van-stepper disable-input button-size="22" min="0" v-model="item.inAdvanceDay"/>
230
- <span style="margin-left: 4px;">天</span>
175
+ <div style="display: flex; align-items: center; margin-right: 16px;">
176
+ <van-stepper disable-input button-size="22" min="0" v-model="item.remainDay"/>
177
+ <span style="margin-left: 4px;">天</span>
178
+ </div>
179
+ <div style="display: flex; align-items: center;">
180
+ <van-stepper disable-input button-size="22" min="0" v-model="item.remainTime"/>
181
+ <span style="margin-left: 4px;">小时</span>
182
+ </div>
231
183
  </div>
184
+ </template>
185
+ </van-cell>
186
+ <van-cell title="任务提前">
187
+ <template #default>
232
188
  <div style="display: flex; align-items: center;">
233
- <van-stepper disable-input button-size="22" min="0" v-model="item.inAdvanceTime"/>
234
- <span style="margin-left: 4px;">小时</span>
189
+ <div style="display: flex; align-items: center; margin-right: 16px;">
190
+ <van-stepper disable-input button-size="22" min="0" v-model="item.inAdvanceDay"/>
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.inAdvanceTime"/>
195
+ <span style="margin-left: 4px;">小时</span>
196
+ </div>
235
197
  </div>
236
- </div>
237
- </template>
238
- </van-cell>
239
- <van-field
240
- readonly
241
- clickable
242
- input-align="right"
243
- name="picker"
244
- :value="(handleTypeList.find(handleType => handleType.value === item.processing) || {}).label"
245
- label="处理方式"
246
- placeholder="点击选择处理方式"
247
- @click="$set(showPicker, index, true)"
248
- />
198
+ </template>
199
+ </van-cell>
200
+ <van-field
201
+ readonly
202
+ clickable
203
+ input-align="right"
204
+ name="picker"
205
+ :value="(handleTypeList.find(handleType => handleType.value === item.processing) || {}).label"
206
+ label="处理方式"
207
+ placeholder="点击选择处理方式"
208
+ @click="$set(showPicker, index, true)"
209
+ />
210
+ </div>
249
211
  <van-popup v-model="showPicker[index]" position="bottom">
250
212
  <van-picker
251
213
  show-toolbar
@@ -260,17 +222,19 @@
260
222
  <div class="bar-item approve" @click="handleSelect('select')">确认</div>
261
223
  </div>
262
224
  </van-popup>
263
- <van-popup v-if="selectHandlePopupShow" v-model="selectHandlePopupShow" closeable round position="bottom"
264
- :style="{ height: '80%' }" style="background: #f7f8fa">
265
- <select-handle title="指定办理人" :multi-select="false" :procType="procType" @selectHandle="handleSelectResult"></select-handle>
225
+ <van-popup v-if="selectUserPopupShow" v-model="selectUserPopupShow" closeable round position="bottom"
226
+ :style="{ height: '80%' }" :class="selectUserPopupParams.class">
227
+ <select-handle :title="selectUserPopupParams.title" :multi-select="selectUserPopupParams.multiSelect" :procType="procType" :organ-tree-type="selectUserPopupParams.organTreeType"
228
+ :org-list="selectUserPopupParams.orgList" :user-list="selectUserPopupParams.userList" @selectHandle="selectUserPopupParams.action"></select-handle>
266
229
  </van-popup>
267
230
  <van-popup v-if="selectOrganizePopupShow" v-model="selectOrganizePopupShow" closeable round position="bottom"
268
231
  :style="{ height: '80%' }">
269
- <select-organize ref="selectOrganize" :all-organize="false" :organize-id-list="permissionsScope.O" @selectOrganizeHandle="selectOrganizeHandle" ></select-organize>
232
+ <select-organize ref="selectOrganize" :treetype-id="orgTreeType" :all-organize="false" :organize-id-list="permissionsScope.O" @selectOrganizeHandle="selectOrganizeHandle" ></select-organize>
270
233
  </van-popup>
271
- <van-popup v-if="selectUserPopupShow" v-model="selectUserPopupShow" closeable round position="bottom"
234
+ <van-popup v-if="selectCandUserPopupShow" v-model="selectCandUserPopupShow" closeable round position="bottom"
272
235
  :style="{ height: '80%' }">
273
- <select-handle title="选择用户" :multi-select="true" :procType="procType" @selectHandle="selectUserHandle" ></select-handle>
236
+ <select-handle title="选择用户" :multi-select="true" :procType="procType" :organ-tree-type="selectUserPopupParams.organTreeType" :org-list="selectUserPopupParams.orgList"
237
+ :user-list="selectUserPopupParams.userList" @selectHandle="selectCandUserHandle"></select-handle>
274
238
  </van-popup>
275
239
  <van-popup v-if="selectPositionPopupShow" v-model="selectPositionPopupShow" closeable round position="bottom"
276
240
  :style="{ height: '80%' }">
@@ -280,7 +244,23 @@
280
244
  :style="{ height: '80%' }">
281
245
  <select-normal-list title="选择角色" :id-list="permissionsScope.R" @selectNormalListHandle="selectRoleHandle" :parse-function="parseRoleFunction"></select-normal-list>
282
246
  </van-popup>
283
- <van-dialog v-model="nodeListDialogShow" @confirm="processJumpSpecifiedNode()" @close="nodeListDialogShowClose()"
247
+ <van-popup v-if="selectReductionUserPopupShow" v-model="selectReductionUserPopupShow" closeable round position="bottom" :style="{ height: '80%' }">
248
+ <div id="reductionListContent" style="background: #f7f8fa">
249
+ <div class="van-nav-bar__content">
250
+ <div class="van-nav-bar__title">{{ getAuditButtonStatus(83) }}</div>
251
+ </div>
252
+ <van-list style="padding-top: 15px">
253
+ <select-handle-card ref="selectHandleCard" :multi-select="true" :person-list="unapprovedAssigneeList"
254
+ :result.sync="reductionUsers" :show-status="false">
255
+ </select-handle-card>
256
+ </van-list>
257
+ </div>
258
+ <div v-if="reductionUsers.length > 0" class="custom-bottom-bar">
259
+ <div class="bar-item" @click="handleReductionUserSelect('cancel')">取消</div>
260
+ <div class="bar-item approve" @click="handleReductionUserSelect('select')">选择</div>
261
+ </div>
262
+ </van-popup>
263
+ <van-dialog v-model="nodeListDialogShow" @confirm="processJumpSpecifiedNode()" @close="nodeListDialogShowClose()" :before-close="onBeforeClose"
284
264
  :title="getAuditButtonStatus(auditResult)" show-cancel-button>
285
265
  <van-radio-group v-model="radio">
286
266
  <van-cell-group>
@@ -291,7 +271,7 @@
291
271
  </div>
292
272
  <div class="table-row"
293
273
  v-for="(item, index) in nodeList" :key="index"
294
- @click="radio = index">
274
+ @click="selectNode(item, index)">
295
275
  <div>{{ item.taskName }}</div>
296
276
  <div>
297
277
  <van-tag v-if="item.auditResult" :type="getAuditStatus(item.auditResult).type">
@@ -305,9 +285,48 @@
305
285
  <van-radio :name='index'/>
306
286
  </div>
307
287
  </div>
288
+ <div v-if="showRejectOption" class="reject-attribute">
289
+ <div :style="{ width: rejectAttributeTitleWidth }">驳回给:</div>
290
+ <van-radio-group v-model="rejectToTakeOver">
291
+ <van-radio name="original">原办理人</van-radio>
292
+ <van-radio name="actual">实际办理人</van-radio>
293
+ </van-radio-group>
294
+ </div>
295
+ <div v-if="showRejectToSelected" class="table-row">
296
+ <van-field @click="handleSelectRejectUser" label="选择驳回人员:" :label-width="rejectAttributeTitleWidth" readonly placeholder="点击选择" :value="rejectUsers.name"></van-field>
297
+ </div>
298
+ <div v-if="showProcessControl" class="reject-attribute">
299
+ <div :style="{ width: rejectAttributeTitleWidth }">驳回的节点通过后:</div>
300
+ <van-radio-group v-model="rejectAttribute">
301
+ <van-radio v-for="(item, index) in rejectAttributeList" :key="index" :name="item.type">{{ item.name }}</van-radio>
302
+ </van-radio-group>
303
+ </div>
308
304
  </van-cell-group>
309
305
  </van-radio-group>
310
306
  </van-dialog>
307
+ <van-dialog v-model="rejectAttributesBoxShow" @confirm="processRejectNode()" @close="rejectAttributesDialogShowClose()"
308
+ title="驳回设置" show-cancel-button>
309
+ <div v-if="showProcessControl" class="reject-attribute">
310
+ <div :style="{ width: rejectAttributeTitleWidth }">驳回的节点通过后:</div>
311
+ <van-radio-group v-model="rejectAttribute">
312
+ <van-radio v-for="(item, index) in rejectAttributeList" :key="index" :name="item.type">{{ item.name }}</van-radio>
313
+ </van-radio-group>
314
+ </div>
315
+ <div v-if="showRejectOption" class="reject-attribute">
316
+ <div :style="{ width: rejectAttributeTitleWidth }">驳回给:</div>
317
+ <van-radio-group v-model="rejectToTakeOver">
318
+ <van-radio name="original">原办理人</van-radio>
319
+ <van-radio name="actual">实际办理人</van-radio>
320
+ </van-radio-group>
321
+ </div>
322
+ <div v-if="showRejectToSelected" class="reject-attribute">
323
+ <van-field @click="handleSelectRejectUser" label="选择驳回人员:" :label-width="rejectAttributeTitleWidth" readonly placeholder="点击选择" :value="rejectUsers.name"></van-field>
324
+ </div>
325
+ </van-dialog>
326
+ <van-popup v-if="selectRejectUserPopupShow" v-model="selectRejectUserPopupShow" closeable round position="bottom"
327
+ :style="{ height: '80%' }">
328
+ <select-handle title="选择驳回人员" :multi-select="true" :procType="procType" :user-list="rejectUserList" @selectHandle="selectRejectUserHandle"></select-handle>
329
+ </van-popup>
311
330
  </div>
312
331
  </template>
313
332
 
@@ -321,8 +340,16 @@ import {
321
340
  getNodesBehind,
322
341
  getPosition,
323
342
  getPreNode,
343
+ getNodeData,
324
344
  getProcessHis,
325
- getProcessType, getRole
345
+ getProcessType, getRole,
346
+ getTransferRange,
347
+ updateMultitaskInstance,
348
+ getUnapprovedListOfMultiNode,
349
+ getProcessTodoList,
350
+ getProcessAttributes,
351
+ listAll,
352
+ getDoneDetail
326
353
  } from "../api";
327
354
  import {Dialog, Toast} from "vant";
328
355
  import UploadFile from '@lambo-design-mobile/upload-file';
@@ -331,15 +358,203 @@ import SelectHandle from "./SelectHandle.vue";
331
358
  import Tree from "./tree/Tree.vue";
332
359
  import SelectOrganize from "./SelectOrganize.vue";
333
360
  import SelectNormalList from "./SelectNormalList.vue";
361
+ import ApprovalNodeCell from "./ApprovalNodeCell.vue";
362
+ import {getAuditStatus} from "./js/global";
363
+ import {NodeType} from "./utils/const";
364
+ import SelectHandleCard from "./SelectHandleCard.vue";
334
365
 
335
366
 
336
367
  export default {
337
368
  computed: {
338
369
  config() {
339
370
  return config
371
+ },
372
+ rejectAttributeTitleWidth(){
373
+ if (this.showProcessControl) {
374
+ return '145px';
375
+ }
376
+ if (this.showRejectToSelected) {
377
+ return '100px';
378
+ }
379
+ if (this.showRejectOption) {
380
+ return '75px';
381
+ }
382
+ return 'auto';
383
+ },
384
+ showAuditOpinion(){
385
+ return this.handleButtons && (this.handleButtons.includes('auditOpinion') || this.handleButtons.includes('appointHandler') || this.handleButtons.includes('attachmentFile'))
386
+ },
387
+ operationButtons() {
388
+ // 流程跟踪按钮
389
+ const processTraceButton = {
390
+ id: 'processTrace',
391
+ label: '流程跟踪',
392
+ condition: this.handleButtons?.includes('processTrace'),
393
+ class: 'bar-item',
394
+ action: () => this.onTrack(),
395
+ }
396
+
397
+ // 审批按钮
398
+ const auditButtonDefinitions = [
399
+ {
400
+ id: 'auditTo30',
401
+ label: this.getAuditButtonStatus(30),
402
+ condition: this.handleButtons?.includes('auditTo30') && !this.isDetail && !this.revokeDelegateTask && !this.appointTask && !this.coSignVotingTask,
403
+ class: 'bar-item approve',
404
+ action: () => this.audit('30'),
405
+ },
406
+ {
407
+ id: 'auditTo62',
408
+ label: this.getAuditButtonStatus(62),
409
+ condition: !this.isDetail && this.revokeDelegateTask,
410
+ class: 'bar-item approve',
411
+ action: () => this.audit('62'),
412
+ },
413
+ {
414
+ id: 'auditTo61',
415
+ label: this.getAuditButtonStatus(61),
416
+ condition: !this.isDetail && this.appointTask,
417
+ class: 'bar-item approve',
418
+ action: () => this.audit('61'),
419
+ },
420
+ {
421
+ id: 'auditTo32',
422
+ label: this.getAuditButtonStatus(32),
423
+ condition: this.coSignVotingTask,
424
+ class: 'bar-item disagree',
425
+ action: () => this.audit('32'),
426
+ },
427
+ {
428
+ id: 'auditTo31',
429
+ label: this.getAuditButtonStatus(31),
430
+ condition: this.coSignVotingTask,
431
+ class: 'bar-item approve',
432
+ action: () => this.audit('31'),
433
+ },
434
+ ]
435
+
436
+ // 实际可以展示的审批按钮
437
+ const validAuditButtons = auditButtonDefinitions.filter(item => item.condition)
438
+ const showButtons = !this.appointTask && !this.rejectedTask && !this.revokeDelegateTask && !this.coSignVotingTask && !this.isDetail
439
+ const handleButtonsDefinitions = [
440
+ {
441
+ id: 'delegateTask',
442
+ label: this.getAuditButtonStatus(84),
443
+ condition: this.curNodeType === NodeType.UserTask &&
444
+ this.handleButtons?.includes('delegateTask') &&
445
+ showButtons,
446
+ class: 'popup-option',
447
+ barClass: 'bar-item approve',
448
+ action: () => this.audit('84'),
449
+ },
450
+ {
451
+ id: 'addMultitaskInstance',
452
+ label: this.getAuditButtonStatus(81),
453
+ condition: this.curNodeType === NodeType.MultiNode &&
454
+ this.handleButtons?.includes('addMultitaskInstance') &&
455
+ showButtons,
456
+ class: 'popup-option',
457
+ barClass: 'bar-item approve',
458
+ action: () => this.showUpdateMultitaskInstanceModal('81'),
459
+ },
460
+ {
461
+ id: 'reductionMultitaskInstance',
462
+ label: this.getAuditButtonStatus(83),
463
+ condition: this.curNodeType === NodeType.MultiNode &&
464
+ this.handleButtons?.includes('reductionMultitaskInstance') &&
465
+ showButtons,
466
+ class: 'popup-option',
467
+ barClass: 'bar-item disagree',
468
+ action: () => this.showUpdateMultitaskInstanceModal('83'),
469
+ },
470
+ {
471
+ id: 'auditTo70',
472
+ label: this.getAuditButtonStatus(70),
473
+ condition: this.handleButtons && this.handleButtons.includes('auditTo70') && showButtons,
474
+ class: 'popup-option',
475
+ barClass: 'bar-item disagree',
476
+ action: () => this.audit('70'),
477
+ },
478
+ {
479
+ id: 'auditTo40',
480
+ label: this.getAuditButtonStatus(40),
481
+ condition: this.handleButtons && this.handleButtons.includes('auditTo40') && showButtons,
482
+ class: 'popup-option highlighted',
483
+ barClass: 'bar-item disagree',
484
+ action: () => this.audit('40'),
485
+ },
486
+ {
487
+ id: 'auditTo90',
488
+ label: this.getAuditButtonStatus(90),
489
+ condition: this.handleButtons && this.handleButtons.includes('auditTo90') && showButtons,
490
+ class: 'popup-option',
491
+ barClass: 'bar-item disagree',
492
+ action: () => this.audit('90'),
493
+ },
494
+ {
495
+ id: 'auditTo80',
496
+ label: this.getAuditButtonStatus(80),
497
+ condition: this.handleButtons && this.handleButtons.includes('auditTo80') && showButtons,
498
+ class: 'popup-option',
499
+ barClass: 'bar-item approve',
500
+ action: () => this.audit('80'),
501
+ },
502
+ {
503
+ id: 'auditTo82',
504
+ label: this.getAuditButtonStatus(82),
505
+ condition: this.handleButtons && this.handleButtons.includes('auditTo82') && showButtons,
506
+ class: 'popup-option',
507
+ barClass: 'bar-item approve',
508
+ action: () => this.audit('82'),
509
+ },
510
+ {
511
+ id: 'auditTo50',
512
+ label: this.getAuditButtonStatus(50),
513
+ condition: this.handleButtons && this.handleButtons.includes('auditTo50') && showButtons,
514
+ class: 'popup-option',
515
+ barClass: 'bar-item disagree',
516
+ action: () => this.audit('50'),
517
+ },
518
+ ]
519
+
520
+ // 实际更多操作按钮
521
+ let moreOperations = handleButtonsDefinitions.filter(item => item.condition)
522
+
523
+ let moreButtons = []
524
+ let borrowedButton = null;
525
+ // 如果审批按钮为空,且更多操作里有按钮,则借调最后一个
526
+ if (validAuditButtons.length === 0 && moreOperations.length > 0) {
527
+ borrowedButton = moreOperations.pop();
528
+ borrowedButton.class = borrowedButton.barClass
529
+ }
530
+ if (!this.isDetail) {
531
+ if (moreOperations.length > 1) {
532
+ moreButtons.push({
533
+ id: 'onMore',
534
+ label: '更多',
535
+ condition: true,
536
+ class: 'bar-item',
537
+ action: () => this.onMore(),
538
+ })
539
+ } else if (moreOperations.length === 1) {
540
+ moreButtons.push({
541
+ id: moreOperations[0].id,
542
+ label: moreOperations[0].label,
543
+ condition: true,
544
+ class: 'bar-item',
545
+ action: () => moreOperations[0].action(),
546
+ })
547
+ }
548
+ }
549
+ const baseList = [...moreButtons, processTraceButton]
550
+ if (borrowedButton) {
551
+ baseList.splice(2, 0, borrowedButton);
552
+ }
553
+ const bottomButtons = [...baseList, ...validAuditButtons]
554
+ return { bottomButtons, moreOperations }
340
555
  }
341
556
  },
342
- components: {SelectNormalList, SelectOrganize, Tree, SelectHandle, UploadFile},
557
+ components: {ApprovalNodeCell, SelectNormalList, SelectOrganize, Tree, SelectHandle, UploadFile, SelectHandleCard},
343
558
  props: {
344
559
  //业务表单保存方法
345
560
  businessFormSave: {
@@ -359,10 +574,39 @@ export default {
359
574
  required: false,
360
575
  default: false
361
576
  },
577
+ //审批人来源
578
+ auditGroup: {
579
+ type: String,
580
+ required: false,
581
+ default: '',
582
+ },
583
+ //默认审批意见
584
+ defaultAuditOpinion: {
585
+ type: String,
586
+ required: false,
587
+ default: '',
588
+ },
362
589
  processTraceRouterName: {
363
590
  type: String,
364
591
  required: true,
365
- }
592
+ },
593
+ activeFirstTab: {
594
+ type: Boolean,
595
+ default: false,
596
+ },
597
+ offsetTop: {
598
+ type: Number,
599
+ default: 0
600
+ },
601
+ foldingApprovalComments: {
602
+ type: Boolean,
603
+ default: false
604
+ },
605
+ //默认是否驳回给转办人
606
+ defaultRejectToTakeOver: {
607
+ type: Boolean,
608
+ default: false
609
+ },
366
610
  },
367
611
  data() {
368
612
  return {
@@ -378,20 +622,30 @@ export default {
378
622
  handleButtons: this.$route.query.handleButtons,
379
623
 
380
624
  approvalForm: {
381
- auditOpinion: '',
625
+ auditOpinion: this.defaultAuditOpinion,
382
626
  nextNode: '',
383
627
  handlingPersonnel: '',
384
628
  nodeConfigMaps: '',
385
629
  },
630
+ approvalError: false,
631
+ orgTreeType: '00',
386
632
 
387
633
  processHistory: [],
388
634
  tableData: [],
389
635
 
390
636
  bpmnViewer: null,
391
637
 
392
- activeNames: [0],
638
+ activeNames: [],
393
639
  popupShow: false,
394
-
640
+ appointTask: false,
641
+ revokeDelegateTask: false,
642
+ rejectedTask: false,
643
+ coSignVotingTask: false,
644
+ curNodeType: NodeType.UserTask,
645
+
646
+ nextAssigneeSelectionType: '',
647
+ useTransferUser: false,
648
+ useTransferOrgan: false,
395
649
  nextNodePopupShow: false,
396
650
  nextNodeHandleIndex: 0,
397
651
  nextNodeProcessedData: [], // 用于存储处理下一节点的数据
@@ -416,6 +670,8 @@ export default {
416
670
  ],
417
671
  handleTypeValue: '',
418
672
  showPicker: {},
673
+ rejectName: '驳回',
674
+ passName: '通过',
419
675
 
420
676
  nodeListDialogShow: false,
421
677
  nodeList: [],
@@ -428,16 +684,49 @@ export default {
428
684
  files: [],
429
685
  //文件上传列表
430
686
  fileList: [],
687
+ // 可以减签的人员
688
+ unapprovedAssigneeList: [],
689
+ // 减签人员
690
+ reductionUsers: [],
431
691
 
432
692
  permissionsScope: {},
433
693
  selectOrganizePopupShow: false,
694
+ selectCandUserPopupShow: false,
695
+ selectRejectUserPopupShow: false,
434
696
  selectUserPopupShow: false,
435
697
  selectRolePopupShow: false,
698
+ selectReductionUserPopupShow: false,
436
699
  selectPositionPopupShow: false,
700
+ selectUserPopupParams: {
701
+ title: '选择用户',
702
+ class: '',
703
+ multiSelect: true,
704
+ userList: [],
705
+ orgList: [],
706
+ organTreeType: '',
707
+ action: (checkResult) => { console.log('未定义回调方法') },
708
+ },
709
+ showRejectToSelected: false,
710
+ showProcessControl: false,
711
+ showRejectOption: false,
712
+ rejectAttributesBoxShow: false,
713
+ rejectAttribute: 'inSequence',
714
+ rejectToTakeOver: this.defaultRejectToTakeOver ? 'actual' : 'original',
715
+ rejectAttributeList: [],
716
+ taskAuditUserList: [],
717
+ rejectUserList: [],
718
+ targetTaskNodeProcessControl: '',
719
+ rejectUsers: {
720
+ name: '全部办理人',
721
+ id: 'all'
722
+ },
437
723
  };
438
724
  },
439
725
  methods: {
726
+ getAuditStatus,
440
727
  initData() {
728
+ this.getHandleButtons();
729
+ if (!this.isDetail) this.getTaskStatus();
441
730
  this.getProcessHistory();
442
731
  this.getNextNodes();
443
732
  this.getAttachmentList();
@@ -445,10 +734,79 @@ export default {
445
734
  //设置提示持续时间默认为500ms
446
735
  Toast.setDefaultOptions({duration: 500});
447
736
  },
737
+
738
+ // 计算有多少个可见的handleButtons
739
+ getVisibleButtonsCount() {
740
+ return ['auditTo70', 'auditTo40', 'auditTo90', 'auditTo80', 'auditTo82', 'auditTo50']
741
+ .filter(btn => this.handleButtons && this.handleButtons.includes(btn))
742
+ .length;
743
+ },
744
+
745
+ // 获取唯一按钮的文本
746
+ getSingleButtonText() {
747
+ const buttonIds = ['auditTo70', 'auditTo40', 'auditTo90', 'auditTo80', 'auditTo82', 'auditTo50'];
748
+ const singleButtonId = buttonIds.find(btn => this.handleButtons && this.handleButtons.includes(btn));
749
+ if (singleButtonId) {
750
+ const statusId = parseInt(singleButtonId.replace('auditTo', ''));
751
+ return this.getAuditButtonStatus(statusId);
752
+ }
753
+ return '';
754
+ },
755
+
756
+ // 处理唯一按钮的点击
757
+ handleSingleButton() {
758
+ const buttonIds = ['auditTo70', 'auditTo40', 'auditTo90', 'auditTo80', 'auditTo82', 'auditTo50'];
759
+ const singleButtonId = buttonIds.find(btn => this.handleButtons && this.handleButtons.includes(btn));
760
+ if (singleButtonId) {
761
+ const statusId = singleButtonId.replace('auditTo', '');
762
+ this.audit(statusId);
763
+ }
764
+ },
765
+
766
+ getTaskStatus() {
767
+ const self = this
768
+ let params = {
769
+ procId: self.procId,
770
+ applyId: self.applyId,
771
+ taskNode: self.taskNode,
772
+ taskId: self.taskId,
773
+ auditGroup: self.auditGroup,
774
+ }
775
+ getProcessTodoList(0, 10, params).then(resp => {
776
+ if (resp.data.code === '200') {
777
+ const todoData = resp.data.data.rows[0]
778
+ // 委派任务状态
779
+ self.revokeDelegateTask = todoData?.delegateStatus ? todoData.delegateStatus === '10' : false
780
+ self.appointTask = todoData?.delegateStatus ? todoData.delegateStatus === '20' : false
781
+ // 判断节点是否是被驳回的节点
782
+ const taskExtensionAttributes = todoData?.taskExtensionAttributes ? JSON.parse(todoData.taskExtensionAttributes) : ''
783
+ if (taskExtensionAttributes && taskExtensionAttributes.rejectAttributes) {
784
+ const rejectAttributes = taskExtensionAttributes.rejectAttributes
785
+ if (rejectAttributes && rejectAttributes.type && rejectAttributes.type != 'inSequence') self.rejectedTask = true
786
+ }
787
+ }
788
+ })
789
+ },
790
+ getHandleButtons() {
791
+ getNodeData(this.procId, this.taskNode).then(resp => {
792
+ if (resp.data.code === '200') {
793
+ const data = resp.data.data[0]
794
+ this.handleButtons = this.handleButtons ? this.handleButtons : data.handleButtons ? data.handleButtons : ["processTrace", "auditHistory", "auditOpinion", "attachmentFile", "auditTo30", "auditTo70", "auditTo40", "auditTo90", "auditTo80", "auditTo82", "auditTo50"];
795
+ this.coSignVotingTask = this.handleButtons && this.handleButtons.includes('coSignVoting')
796
+ this.nextAssigneeSelectionType = data.nextAssigneeSelectionType;
797
+ this.passName = data.handleName === '审批' ? '通过' : data.handleName;
798
+ this.rejectName = data.rejectName;
799
+ if (data.hasOwnProperty('isSequential')) {
800
+ this.curNodeType = data.isSequential ? NodeType.SequentialMultiNode : NodeType.MultiNode;
801
+ }
802
+ }
803
+ })
804
+ },
448
805
  getProcessHistory() {
449
806
  getProcessHis(this.applyId, this.instanceId, this.procId, this.taskId).then(resp => {
450
807
  if (resp.data.code === '200') {
451
808
  this.processHistory = resp.data.data
809
+ this.activeNames = this.processHistory.map((_, index) => index);
452
810
  }
453
811
  })
454
812
  },
@@ -459,7 +817,7 @@ export default {
459
817
  this.nextNodeProcessedData = this.processData(result.data);
460
818
  this.approvalForm.nextNode = result.data.map(node => node.name).join(',');
461
819
  this.approvalForm.handlingPersonnel = result.data
462
- .map(node => node.assignee?.name || '未指定')
820
+ .map(node => node.assignee?.name || '候选人')
463
821
  .join(',');
464
822
  }
465
823
  })
@@ -537,7 +895,7 @@ export default {
537
895
  },
538
896
  processData(data) {
539
897
  return data.map(node => {
540
- node.personType = node.assignee ? "assignee" : "candidate";
898
+ node.personType = this.nextAssigneeSelectionType ? this.nextAssigneeSelectionType : node.assignee ? "assignee" : "candidate";
541
899
  if (node.assignee) {
542
900
  node.candidateGroups = {
543
901
  organs: [],
@@ -574,7 +932,10 @@ export default {
574
932
  return node;
575
933
  });
576
934
  },
577
- toggleDetails(index) {
935
+ async toggleDetails(index) {
936
+ if (!this.detailsVisible[index]){
937
+ await this.fetchTransferRange('appointHandlerWithCandGroups')
938
+ }
578
939
  this.$set(this.detailsVisible, index, !this.detailsVisible[index]);
579
940
  },
580
941
  onHandleTypeConfirm(selectedLabel, selectedIndex, index) {
@@ -595,6 +956,13 @@ export default {
595
956
  // 拼接成目标格式
596
957
  return `O:${organIds},U:${userIds},P:${positionIds},R:${roleIds},RP:,T:`;
597
958
  },
959
+ handleReductionUserSelect(handle) {
960
+ if (handle === 'select') {
961
+ this.updateMultitaskInstance('83', this.reductionUsers.map(item => item.userId).join(','))
962
+ } else {
963
+ this.checkResult = [];
964
+ }
965
+ },
598
966
  handleSelect(handle) {
599
967
  if (handle === 'select') {
600
968
  //处理审批参数
@@ -619,7 +987,7 @@ export default {
619
987
  }, {});
620
988
  this.approvalForm.nodeConfigMaps = JSON.stringify(params)
621
989
  this.approvalForm.handlingPersonnel = this.nextNodeProcessedData
622
- .map(node => node.assignee?.name || '未指定')
990
+ .map(node => node.personType === "assignee" ? node.assignee?.name : '候选人')
623
991
  .join(',');
624
992
  this.nextNodePopupShow = false;
625
993
  } else {
@@ -630,11 +998,77 @@ export default {
630
998
  },
631
999
  handleSelectOrganizePopupShow(index) {
632
1000
  this.nextNodeHandleIndex = index;
1001
+ if (this.useTransferOrgan){
1002
+ this.permissionsScope.O = this.selectUserPopupParams.orgList.map(item => item.orgCode)
1003
+ }
633
1004
  this.selectOrganizePopupShow=true;
634
1005
  },
635
- handleSelectUserPopupShow(index) {
1006
+ handleCandUserSelect(index){
1007
+ this.nextNodeHandleIndex = index;
1008
+ this.selectUserPopupParams.userList = []
1009
+ this.selectUserPopupParams.orgList = []
1010
+ this.selectCandUserPopupShow = true
1011
+ },
1012
+ async handleNextNodePopupShow(){
1013
+ // 指定下一环节选人为单/多选并且给出具体选人范围则直接弹出选人框
1014
+ if (this.nextAssigneeSelectionType){
1015
+ const actionType = this.nextAssigneeSelectionType === 'assignee' ? 'appointHandler' : 'appointHandlerWithCandGroups'
1016
+ const action = this.nextAssigneeSelectionType === 'assignee' ? (checkResult) => this.handleAssigneeSelectResult(checkResult) : (checkResult) => this.selectCandUserHandle(checkResult)
1017
+ await this.fetchTransferRange(actionType);
1018
+ if (this.selectUserPopupParams.userList?.length > 0 || this.selectUserPopupParams.orgList?.length > 0){
1019
+ this.handleSelectUserPopupShow('选择用户', this.nextAssigneeSelectionType === 'candidate', action, actionType)
1020
+ return
1021
+ }
1022
+ }
1023
+ this.nextNodePopupShow = true;
1024
+ },
1025
+ handleNextNodeAssigneeSelect(index, actionType){
636
1026
  this.nextNodeHandleIndex = index;
637
- this.selectUserPopupShow=true;
1027
+ this.handleSelectUserPopupShow('选择用户', true, (checkResult) => this.handleAssigneeSelectResult(checkResult), actionType)
1028
+ },
1029
+ handleNextNodeUserSelect(index, actionType){
1030
+ this.nextNodeHandleIndex = index;
1031
+ this.handleSelectUserPopupShow('选择用户', true, (checkResult) => this.selectCandUserHandle(checkResult), actionType)
1032
+ },
1033
+ async handleSelectUserPopupShow(title, multiSelect, action, actionType) {
1034
+ this.selectUserPopupParams.title = title
1035
+ this.selectUserPopupParams.multiSelect = multiSelect
1036
+ this.selectUserPopupParams.action = action
1037
+ const success = await this.fetchTransferRange(actionType);
1038
+ if (success) {
1039
+ this.selectUserPopupShow = true;
1040
+ }
1041
+ },
1042
+ async fetchTransferRange(actionType) {
1043
+ try {
1044
+ const params = {
1045
+ procType: this.procType,
1046
+ procId: this.procId,
1047
+ taskNode: this.taskNode,
1048
+ applyId: this.applyId,
1049
+ taskId: this.taskId,
1050
+ instanceId: this.instanceId,
1051
+ actionType: actionType,
1052
+ };
1053
+ const res = await getTransferRange(params);
1054
+ const result = res.data;
1055
+ if (result.code == 200) {
1056
+ this.selectUserPopupParams.organTreeType = result.data.organTreeType;
1057
+ this.selectUserPopupParams.userList = result.data.userList;
1058
+ this.selectUserPopupParams.orgList = result.data.orgList;
1059
+ if (actionType === 'appointHandlerWithCandGroups'){
1060
+ this.useTransferUser = result.data.userList?.length > 0
1061
+ this.useTransferOrgan = result.data.orgList?.length > 0
1062
+ }
1063
+ return true;
1064
+ } else {
1065
+ console.warn('获取选择人员范围失败,返回码:', result.code);
1066
+ return false;
1067
+ }
1068
+ } catch (err) {
1069
+ console.error('获取选择人员范围异常', err);
1070
+ return false;
1071
+ }
638
1072
  },
639
1073
  handleSelectRolePopupShow(index) {
640
1074
  this.nextNodeHandleIndex = index;
@@ -680,32 +1114,23 @@ export default {
680
1114
  if (ext === 'pdf') return 'P';
681
1115
  return ext.charAt(0).toUpperCase();
682
1116
  },
683
- getAuditStatus(auditResult) {
684
- const statusMap = {
685
- '30': {text: '通过', icon: "tongguo", color: '#0d88ff', type: 'success'},
686
- '40': {text: '驳回上一节点', icon: "bohui", color: '#ed4014', type: 'danger'},
687
- '50': {text: '驳回到原点', icon: "bohui", color: '#ed4014', type: 'danger'},
688
- '51': {text: '流程作废', icon: "liuchengzuofei", color: '#ed4014', type: 'danger'},
689
- '60': {text: '撤回', icon: "chehui", color: '#ed4014', type: 'warning'},
690
- '80': {text: '跳转指定节点', icon: "tiaozhuan", color: '#0d88ff', type: 'primary'},
691
- '90': {text: '驳回指定节点', icon: "bohui", color: '#ed4014', type: 'primary'},
692
- };
693
- return {
694
- text: (statusMap[auditResult] && statusMap[auditResult].text) || '待审批',
695
- icon: (statusMap[auditResult] && statusMap[auditResult].icon) || 'daishenpi',
696
- color: (statusMap[auditResult] && statusMap[auditResult].color) || '#ff9900',
697
- type: (statusMap[auditResult] && statusMap[auditResult].type) || 'warning',
698
- };
699
- },
1117
+
700
1118
  getAuditButtonStatus(auditResult) {
701
1119
  let statusMap = {
702
- '30': '通过',
703
- '40': '驳回上一节点',
1120
+ '30': `${this.passName}`,
1121
+ '31': '同意',
1122
+ '32': '不同意',
1123
+ '40': `${this.rejectName}上一节点`,
704
1124
  '50': '直接结束流程',
705
- '70': '驳回到原点',
1125
+ '61': '交回',
1126
+ '62': '撤回委派',
1127
+ '70': `${this.rejectName}到原点`,
706
1128
  '80': '跳转指定节点',
1129
+ '81': '加签',
707
1130
  '82': '指定他人处理',
708
- '90': '驳回指定节点',
1131
+ '83': '减签',
1132
+ '84': '委派',
1133
+ '90': `${this.rejectName}指定节点`,
709
1134
  };
710
1135
  return statusMap[auditResult];
711
1136
  },
@@ -723,18 +1148,55 @@ export default {
723
1148
  procId: this.procId,
724
1149
  instanceId: this.instanceId,
725
1150
  taskNode: this.taskNode,
1151
+ taskId: this.taskId,
1152
+ predictButtonEnabled: this.handleButtons && this.handleButtons.includes('predictiveRoute'),
726
1153
  }
727
1154
  })
728
1155
  },
1156
+ // 加减签
1157
+ showUpdateMultitaskInstanceModal(updateType){
1158
+ this.selectHandlePopupShow = true
1159
+ const self = this
1160
+ if (updateType === '81') {
1161
+ self.handleSelectUserPopupShow('加签', true, (checkResult) => this.updateMultitaskInstance('81', checkResult.map(item => item.userId).join(',')), 'addMultitaskInstance')
1162
+ } else if (updateType === '83') {
1163
+ getUnapprovedListOfMultiNode(self.taskId).then(res => {
1164
+ if (res.data.code === '200') {
1165
+ self.unapprovedAssigneeList = res.data.data.map(item => ({...item, userName: item.auditName, userId: item.auditId, organName: item.auditUserOrgan ? item.auditUserOrgan.split(':')[1] : '未知部门'}))
1166
+ self.selectReductionUserPopupShow = true
1167
+ } else {
1168
+ Toast.fail(resp.data.message)
1169
+ }
1170
+ this.selectUserPopupShow = false;
1171
+ }).catch(err => {
1172
+ console.log(err)
1173
+ this.selectUserPopupShow = false;
1174
+ })
1175
+ }
1176
+ },
1177
+ updateMultitaskInstance(updateType, assignees) {
1178
+ const self = this
1179
+ updateMultitaskInstance(self.taskId, self.applyId, assignees, updateType).then(res => {
1180
+ res.data.code == 200 ? Toast.success(res.data.message) :Toast.fail(res.data.message)
1181
+ self.selectUserPopupShow = false;
1182
+ self.selectReductionUserPopupShow = false
1183
+ }).catch(err => {
1184
+ console.log(err)
1185
+ self.selectUserPopupShow = false;
1186
+ self.selectReductionUserPopupShow = false
1187
+ })
1188
+ },
729
1189
  audit(auditResult) {
730
1190
  console.log('点击了审批:', auditResult);
731
1191
 
732
1192
  if (this.approvalForm.auditOpinion === '' || this.approvalForm.auditOpinion == null) {
733
- if (!this.handleButtons || this.handleButtons.includes('auditOpinion')) {
1193
+ if (!this.handleButtons || (this.handleButtons.includes('auditOpinion') && auditResult != '30' && auditResult != '31')) {
1194
+ this.updateActiveTab()
1195
+ this.approvalError = true
734
1196
  Toast({message: '请输入审批意见', duration: '500'});
735
1197
  return
736
1198
  } else {
737
- this.auditParams.auditOpinion = this.getAuditButtonStatus(self.auditResult);
1199
+ this.approvalForm.auditOpinion = this.getAuditButtonStatus(auditResult);
738
1200
  }
739
1201
  }
740
1202
 
@@ -746,6 +1208,9 @@ export default {
746
1208
  this.popupShow = false;
747
1209
 
748
1210
  },
1211
+ handleInputFocus() {
1212
+ this.approvalError = false;
1213
+ },
749
1214
  submit() {
750
1215
  let self = this;
751
1216
  self.auditParams = {
@@ -760,21 +1225,35 @@ export default {
760
1225
  //字段不清楚 暂不传递
761
1226
  params: null,
762
1227
  }
763
- self.businessFormSave ? self.businessFormSave(self.handleSaveResult) : self.handleSaveResult(true)
1228
+ let auditResult = {
1229
+ code: self.auditResult,
1230
+ name: self.getAuditButtonStatus(self.auditResult)
1231
+ }
1232
+ self.businessFormSave ? self.businessFormSave(self.handleSaveResult, auditResult) : self.handleSaveResult(true)
764
1233
  },
765
- handleSaveResult(saveResult) {
1234
+ handleSaveResult(saveResult, businessParams) {
766
1235
  if (saveResult) {
767
- this.executeButtonAction();
1236
+ this.executeButtonAction(businessParams);
768
1237
  } else {
769
1238
  console.error('保存失败');
770
1239
  }
771
1240
  },
772
1241
 
773
- executeButtonAction() {
1242
+ executeButtonAction(businessParams) {
774
1243
  const self = this;
1244
+ if (businessParams) {
1245
+ self.auditParams.params = JSON.stringify(businessParams)
1246
+ }
1247
+ let auditResult = {
1248
+ code: self.auditResult,
1249
+ name: self.getAuditButtonStatus(self.auditResult)
1250
+ }
775
1251
  if (self.auditResult === '82') {
776
1252
  // 处理转办逻辑
777
- this.selectHandlePopupShow = true;
1253
+ this.handleSelectUserPopupShow('指定办理人', false, (checkResult) => this.handleSelectResult(checkResult), 'auditTo82')
1254
+ } else if (self.auditResult === '84') {
1255
+ // 处理委派逻辑
1256
+ this.handleSelectUserPopupShow('委派', false, (checkResult) => this.handleDelegateTaskSelectResult(checkResult), 'delegateTaskTask')
778
1257
  } else if (self.auditResult === '80') {
779
1258
  // 处理调转到指定节点逻辑
780
1259
  self.getNodesBehind()
@@ -788,6 +1267,7 @@ export default {
788
1267
  // 处理驳回上一节点逻辑
789
1268
  getPreNode(self.auditParams).then(function (resp) {
790
1269
  let result = resp.data
1270
+ // 上一节点为原点
791
1271
  if (result.code === '30010') {
792
1272
  Dialog.confirm({
793
1273
  title: "提示",
@@ -798,8 +1278,32 @@ export default {
798
1278
  self.auditRequest(self.auditParams)
799
1279
  }).catch(() => {
800
1280
  })
1281
+ } else if (result.code == '30013' || result.code == '30014') {
1282
+ //前序节点为子流程或当前节点为子流程第一个节点不可驳回上一级节点
1283
+ const message = self.rejectName ? self.replaceName(result.message, '驳回', self.rejectName) : result.message
1284
+ Toast.fail(message)
1285
+ //避免未提示消息直接回调
1286
+ setTimeout(() => {
1287
+ self.executionCompleted(false, null, null, auditResult, self.taskId)
1288
+ }, 1000)
1289
+ } else if (result.code == '10012') {
1290
+ // 数据同步
1291
+ self.disable = false
1292
+ Toast.fail(result.message)
1293
+ setTimeout(() => {
1294
+ self.executionCompleted(true, null, null, auditResult, self.taskId)
1295
+ }, 1000)
801
1296
  } else {
802
- self.auditRequest(self.auditParams)
1297
+ // 前序节点只有一个会签节点才可选择驳回人员
1298
+ self.showRejectToSelected = result.data.length === 1 && result.data[0].handleButtons?.includes('rejectToSelected')
1299
+ self.nodeList = Array.isArray(result.data) ? result.data : []
1300
+ self.radio = 0
1301
+ self.showRejectOption = Array.isArray(result.data) && result.data.some(item => item.taskTakedOver && (item.taskTakedOver === true || item.taskTakedOver === 'true'))
1302
+ if ((self.showRejectToSelected || self.showRejectOption) && !self.handleButtons?.includes('rejectProcessControl')) {
1303
+ self.rejectAttributesBoxShow = true
1304
+ } else {
1305
+ self.handleButtons && self.handleButtons.includes('rejectProcessControl') ? self.getProcessAttributes() : self.auditRequest(self.auditParams)
1306
+ }
803
1307
  }
804
1308
  })
805
1309
  }).catch(() => {
@@ -823,12 +1327,95 @@ export default {
823
1327
  message: message,
824
1328
  })
825
1329
  },
1330
+ getProcessAttributes(targetTaskNodeProcessControl){
1331
+ const self = this
1332
+ self.rejectAttributeList = []
1333
+ if (!targetTaskNodeProcessControl || targetTaskNodeProcessControl != 'jump') {
1334
+ self.rejectAttributeList.push({
1335
+ _checked: true,
1336
+ type: 'inSequence',
1337
+ name: '按顺序流转'
1338
+ })
1339
+ }
1340
+ getProcessAttributes(self.procId, self.taskNode).then(resp => {
1341
+ if (resp.data.code === '200') {
1342
+ if ((self.auditParams.auditResult == '70' && !resp.data.data.jumpFirstNode) || (self.auditParams.auditResult == '40' && (resp.data.data.multiNode || resp.data.data.preNodeGateway))) {
1343
+ if ((self.showRejectToSelected && self.nodeList.length > 0) || self.showRejectOption) {
1344
+ self.rejectAttributesBoxShow = true
1345
+ return
1346
+ }
1347
+ self.auditRequest(self.auditParams)
1348
+ return
1349
+ } else {
1350
+ let param = {
1351
+ procId: self.procId,
1352
+ applyId: self.applyId,
1353
+ taskNode: self.taskNode,
1354
+ taskId: self.curTaskId,
1355
+ }
1356
+ listAll(self.procId, self.applyId, self.taskNode, self.taskId).then(todoResp => {
1357
+ if (todoResp.data.code === '200') {
1358
+ const rows = todoResp.data.data.rows
1359
+ self.taskAuditUserList = rows.map(item => item.auditName)
1360
+ const isMultiNode = resp.data.data.multiNode
1361
+ if (isMultiNode) {
1362
+ const parentExecutionId = String(rows[0].parentExecutionId).trim();
1363
+ const nodeDone = self.processHistory.filter(item => item[0].taskNode === self.taskNode && String(item[0].parentExecutionId).trim() === parentExecutionId)
1364
+ if (nodeDone) {
1365
+ const doneAuditName = nodeDone[0].flatMap(item => item.auditName);
1366
+ self.taskAuditUserList = new Set([...doneAuditName, ...self.taskAuditUserList])
1367
+ }
1368
+ }
1369
+ if (self.auditParams.auditResult == '40' || self.taskAuditUserList.length === 1) {
1370
+ self.rejectAttributeList.push(
1371
+ {
1372
+ type: 'returnToCurrentAuditUser',
1373
+ name: '返回我'
1374
+ }
1375
+ )
1376
+ } else {
1377
+ self.rejectAttributeList.push(
1378
+ {
1379
+ type: 'returnToCurrentNode',
1380
+ name: '返回本节点'
1381
+ }
1382
+ )
1383
+ if (!isMultiNode) {
1384
+ self.rejectAttributeList.push(
1385
+ {
1386
+ type: 'returnToCurrentAuditUser',
1387
+ name: '返回我'
1388
+ }
1389
+ )
1390
+ }
1391
+ }
1392
+ self.showProcessControl = self.rejectAttributeList.length > 0 && self.handleButtons && self.handleButtons.includes('rejectProcessControl')
1393
+ if (self.auditParams.auditResult == '70' || self.auditParams.auditResult == '40') {
1394
+ self.rejectAttributesBoxShow = self.rejectAttributeList.length > 0 && self.handleButtons && self.handleButtons.includes('rejectProcessControl')
1395
+ }
1396
+ }
1397
+ }).catch((err) => {
1398
+ console.log(err)
1399
+ })
1400
+ }
1401
+ } else {
1402
+ self.$Message.error(resp.data.message)
1403
+ }
1404
+ }).catch(err => {
1405
+ console.log(err);
1406
+ Toast.fail('获取流程配置失败')
1407
+ })
1408
+ },
826
1409
  auditRequest(auditParams) {
827
1410
  let self = this
828
1411
  let auditResult = {
829
1412
  code: self.auditResult,
830
1413
  name: self.getAuditStatus(self.auditResult).text
831
1414
  }
1415
+ if (self.rejectUsers.id !== 'all'){
1416
+ auditParams.rejectUsers = self.rejectUsers.id
1417
+ }
1418
+ auditParams.rejectToTakeOver = self.rejectToTakeOver && self.rejectToTakeOver != 'original'
832
1419
  self.showToastLoading('处理审批中');
833
1420
  //确认执行审批逻辑
834
1421
  audit(auditParams).then((resp) => { // 使用箭头函数
@@ -843,7 +1430,7 @@ export default {
843
1430
  this.executionCompleted(true, result.data.processInstanceId, taskIds, auditResult, self.taskId);
844
1431
  }
845
1432
  } else {
846
- this.executionCompleted(true, null, null);
1433
+ this.executionCompleted(true, null, null, auditResult, self.taskId);
847
1434
  }
848
1435
  Toast.success(result.message);
849
1436
  } else if (result.code === '20002') {
@@ -854,6 +1441,7 @@ export default {
854
1441
  this.executionCompleted(false, null, null, auditResult, self.taskId)
855
1442
  Toast.fail(result.message);
856
1443
  }
1444
+ this.selectUserPopupShow = false;
857
1445
  })
858
1446
  },
859
1447
  getNodesBehind() {
@@ -889,12 +1477,85 @@ export default {
889
1477
  this.nodeListDialogShow = false
890
1478
  this.auditResult = '';
891
1479
  this.radio = '';
1480
+ this.rejectAttribute = 'inSequence';
1481
+ this.targetTaskNodeProcessControl = ''
1482
+ },
1483
+ onBeforeClose(action, done) {
1484
+ const self = this;
1485
+ if (action === 'confirm') {
1486
+ if (!self.nodeList[self.radio]) {
1487
+ Toast.fail('请选择驳回节点!');
1488
+ done(false);
1489
+ return;
1490
+ }
1491
+ done(true);
1492
+ } else {
1493
+ done(true);
1494
+ }
1495
+ },
1496
+ selectNode(currentRow, index) {
1497
+ this.radio = index
1498
+ this.targetTaskNodeProcessControl = currentRow.processControl
1499
+ this.rejectUsers = { name: '全部办理人', id: 'all' }
1500
+ this.showRejectToSelected = currentRow.rejectToSelected
1501
+ this.rejectToTakeOver = this.defaultRejectToTakeOver ? 'actual' : 'original'
1502
+ this.showRejectOption = currentRow.taskTakedOver && (currentRow.taskTakedOver === true || currentRow.taskTakedOver === 'true')
892
1503
  },
893
1504
  processJumpSpecifiedNode() {
894
1505
  const self = this;
1506
+ if (!self.nodeList[self.radio]) return;
895
1507
  self.auditParams.targetTaskNode = self.nodeList[self.radio].taskNode;
1508
+ self.auditParams.rejectAttribute = self.rejectAttribute ? self.rejectAttribute : 'inSequence'
1509
+ self.auditRequest(self.auditParams)
1510
+ },
1511
+ processRejectNode() {
1512
+ const self = this;
1513
+ self.auditParams.rejectAttribute = self.rejectAttribute ? self.rejectAttribute : 'inSequence'
896
1514
  self.auditRequest(self.auditParams)
897
1515
  },
1516
+ handleSelectRejectUser() {
1517
+ let self = this
1518
+ getDoneDetail(self.nodeList[self.radio].taskNode, self.applyId).then(function (resp) {
1519
+ if (resp.data.code === '200') {
1520
+ const data = resp.data.data.rows
1521
+ const latestAudit = data.reduce((max, current) => {
1522
+ return current.auditDate > max.auditDate ? current : max;
1523
+ });
1524
+ const seenNames = new Set();
1525
+ self.rejectUserList = data.filter(item => {
1526
+ if (item.parentExecutionId && latestAudit.parentExecutionId && item.parentExecutionId !== latestAudit.parentExecutionId) {
1527
+ return false;
1528
+ }
1529
+ if (seenNames.has(item.auditName)) {
1530
+ return false;
1531
+ }
1532
+ seenNames.add(item.auditName);
1533
+ return true;
1534
+ }).map(item => {
1535
+ return {
1536
+ ...item,
1537
+ userName: item.auditName,
1538
+ userId: item.auditId,
1539
+ organName: item.auditUserOrgan?.split(':')[1]
1540
+ }
1541
+ });
1542
+ self.selectRejectUserPopupShow = true
1543
+ } else {
1544
+ Toast.fail(resp.data.message)
1545
+ }
1546
+ })
1547
+ },
1548
+ rejectAttributesDialogShowClose(){
1549
+ //重置
1550
+ this.showRejectToSelected = false
1551
+ this.showRejectOption = false
1552
+ this.rejectToTakeOver = this.defaultRejectToTakeOver ? 'actual' : 'original'
1553
+ this.showProcessControl = false
1554
+ this.rejectAttributesBoxShow = false
1555
+ this.auditResult = '';
1556
+ this.radio = '';
1557
+ this.rejectAttribute = 'inSequence';
1558
+ },
898
1559
 
899
1560
  showAuditDetail(auditComment) {
900
1561
  Dialog.alert({
@@ -926,6 +1587,21 @@ export default {
926
1587
  }
927
1588
  });
928
1589
  },
1590
+ handleDelegateTaskSelectResult(checkResult) {
1591
+ console.log('Selected result:', checkResult);
1592
+ if (!checkResult) {
1593
+ Toast('请选择委派人员')
1594
+ return
1595
+ }
1596
+ this.auditParams.selectedUserId = checkResult[0].userId
1597
+ Dialog.confirm({
1598
+ message: '确定执行:' + this.getAuditButtonStatus(this.auditResult) + ' ?',
1599
+ }).then(() => {
1600
+ this.auditRequest(this.auditParams)
1601
+ }).catch(() => {
1602
+ // on cancel
1603
+ });
1604
+ },
929
1605
  handleSelectResult(checkResult) {
930
1606
  console.log('Selected result:', checkResult);
931
1607
  const self = this;
@@ -938,7 +1614,16 @@ export default {
938
1614
  self.auditParams.selectedUserId = checkResult[0].userId;
939
1615
  self.auditRequest(self.auditParams);
940
1616
  }
941
- self.selectHandlePopupShow = false;
1617
+ self.selectUserPopupShow = false;
1618
+ },
1619
+ handleAssigneeSelectResult(checkResult) {
1620
+ console.log('Selected result:', checkResult);
1621
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].assignee.id = checkResult[0].userId;
1622
+ this.nextNodeProcessedData[this.nextNodeHandleIndex].assignee.name = checkResult[0].userName;
1623
+ if (!this.nextNodePopupShow){
1624
+ this.handleSelect('select')
1625
+ }
1626
+ this.selectUserPopupShow = false;
942
1627
  },
943
1628
  selectOrganizeHandle(handle, checkedNodes) {
944
1629
  if (handle === 'select') {
@@ -950,13 +1635,22 @@ export default {
950
1635
  }
951
1636
  this.selectOrganizePopupShow = false
952
1637
  },
953
- selectUserHandle(checkResult) {
1638
+ selectCandUserHandle(checkResult) {
954
1639
  this.nextNodeProcessedData[this.nextNodeHandleIndex].candidateGroups.users =
955
1640
  checkResult.map(item => ({
956
1641
  name: item.userName,
957
1642
  id: item.userId,
958
1643
  }));
1644
+ if (!this.nextNodePopupShow){
1645
+ this.handleSelect('select')
1646
+ }
959
1647
  this.selectUserPopupShow = false;
1648
+ this.selectCandUserPopupShow = false;
1649
+ },
1650
+ selectRejectUserHandle(checkResult) {
1651
+ this.rejectUsers.name = checkResult.map(item => item.userName).join(',')
1652
+ this.rejectUsers.id = checkResult.map(item => item.userId).join(',')
1653
+ this.selectRejectUserPopupShow = false;
960
1654
  },
961
1655
  selectPositionHandle(handle, result) {
962
1656
  if (handle === 'select') {
@@ -980,12 +1674,33 @@ export default {
980
1674
  },
981
1675
  },
982
1676
  activated() {
983
- this.updateActiveTab();
1677
+ if (!this.activeFirstTab) {
1678
+ this.updateActiveTab();
1679
+ }
984
1680
  },
985
1681
  mounted() {
986
1682
  this.initData()
987
1683
  // 默认选中最后一个tab页
988
- this.updateActiveTab();
1684
+ if (!this.activeFirstTab) {
1685
+ this.updateActiveTab();
1686
+ }
1687
+ },
1688
+ watch: {
1689
+ targetTaskNodeProcessControl(val) {
1690
+ const self = this
1691
+ if (self.handleButtons && self.handleButtons.includes('rejectProcessControl')) {
1692
+ if (val == 'inSequence') {
1693
+ self.showProcessControl = false
1694
+ return
1695
+ }
1696
+ self.getProcessAttributes(val)
1697
+ }
1698
+ },
1699
+ selectReductionUserPopupShow(newVal) {
1700
+ if (newVal) {
1701
+ this.reductionUsers = [];
1702
+ }
1703
+ }
989
1704
  }
990
1705
 
991
1706
  };
@@ -994,6 +1709,10 @@ export default {
994
1709
  <style scoped>
995
1710
  @import 'styles/global.css';
996
1711
 
1712
+ ::v-deep .van-tabs__content {
1713
+ background: #f7f8fa;
1714
+ }
1715
+
997
1716
  ::v-deep .van-collapse-item__content {
998
1717
  padding: 0 0;
999
1718
  }
@@ -1010,6 +1729,7 @@ export default {
1010
1729
  ::v-deep .van-tab--active .van-tab__text {
1011
1730
  color: #3478f6; /* 设置激活状态下的文字颜色为蓝色 */
1012
1731
  font-size: 16px; /* 增大激活状态下的字体大小 */
1732
+ font-weight: bold;
1013
1733
  }
1014
1734
 
1015
1735
  ::v-deep .van-tabs__line {
@@ -1052,42 +1772,21 @@ export default {
1052
1772
  background-color: #1989fa;
1053
1773
  }
1054
1774
 
1055
- .title-info {
1056
- font-weight: bold;
1057
- font-size: 14px;
1058
- padding: 20px 10px;
1775
+ .disagree {
1776
+ color: #fff;
1777
+ background: linear-gradient(90deg, #ff4d4f, #f5222d);
1778
+ border-radius: 9px;
1779
+ margin: 0 20px;
1059
1780
  }
1060
1781
 
1061
- .completed-icon {
1062
- width: 45px; /* 自定义宽度 */
1063
- height: 45px; /* 自定义高度 */
1064
- background-clip: padding-box; /* 使背景不填充边框 */
1065
-
1066
- border-radius: 50%;
1067
- margin-right: 15px;
1068
-
1069
- display: flex;
1070
- align-items: center;
1071
- justify-content: center;
1072
-
1073
- background: linear-gradient(90deg, #0096FF, #1677FF);
1074
- border: 5px solid rgb(229, 244, 255); /* 半透明边框,颜色和背景色相同 */
1782
+ .disagree:active {
1783
+ background-color: #ff7875;
1075
1784
  }
1076
1785
 
1077
- .pending-icon {
1078
- width: 45px; /* 自定义宽度 */
1079
- height: 45px; /* 自定义高度 */
1080
- background-clip: padding-box; /* 使背景不填充边框 */
1081
-
1082
- border-radius: 50%;
1083
- margin-right: 15px;
1084
-
1085
- display: flex;
1086
- align-items: center;
1087
- justify-content: center;
1088
-
1089
- background: linear-gradient(90deg, #FF7E00, #FFA200);
1090
- border: 5px solid rgb(255, 245, 229); /* 半透明边框,颜色和背景色相同 */
1786
+ .title-info {
1787
+ font-weight: bold;
1788
+ font-size: 14px;
1789
+ padding: 20px 10px;
1091
1790
  }
1092
1791
 
1093
1792
  .info-icon {
@@ -1156,10 +1855,11 @@ export default {
1156
1855
  font-size: 15px;
1157
1856
  }
1158
1857
 
1159
- .table-row {
1858
+ .table-row, .reject-attribute {
1160
1859
  display: flex;
1161
1860
  border-bottom: 1px solid #ebedf0;
1162
1861
  font-size: 14px;
1862
+ padding-left: 5px;
1163
1863
  }
1164
1864
 
1165
1865
  .table-header > div,
@@ -1173,6 +1873,14 @@ export default {
1173
1873
  border-bottom: none;
1174
1874
  }
1175
1875
 
1876
+ .reject-attribute > div {
1877
+ padding: 10px 10px 10px 5px;
1878
+ }
1879
+ .reject-attribute .van-radio {
1880
+ padding: 5px;
1881
+ justify-content: flex-start !important;
1882
+ }
1883
+
1176
1884
  .van-radio {
1177
1885
  justify-content: center;
1178
1886
  }