提交 | 用户 | 时间
9259c2 1 <!-- UserTask 自定义配置:
H 2      1. 审批人与提交人为同一人时
3      2. 审批人拒绝时
4      3. 审批人为空时
5      4. 操作按钮
6      5. 字段权限
7      6. 审批类型
8 -->
9 <template>
10   <div>
11     <el-divider content-position="left">审批类型</el-divider>
12     <el-form-item prop="approveType">
13       <el-radio-group v-model="approveType.value">
14         <el-radio
15           v-for="(item, index) in APPROVE_TYPE"
16           :key="index"
17           :value="item.value"
18           :label="item.value"
19         >
20           {{ item.label }}
21         </el-radio>
22       </el-radio-group>
23     </el-form-item>
24
25     <el-divider content-position="left">审批人拒绝时</el-divider>
26     <el-form-item prop="rejectHandlerType">
27       <el-radio-group
28         v-model="rejectHandlerType"
29         :disabled="returnTaskList.length === 0"
30         @change="updateRejectHandlerType"
31       >
32         <div class="flex-col">
33           <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
34             <el-radio :key="item.value" :value="item.value" :label="item.label" />
35           </div>
36         </div>
37       </el-radio-group>
38     </el-form-item>
39     <el-form-item
40       v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
41       label="驳回节点"
42       prop="returnNodeId"
43     >
44       <el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
45         <el-option
46           v-for="item in returnTaskList"
47           :key="item.id"
48           :label="item.name"
49           :value="item.id"
50         />
51       </el-select>
52     </el-form-item>
53
54     <el-divider content-position="left">审批人为空时</el-divider>
55     <el-form-item prop="assignEmptyHandlerType">
56       <el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
57         <div class="flex-col">
58           <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
59             <el-radio :key="item.value" :value="item.value" :label="item.label" />
60           </div>
61         </div>
62       </el-radio-group>
63     </el-form-item>
64     <el-form-item
65       v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
66       label="指定用户"
67       prop="assignEmptyHandlerUserIds"
68       span="24"
69     >
70       <el-select
71         v-model="assignEmptyUserIds"
72         clearable
73         multiple
74         style="width: 100%"
75         @change="updateAssignEmptyUserIds"
76       >
77         <el-option
78           v-for="item in userOptions"
79           :key="item.id"
80           :label="item.nickname"
81           :value="item.id"
82         />
83       </el-select>
84     </el-form-item>
85
86     <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
87     <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
88       <div class="flex-col">
89         <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
90           <el-radio :key="item.value" :value="item.value" :label="item.label" />
91         </div>
92       </div>
93     </el-radio-group>
94
95     <el-divider content-position="left">操作按钮</el-divider>
96     <div class="button-setting-pane">
97       <div class="button-setting-title">
98         <div class="button-title-label">操作按钮</div>
99         <div class="pl-4 button-title-label">显示名称</div>
100         <div class="button-title-label">启用</div>
101       </div>
102       <div class="button-setting-item" v-for="(item, index) in buttonsSettingEl" :key="index">
103         <div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
104         <div class="button-setting-item-label">
105           <input
106             type="text"
107             class="editable-title-input"
108             @blur="btnDisplayNameBlurEvent(index)"
109             v-mountedFocus
110             v-model="item.displayName"
111             :placeholder="item.displayName"
112             v-if="btnDisplayNameEdit[index]"
113           />
114           <el-button v-else text @click="changeBtnDisplayName(index)"
115             >{{ item.displayName }} &nbsp;<Icon icon="ep:edit"
116           /></el-button>
117         </div>
118         <div class="button-setting-item-label">
119           <el-switch v-model="item.enable" />
120         </div>
121       </div>
122     </div>
123
124     <el-divider content-position="left">字段权限</el-divider>
125     <div class="field-setting-pane" v-if="formType === 10">
126       <div class="field-permit-title">
127         <div class="setting-title-label first-title"> 字段名称 </div>
128         <div class="other-titles">
129           <span class="setting-title-label">只读</span>
130           <span class="setting-title-label">可编辑</span>
131           <span class="setting-title-label">隐藏</span>
132         </div>
133       </div>
134       <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
135         <div class="field-setting-item-label"> {{ item.title }} </div>
136         <el-radio-group class="field-setting-item-group" v-model="item.permission">
137           <div class="item-radio-wrap">
138             <el-radio
139               :value="FieldPermissionType.READ"
140               size="large"
141               :label="FieldPermissionType.READ"
142               ><span></span
143             ></el-radio>
144           </div>
145           <div class="item-radio-wrap">
146             <el-radio
147               :value="FieldPermissionType.WRITE"
148               size="large"
149               :label="FieldPermissionType.WRITE"
150               ><span></span
151             ></el-radio>
152           </div>
153           <div class="item-radio-wrap">
154             <el-radio
155               :value="FieldPermissionType.NONE"
156               size="large"
157               :label="FieldPermissionType.NONE"
158               ><span></span
159             ></el-radio>
160           </div>
161         </el-radio-group>
162       </div>
163     </div>
164   </div>
165 </template>
166
167 <script lang="ts" setup>
168 import {
169   ASSIGN_START_USER_HANDLER_TYPES,
170   RejectHandlerType,
171   REJECT_HANDLER_TYPES,
172   ASSIGN_EMPTY_HANDLER_TYPES,
173   AssignEmptyHandlerType,
174   OPERATION_BUTTON_NAME,
175   DEFAULT_BUTTON_SETTING,
176   FieldPermissionType,
177   APPROVE_TYPE,
178   ApproveType,
179   ButtonSetting
180 } from '@/components/SimpleProcessDesignerV2/src/consts'
181 import * as UserApi from '@/api/system/user'
182 import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
183
184 defineOptions({ name: 'ElementCustomConfig4UserTask' })
185 const props = defineProps({
186   id: String,
187   type: String
188 })
189 const prefix = inject('prefix')
190
191 // 审批人与提交人为同一人时
192 const assignStartUserHandlerTypeEl = ref()
193 const assignStartUserHandlerType = ref()
194
195 // 审批人拒绝时
196 const rejectHandlerTypeEl = ref()
197 const rejectHandlerType = ref()
198 const returnNodeIdEl = ref()
199 const returnNodeId = ref()
200 const returnTaskList = ref([])
201
202 // 审批人为空时
203 const assignEmptyHandlerTypeEl = ref()
204 const assignEmptyHandlerType = ref()
205 const assignEmptyUserIdsEl = ref()
206 const assignEmptyUserIds = ref()
207
208 // 操作按钮
209 const buttonsSettingEl = ref()
210 const { btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = useButtonsSetting()
211
212 // 字段权限
213 const fieldsPermissionEl = ref([])
214 const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
215   FieldPermissionType.READ
216 )
217
218 // 审批类型
219 const approveType = ref({ value: ApproveType.USER })
220
221 const elExtensionElements = ref()
222 const otherExtensions = ref()
223 const bpmnElement = ref()
224 const bpmnInstances = () => (window as any)?.bpmnInstances
225
226 const resetCustomConfigList = () => {
227   bpmnElement.value = bpmnInstances().bpmnElement
228
229   // 获取可回退的列表
230   returnTaskList.value = findAllPredecessorsExcludingStart(
231     bpmnElement.value.id,
232     bpmnInstances().modeler
233   )
234
235   // 获取元素扩展属性 或者 创建扩展属性
236   elExtensionElements.value =
237     bpmnElement.value.businessObject?.extensionElements ??
238     bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
239
240   // 审批类型
241   approveType.value =
242     elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ApproveType`)?.[0] ||
243     bpmnInstances().moddle.create(`${prefix}:ApproveType`, { value: ApproveType.USER })
244
245   // 审批人与提交人为同一人时
246   assignStartUserHandlerTypeEl.value =
247     elExtensionElements.value.values?.filter(
248       (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
249     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
250   assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
251
252   // 审批人拒绝时
253   rejectHandlerTypeEl.value =
254     elExtensionElements.value.values?.filter(
255       (ex) => ex.$type === `${prefix}:RejectHandlerType`
256     )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
257   rejectHandlerType.value = rejectHandlerTypeEl.value.value
258   returnNodeIdEl.value =
259     elExtensionElements.value.values?.filter(
260       (ex) => ex.$type === `${prefix}:RejectReturnTaskId`
261     )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
262   returnNodeId.value = returnNodeIdEl.value.value
263
264   // 审批人为空时
265   assignEmptyHandlerTypeEl.value =
266     elExtensionElements.value.values?.filter(
267       (ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
268     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
269   assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
270   assignEmptyUserIdsEl.value =
271     elExtensionElements.value.values?.filter(
272       (ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
273     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
274   assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value?.split(',').map((item) => {
275     // 如果数字超出了最大安全整数范围,则将其作为字符串处理
276     let num = Number(item)
277     return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
278   })
279
280   // 操作按钮
281   buttonsSettingEl.value = elExtensionElements.value.values?.filter(
282     (ex) => ex.$type === `${prefix}:ButtonsSetting`
283   )
284   if (buttonsSettingEl.value.length === 0) {
285     DEFAULT_BUTTON_SETTING.forEach((item) => {
286       buttonsSettingEl.value.push(
287         bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, {
288           'flowable:id': item.id,
289           'flowable:displayName': item.displayName,
290           'flowable:enable': item.enable
291         })
292       )
293     })
294   }
295
296   // 字段权限
297   if (formType.value === 10) {
298     const fieldsPermissionList = elExtensionElements.value.values?.filter(
299       (ex) => ex.$type === `${prefix}:FieldsPermission`
300     )
301     fieldsPermissionEl.value = []
302     getNodeConfigFormFields()
303     // 由于默认添加了发起人元素,这里需要删掉
304     fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
305     fieldsPermissionConfig.value.forEach((element) => {
306       element.permission =
307         fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
308       fieldsPermissionEl.value.push(
309         bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, element)
310       )
311     })
312   }
313
314   // 保留剩余扩展元素,便于后面更新该元素对应属性
315   otherExtensions.value =
316     elExtensionElements.value.values?.filter(
317       (ex) =>
318         ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
319         ex.$type !== `${prefix}:RejectHandlerType` &&
320         ex.$type !== `${prefix}:RejectReturnTaskId` &&
321         ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
322         ex.$type !== `${prefix}:AssignEmptyUserIds` &&
323         ex.$type !== `${prefix}:ButtonsSetting` &&
324         ex.$type !== `${prefix}:FieldsPermission` &&
325         ex.$type !== `${prefix}:ApproveType`
326     ) ?? []
327
328   // 更新元素扩展属性,避免后续报错
329   updateElementExtensions()
330 }
331
332 const updateAssignStartUserHandlerType = () => {
333   assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
334
335   updateElementExtensions()
336 }
337
338 const updateRejectHandlerType = () => {
339   rejectHandlerTypeEl.value.value = rejectHandlerType.value
340
341   returnNodeId.value = returnTaskList.value[0].id
342   returnNodeIdEl.value.value = returnNodeId.value
343
344   updateElementExtensions()
345 }
346
347 const updateReturnNodeId = () => {
348   returnNodeIdEl.value.value = returnNodeId.value
349
350   updateElementExtensions()
351 }
352
353 const updateAssignEmptyHandlerType = () => {
354   assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
355
356   updateElementExtensions()
357 }
358
359 const updateAssignEmptyUserIds = () => {
360   assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
361
362   updateElementExtensions()
363 }
364
365 const updateElementExtensions = () => {
366   const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
367     values: [
368       ...otherExtensions.value,
369       assignStartUserHandlerTypeEl.value,
370       rejectHandlerTypeEl.value,
371       returnNodeIdEl.value,
372       assignEmptyHandlerTypeEl.value,
373       assignEmptyUserIdsEl.value,
374       approveType.value,
375       ...buttonsSettingEl.value,
376       ...fieldsPermissionEl.value
377     ]
378   })
379   bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
380     extensionElements: extensions
381   })
382 }
383
384 watch(
385   () => props.id,
386   (val) => {
387     val &&
388       val.length &&
389       nextTick(() => {
390         resetCustomConfigList()
391       })
392   },
393   { immediate: true }
394 )
395
396 function findAllPredecessorsExcludingStart(elementId, modeler) {
397   const elementRegistry = modeler.get('elementRegistry')
398   const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
399   const predecessors = new Set() // 使用 Set 来避免重复节点
400   const visited = new Set() // 用于记录已访问的节点
401
402   // 检查是否是开始事件节点
403   function isStartEvent(element) {
404     return element.type === 'bpmn:StartEvent'
405   }
406
407   function findPredecessorsRecursively(element) {
408     // 如果该节点已经访问过,直接返回,避免循环
409     if (visited.has(element)) {
410       return
411     }
412
413     // 标记当前节点为已访问
414     visited.add(element)
415
416     // 获取与当前节点相连的所有连接
417     const incomingConnections = allConnections.filter((connection) => connection.target === element)
418
419     incomingConnections.forEach((connection) => {
420       const source = connection.source // 获取前置节点
421
422       // 只添加不是开始事件的前置节点
423       if (!isStartEvent(source)) {
424         predecessors.add(source.businessObject)
425         // 递归查找前置节点
426         findPredecessorsRecursively(source)
427       }
428     })
429   }
430
431   const targetElement = elementRegistry.get(elementId)
432   if (targetElement) {
433     findPredecessorsRecursively(targetElement)
434   }
435
436   return Array.from(predecessors) // 返回前置节点数组
437 }
438
439 function useButtonsSetting() {
440   const buttonsSetting = ref<ButtonSetting[]>()
441   // 操作按钮显示名称可编辑
442   const btnDisplayNameEdit = ref<boolean[]>([])
443   const changeBtnDisplayName = (index: number) => {
444     btnDisplayNameEdit.value[index] = true
445   }
446   const btnDisplayNameBlurEvent = (index: number) => {
447     btnDisplayNameEdit.value[index] = false
448     const buttonItem = buttonsSetting.value![index]
449     buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!
450   }
451   return {
452     buttonsSetting,
453     btnDisplayNameEdit,
454     changeBtnDisplayName,
455     btnDisplayNameBlurEvent
456   }
457 }
458
459 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
460 onMounted(async () => {
461   // 获得用户列表
462   userOptions.value = await UserApi.getSimpleUserList()
463 })
464 </script>
465
466 <style lang="scss" scoped>
467 .button-setting-pane {
468   display: flex;
469   flex-direction: column;
470   font-size: 14px;
471   margin-top: 8px;
472
473   .button-setting-desc {
474     padding-right: 8px;
475     margin-bottom: 16px;
476     font-size: 16px;
477     font-weight: 700;
478   }
479
480   .button-setting-title {
481     display: flex;
482     justify-content: space-between;
483     align-items: center;
484     height: 45px;
485     padding-left: 12px;
486     background-color: #f8fafc0a;
487     border: 1px solid #1f38581a;
488
489     & > :first-child {
490       width: 100px !important;
491       text-align: left !important;
492     }
493
494     & > :last-child {
495       text-align: center !important;
496     }
497
498     .button-title-label {
499       width: 150px;
500       font-size: 13px;
501       font-weight: 700;
502       color: #000;
503       text-align: left;
504     }
505   }
506
507   .button-setting-item {
508     align-items: center;
509     display: flex;
510     justify-content: space-between;
511     height: 38px;
512     padding-left: 12px;
513     border: 1px solid #1f38581a;
514     border-top: 0;
515
516     & > :first-child {
517       width: 100px !important;
518     }
519
520     & > :last-child {
521       text-align: center !important;
522     }
523
524     .button-setting-item-label {
525       width: 150px;
526       overflow: hidden;
527       text-align: left;
528       text-overflow: ellipsis;
529       white-space: nowrap;
530     }
531
532     .editable-title-input {
533       height: 24px;
534       max-width: 130px;
535       margin-left: 4px;
536       line-height: 24px;
537       border: 1px solid #d9d9d9;
538       border-radius: 4px;
539       transition: all 0.3s;
540
541       &:focus {
542         border-color: #40a9ff;
543         outline: 0;
544         box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
545       }
546     }
547   }
548 }
549
550 .field-setting-pane {
551   display: flex;
552   flex-direction: column;
553   font-size: 14px;
554
555   .field-setting-desc {
556     padding-right: 8px;
557     margin-bottom: 16px;
558     font-size: 16px;
559     font-weight: 700;
560   }
561
562   .field-permit-title {
563     display: flex;
564     justify-content: space-between;
565     align-items: center;
566     height: 45px;
567     padding-left: 12px;
568     line-height: 45px;
569     background-color: #f8fafc0a;
570     border: 1px solid #1f38581a;
571
572     .first-title {
573       text-align: left !important;
574     }
575
576     .other-titles {
577       display: flex;
578       justify-content: space-between;
579     }
580
581     .setting-title-label {
582       display: inline-block;
583       width: 100px;
584       padding: 5px 0;
585       font-size: 13px;
586       font-weight: 700;
587       color: #000;
588       text-align: center;
589     }
590   }
591
592   .field-setting-item {
593     align-items: center;
594     display: flex;
595     justify-content: space-between;
596     height: 38px;
597     padding-left: 12px;
598     border: 1px solid #1f38581a;
599     border-top: 0;
600
601     .field-setting-item-label {
602       display: inline-block;
603       width: 100px;
604       min-height: 16px;
605       overflow: hidden;
606       text-overflow: ellipsis;
607       white-space: nowrap;
608       cursor: text;
609     }
610
611     .field-setting-item-group {
612       display: flex;
613       justify-content: space-between;
614
615       .item-radio-wrap {
616         display: inline-block;
617         width: 100px;
618         text-align: center;
619       }
620     }
621   }
622 }
623 </style>