xm
2024-06-14 722af26bc6fec32bb289b1df51a9016a4935610f
提交 | 用户 | 时间
722af2 1 <template>
X 2   <div>
3     <el-row>
4       <h4><b>审批人设置</b></h4>
5       <el-radio-group v-model="dataType" @change="changeDataType">
6         <el-radio label="USERS">指定用户</el-radio>
7         <el-radio label="ROLES">角色</el-radio>
8         <el-radio label="DEPTS">部门</el-radio>
9         <el-radio label="INITIATOR">发起人</el-radio>
10       </el-radio-group>
11     </el-row>
12     <el-row>
13       <div v-if="dataType === 'USERS'">
14         <el-tag v-for="userText in selectedUser.text" :key="userText" effect="plain">
15           {{userText}}
16         </el-tag>
17         <div class="element-drawer__button">
18           <el-button size="mini" type="primary" icon="el-icon-plus" @click="onSelectUsers()">添加用户</el-button>
19         </div>
20       </div>
21       <div v-if="dataType === 'ROLES'">
22         <el-select v-model="roleIds" multiple size="mini" placeholder="请选择 角色" @change="changeSelectRoles">
23           <el-option
24             v-for="item in roleOptions"
25             :key="item.roleId"
26             :label="item.roleName"
27             :value="`ROLE${item.roleId}`"
28             :disabled="item.status === 1">
29           </el-option>
30         </el-select>
31       </div>
32       <div v-if="dataType === 'DEPTS'">
33         <tree-select
34           :width="320"
35           :height="400"
36           size="mini"
37           :data="deptTreeData"
38           :defaultProps="deptProps"
39           multiple
40           clearable
41           checkStrictly
42           nodeKey="id"
43           :checkedKeys="deptIds"
44           @change="checkedDeptChange">
45         </tree-select>
46       </div>
47     </el-row>
48     <el-row>
49       <div v-show="showMultiFlog">
50         <el-divider />
51         <h4><b>多实例审批方式</b></h4>
52         <el-row>
53           <el-radio-group v-model="multiLoopType" @change="changeMultiLoopType()">
54             <el-row><el-radio label="Null">无</el-radio></el-row>
55             <el-row><el-radio label="SequentialMultiInstance">会签(需所有审批人同意)</el-radio></el-row>
56             <el-row><el-radio label="ParallelMultiInstance">或签(一名审批人同意即可)</el-radio></el-row>
57           </el-radio-group>
58         </el-row>
59         <el-row v-if="multiLoopType !== 'Null'">
60           <el-tooltip content="开启后,实例需按顺序轮流审批" placement="top-start" @click.stop.prevent>
61             <i class="header-icon el-icon-info"></i>
62           </el-tooltip>
63           <span class="custom-label">顺序审批:</span>
64           <el-switch v-model="isSequential" @change="changeMultiLoopType()" />
65         </el-row>
66       </div>
67     </el-row>
68
69     <!-- 候选用户弹窗 -->
70     <el-dialog title="候选用户" :visible.sync="userOpen" width="60%" append-to-body>
71       <el-row type="flex" :gutter="20">
72         <!--部门数据-->
73         <el-col :span="7">
74           <el-card shadow="never" style="height: 100%">
75             <div slot="header">
76               <span>部门列表</span>
77             </div>
78             <div class="head-container">
79               <el-input
80                 v-model="deptName"
81                 placeholder="请输入部门名称"
82                 clearable
83                 size="small"
84                 prefix-icon="el-icon-search"
85                 style="margin-bottom: 20px"
86               />
87               <el-tree
88                 :data="deptOptions"
89                 :props="deptProps"
90                 :expand-on-click-node="false"
91                 :filter-node-method="filterNode"
92                 ref="tree"
93                 default-expand-all
94                 @node-click="handleNodeClick"
95               />
96             </div>
97           </el-card>
98         </el-col>
99         <el-col :span="17">
100           <el-table ref="multipleTable" height="600" :data="userTableList" border @selection-change="handleSelectionChange">
101             <el-table-column type="selection" width="50" align="center" />
102             <el-table-column label="用户名" align="center" prop="nickName" />
103             <el-table-column label="部门" align="center" prop="dept.deptName" />
104           </el-table>
105           <pagination
106             :total="userTotal"
107             :page.sync="queryParams.pageNum"
108             :limit.sync="queryParams.pageSize"
109             @pagination="getUserList"
110           />
111         </el-col>
112       </el-row>
113       <div slot="footer" class="dialog-footer">
114         <el-button type="primary" @click="handleTaskUserComplete">确 定</el-button>
115         <el-button @click="userOpen = false">取 消</el-button>
116       </div>
117     </el-dialog>
118   </div>
119
120 </template>
121
122 <script>
123 import { listUser, deptTreeSelect } from "@/api/system/user";
124 import { listRole } from "@/api/system/role";
125 import TreeSelect from "@/components/TreeSelect";
126
127 const userTaskForm = {
128   dataType: '',
129   assignee: '',
130   candidateUsers: '',
131   candidateGroups: '',
132   text: '',
133   // dueDate: '',
134   // followUpDate: '',
135   // priority: ''
136 }
137
138 export default {
139   name: "UserTask",
140   props: {
141     id: String,
142     type: String
143   },
144   components: { TreeSelect },
145   data() {
146     return {
147       loading: false,
148       dataType: 'USERS',
149       selectedUser: {
150         ids: [],
151         text: []
152       },
153       userOpen: false,
154       deptName: undefined,
155       deptOptions: [],
156       deptProps: {
157         children: "children",
158         label: "label"
159       },
160       deptTempOptions: [],
161       userTableList: [],
162       userTotal: 0,
163       selectedUserDate: [],
164       roleOptions: [],
165       roleIds: [],
166       deptTreeData: [],
167       deptIds: [],
168       // 查询参数
169       queryParams: {
170         deptId: undefined
171       },
172       showMultiFlog: false,
173       isSequential: false,
174       multiLoopType: 'Null',
175     };
176   },
177   watch: {
178     id: {
179       immediate: true,
180       handler() {
181         this.bpmnElement = window.bpmnInstances.bpmnElement;
182         this.$nextTick(() => this.resetTaskForm());
183       }
184     },
185     // 根据名称筛选部门树
186     deptName(val) {
187       this.$refs.tree.filter(val);
188     }
189   },
190   beforeDestroy() {
191     this.bpmnElement = null;
192   },
193   methods: {
194     resetTaskForm() {
195       const bpmnElementObj = this.bpmnElement?.businessObject;
196       if (!bpmnElementObj) {
197         return;
198       }
199       this.clearOptionsData()
200       this.dataType = bpmnElementObj['dataType'];
201       if (this.dataType === 'USERS') {
202         let userIdData = bpmnElementObj['candidateUsers'] || bpmnElementObj['assignee'];
203         let userText = bpmnElementObj['text'] || [];
204         if (userIdData && userIdData.toString().length > 0 && userText && userText.length > 0) {
205           this.selectedUser.ids = userIdData?.toString().split(',');
206           this.selectedUser.text = userText?.split(',');
207         }
208         if (this.selectedUser.ids.length > 1) {
209           this.showMultiFlog = true;
210         }
211       } else if (this.dataType === 'ROLES') {
212         this.getRoleOptions();
213         let roleIdData = bpmnElementObj['candidateGroups'] || [];
214         if (roleIdData && roleIdData.length > 0) {
215           this.roleIds = roleIdData.split(',')
216         }
217         this.showMultiFlog = true;
218       } else if (this.dataType === 'DEPTS') {
219         this.getDeptTreeData();
220         let deptIdData = bpmnElementObj['candidateGroups'] || [];
221         if (deptIdData && deptIdData.length > 0) {
222           this.deptIds = deptIdData.split(',');
223         }
224         this.showMultiFlog = true;
225       }
226       this.getElementLoop(bpmnElementObj);
227     },
228     /**
229      * 清空选项数据
230      */
231     clearOptionsData() {
232       this.selectedUser.ids = [];
233       this.selectedUser.text = [];
234       this.roleIds = [];
235       this.deptIds = [];
236     },
237     /**
238      * 跟新节点数据
239      */
240     updateElementTask() {
241       const taskAttr = Object.create(null);
242       for (let key in userTaskForm) {
243           taskAttr[key] = userTaskForm[key];
244       }
245       window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
246     },
247     /**
248      * 查询部门下拉树结构
249      */
250     getDeptOptions() {
251       return new Promise((resolve, reject) => {
252         if (!this.deptOptions || this.deptOptions.length <= 0) {
253           deptTreeSelect().then(response => {
254             this.deptTempOptions = response.data;
255             this.deptOptions = response.data;
256             resolve()
257           })
258         } else {
259           reject()
260         }
261       });
262     },
263     /**
264      * 查询部门下拉树结构(含部门前缀)
265      */
266     getDeptTreeData() {
267       function refactorTree(data) {
268         return data.map(node => {
269           let treeData = { id: `DEPT${node.id}`, label: node.label, parentId: node.parentId, weight: node.weight };
270           if (node.children && node.children.length > 0) {
271             treeData.children = refactorTree(node.children);
272           }
273           return treeData;
274         });
275       }
276       return new Promise((resolve, reject) => {
277         if (!this.deptTreeData || this.deptTreeData.length <= 0) {
278           this.getDeptOptions().then(() => {
279             this.deptTreeData = refactorTree(this.deptOptions);
280             resolve()
281           }).catch(() => {
282             reject()
283           })
284         } else {
285           resolve()
286         }
287       })
288     },
289     /**
290      * 查询部门下拉树结构
291      */
292     getRoleOptions() {
293       if (!this.roleOptions || this.roleOptions.length <= 0) {
294         listRole().then(response => this.roleOptions = response.rows);
295       }
296     },
297     /** 查询用户列表 */
298     getUserList() {
299       listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
300         this.userTableList = response.rows;
301         this.userTotal = response.total;
302       });
303     },
304     // 筛选节点
305     filterNode(value, data) {
306       if (!value) return true;
307       return data.label.indexOf(value) !== -1;
308     },
309     // 节点单击事件
310     handleNodeClick(data) {
311       this.queryParams.deptId = data.id;
312       this.getUserList();
313     },
314     // 关闭标签
315     handleClose(tag) {
316       this.selectedUserDate.splice(this.selectedUserDate.indexOf(tag), 1);
317       this.$refs.multipleTable.toggleRowSelection(tag);
318     },
319     // 多选框选中数据
320     handleSelectionChange(selection) {
321       this.selectedUserDate = selection;
322     },
323     onSelectUsers() {
324       this.selectedUserDate = []
325       this.$refs.multipleTable?.clearSelection();
326       this.getDeptOptions();
327       this.userOpen = true;
328     },
329     handleTaskUserComplete() {
330       if (!this.selectedUserDate || this.selectedUserDate.length <= 0) {
331         this.$modal.msgError('请选择用户');
332         return;
333       }
334       userTaskForm.dataType = 'USERS';
335       this.selectedUser.text = this.selectedUserDate.map(k => k.nickName) || [];
336       if (this.selectedUserDate.length === 1) {
337         let data = this.selectedUserDate[0];
338         userTaskForm.assignee = data.userId;
339         userTaskForm.text = data.nickName;
340         userTaskForm.candidateUsers = null;
341         this.showMultiFlog = false;
342         this.multiLoopType = 'Null';
343         this.changeMultiLoopType();
344       } else {
345         userTaskForm.candidateUsers = this.selectedUserDate.map(k => k.userId).join() || null;
346         userTaskForm.text = this.selectedUserDate.map(k => k.nickName).join() || null;
347         userTaskForm.assignee = null;
348         this.showMultiFlog = true;
349       }
350       this.updateElementTask()
351       this.userOpen = false;
352     },
353     changeSelectRoles(val) {
354       let groups = null;
355       let text = null;
356       if (val && val.length > 0) {
357         userTaskForm.dataType = 'ROLES';
358         groups = val.join() || null;
359         let textArr = this.roleOptions.filter(k => val.indexOf(`ROLE${k.roleId}`) >= 0);
360         text = textArr?.map(k => k.roleName).join() || null;
361       } else {
362         userTaskForm.dataType = null;
363         this.multiLoopType = 'Null';
364       }
365       userTaskForm.candidateGroups = groups;
366       userTaskForm.text = text;
367       this.updateElementTask();
368       this.changeMultiLoopType();
369     },
370     checkedDeptChange(checkedIds) {
371       let groups = null;
372       let text = null;
373       this.deptIds = checkedIds;
374       if (checkedIds && checkedIds.length > 0) {
375         userTaskForm.dataType = 'DEPTS';
376         groups = checkedIds.join() || null;
377         let textArr = []
378         let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
379         checkedIds.forEach(id => {
380           let stark = []
381           stark = stark.concat(treeStarkData);
382           while(stark.length) {
383             let temp = stark.shift();
384             if(temp.children) {
385               stark = temp.children.concat(stark);
386             }
387             if(id === temp.id) {
388               textArr.push(temp);
389             }
390           }
391         })
392         text = textArr?.map(k => k.label).join() || null;
393       } else {
394         userTaskForm.dataType = null;
395         this.multiLoopType = 'Null';
396       }
397       userTaskForm.candidateGroups = groups;
398       userTaskForm.text = text;
399       this.updateElementTask();
400       this.changeMultiLoopType();
401     },
402     changeDataType(val) {
403       if (val === 'ROLES' || val === 'DEPTS' || (val === 'USERS' && this.selectedUser.ids.length > 1)) {
404         this.showMultiFlog = true;
405       } else {
406         this.showMultiFlog = false;
407       }
408       this.multiLoopType = 'Null';
409       this.changeMultiLoopType();
410       // 清空 userTaskForm 所有属性值
411       Object.keys(userTaskForm).forEach(key => userTaskForm[key] = null);
412       userTaskForm.dataType = val;
413       if (val === 'USERS') {
414         if (this.selectedUser && this.selectedUser.ids && this.selectedUser.ids.length > 0) {
415           if (this.selectedUser.ids.length === 1) {
416             userTaskForm.assignee = this.selectedUser.ids[0];
417           } else {
418             userTaskForm.candidateUsers = this.selectedUser.ids.join()
419           }
420           userTaskForm.text = this.selectedUser.text?.join() || null
421         }
422       } else if (val === 'ROLES') {
423         this.getRoleOptions();
424         if (this.roleIds && this.roleIds.length > 0) {
425           userTaskForm.candidateGroups = this.roleIds.join() || null;
426           let textArr = this.roleOptions.filter(k => this.roleIds.indexOf(`ROLE${k.roleId}`) >= 0);
427           userTaskForm.text = textArr?.map(k => k.roleName).join() || null;
428         }
429       } else if (val === 'DEPTS') {
430         this.getDeptTreeData();
431         if (this.deptIds && this.deptIds.length > 0) {
432           userTaskForm.candidateGroups = this.deptIds.join() || null;
433           let textArr = []
434           let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
435           this.deptIds.forEach(id => {
436             let stark = []
437             stark = stark.concat(treeStarkData);
438             while(stark.length) {
439               let temp = stark.shift();
440               if(temp.children) {
441                 stark = temp.children.concat(stark);
442               }
443               if(id === temp.id) {
444                 textArr.push(temp);
445               }
446             }
447           })
448           userTaskForm.text = textArr?.map(k => k.label).join() || null;
449         }
450       } else if (val === 'INITIATOR') {
451         userTaskForm.assignee = "${initiator}";
452         userTaskForm.text = "流程发起人";
453       }
454       this.updateElementTask();
455     },
456     getElementLoop(businessObject) {
457       if (!businessObject.loopCharacteristics) {
458         this.multiLoopType = "Null";
459         return;
460       }
461       this.isSequential = businessObject.loopCharacteristics.isSequential;
462       if (businessObject.loopCharacteristics.completionCondition) {
463         if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= nrOfInstances}") {
464           this.multiLoopType = "SequentialMultiInstance";
465         } else {
466           this.multiLoopType = "ParallelMultiInstance";
467
468         }
469       }
470     },
471     changeMultiLoopType() {
472       // 取消多实例配置
473       if (this.multiLoopType === "Null") {
474         window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null, assignee: null });
475         return;
476       }
477       this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: this.isSequential });
478       // 更新多实例配置
479       window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
480         loopCharacteristics: this.multiLoopInstance,
481         assignee: '${assignee}'
482       });
483       // 完成条件
484       let completionCondition = null;
485       // 会签
486       if (this.multiLoopType === "SequentialMultiInstance") {
487         completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances >= nrOfInstances}" });
488       }
489       // 或签
490       if (this.multiLoopType === "ParallelMultiInstance") {
491         completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances > 0}" });
492       }
493       // 更新模块属性信息
494       window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
495         collection: '${multiInstanceHandler.getUserIds(execution)}',
496         elementVariable: 'assignee',
497         completionCondition
498       });
499     },
500   }
501 };
502 </script>
503
504 <style scoped lang="scss">
505 .el-row .el-radio-group {
506   margin-bottom: 15px;
507   .el-radio {
508     line-height: 28px;
509   }
510 }
511 .el-tag {
512   margin-bottom: 10px;
513   + .el-tag {
514     margin-left: 10px;
515   }
516 }
517
518 .custom-label {
519   padding-left: 5px;
520   font-weight: 500;
521   font-size: 14px;
522   color: #606266;
523 }
524
525 </style>