houzhongjian
2024-07-11 759b1c71011abd6b58c37d2566f3f3c208c2f1b2
提交 | 用户 | 时间
759b1c 1 <template>
H 2   <div class="app-container">
3     <!-- 审批信息 -->
4     <el-card class="box-card" v-loading="processInstanceLoading" v-for="(item, index) in runningTasks" :key="index">
5       <div slot="header" class="clearfix">
6         <span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span>
7       </div>
8       <el-col :span="16" :offset="6">
9         <el-form :ref="'form' + index" :model="auditForms[index]" :rules="auditRule" label-width="100px">
10           <el-form-item label="流程名" v-if="processInstance && processInstance.name">
11             {{ processInstance.name }}
12           </el-form-item>
13           <el-form-item label="流程发起人" v-if="processInstance && processInstance.startUser">
14             {{ processInstance.startUser.nickname }}
15             <el-tag type="info" size="mini">{{ processInstance.startUser.deptName }}</el-tag>
16           </el-form-item>
17           <el-form-item label="审批建议" prop="reason">
18             <el-input type="textarea" v-model="auditForms[index].reason" placeholder="请输入审批建议"/>
19           </el-form-item>
20         </el-form>
21         <div style="margin-left: 10%; margin-bottom: 20px; font-size: 14px;">
22           <el-button icon="el-icon-edit-outline" type="success" size="mini" @click="handleAudit(item, true)">通过
23           </el-button>
24           <el-button icon="el-icon-circle-close" type="danger" size="mini" @click="handleAudit(item, false)">不通过
25           </el-button>
26           <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleUpdateAssignee(item)">转办
27           </el-button>
28           <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate(item)">委派
29           </el-button>
30           <el-button icon="el-icon-refresh-left" type="warning" size="mini" @click="handleBackList(item)">退回
31           </el-button>
32         </div>
33       </el-col>
34     </el-card>
35     <!-- 申请信息 -->
36     <el-card class="box-card" v-loading="processInstanceLoading">
37       <div slot="header" class="clearfix">
38         <span class="el-icon-document">申请信息【{{ processInstance.name }}】</span>
39       </div>
40       <el-col v-if="this.processInstance.processDefinition && this.processInstance.processDefinition.formType === 10"
41               :span="16" :offset="6">
42         <div>
43           <parser :key="new Date().getTime()" :form-conf="detailForm"/>
44         </div>
45       </el-col>
46       <div v-if="this.processInstance.processDefinition && this.processInstance.processDefinition.formType === 20">
47         <async-biz-form-component :id="this.processInstance.businessKey"></async-biz-form-component>
48       </div>
49     </el-card>
50
51     <!-- 审批记录 -->
52     <el-card class="box-card" v-loading="tasksLoad">
53       <div slot="header" class="clearfix">
54         <span class="el-icon-picture-outline">审批记录</span>
55       </div>
56       <el-col :span="16" :offset="4">
57         <div class="block">
58           <el-timeline>
59             <el-timeline-item v-for="(item, index) in tasks" :key="index"
60                               :icon="getTimelineItemIcon(item)" :type="getTimelineItemType(item)">
61               <p style="font-weight: 700">任务:{{ item.name }}</p>
62               <el-card :body-style="{ padding: '10px' }">
63                 <label v-if="item.assigneeUser" style="font-weight: normal; margin-right: 30px;">
64                   审批人:{{ item.assigneeUser.nickname }}
65                   <el-tag type="info" size="mini">{{ item.assigneeUser.deptName }}</el-tag>
66                 </label>
67                 <label style="font-weight: normal" v-if="item.createTime">创建时间:</label>
68                 <label style="color:#8a909c; font-weight: normal">{{ parseTime(item.createTime) }}</label>
69                 <label v-if="item.endTime" style="margin-left: 30px;font-weight: normal">审批时间:</label>
70                 <label v-if="item.endTime" style="color:#8a909c;font-weight: normal"> {{
71                     parseTime(item.endTime)
72                   }}</label>
73                 <label v-if="item.durationInMillis" style="margin-left: 30px;font-weight: normal">耗时:</label>
74                 <label v-if="item.durationInMillis" style="color:#8a909c;font-weight: normal">
75                   {{ getDateStar(item.durationInMillis) }} </label>
76                 <p v-if="item.reason">
77                   <el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag>
78                 </p>
79               </el-card>
80             </el-timeline-item>
81           </el-timeline>
82         </div>
83       </el-col>
84     </el-card>
85
86     <!-- 高亮流程图 -->
87     <el-card class="box-card" v-loading="processInstanceLoading">
88       <div slot="header" class="clearfix">
89         <span class="el-icon-picture-outline">流程图</span>
90       </div>
91       <my-process-viewer key="designer" v-model="bpmnXML" v-bind="bpmnControlForm" :activityData="activityList"
92                          :processInstanceData="processInstance" :taskData="tasks"/>
93     </el-card>
94
95     <!-- 对话框(转办审批人) -->
96     <el-dialog title="转办审批人" :visible.sync="updateAssignee.open" width="500px" append-to-body>
97       <el-form ref="updateAssigneeForm" :model="updateAssignee.form" :rules="updateAssignee.rules" label-width="110px">
98         <el-form-item label="新审批人" prop="assigneeUserId">
99           <el-select v-model="updateAssignee.form.assigneeUserId" clearable style="width: 100%">
100             <el-option v-for="item in userOptions" :key="parseInt(item.id)" :label="item.nickname"
101                        :value="parseInt(item.id)"/>
102           </el-select>
103         </el-form-item>
104       </el-form>
105       <div slot="footer" class="dialog-footer">
106         <el-button type="primary" @click="submitUpdateAssigneeForm">确 定</el-button>
107         <el-button @click="cancelUpdateAssigneeForm">取 消</el-button>
108       </div>
109     </el-dialog>
110     <!-- 对话框(委派审批人) -->
111     <el-dialog title="委派审批人" :visible.sync="updateDelegate.open" width="500px" append-to-body>
112       <el-form ref="updateDelegateForm" :model="updateDelegate.form" :rules="updateDelegate.rules" label-width="110px">
113         <el-form-item label="新审批人" prop="assigneeUserId">
114           <el-select v-model="updateDelegate.form.delegateUserId" clearable style="width: 100%">
115             <el-option v-for="item in userOptions" :key="parseInt(item.id)" :label="item.nickname"
116                        :value="parseInt(item.id)"/>
117           </el-select>
118         </el-form-item>
119         <el-form-item label="委派理由" prop="reason">
120           <el-input v-model="updateDelegate.form.reason" clearable placeholder="请输入委派理由"/>
121         </el-form-item>
122       </el-form>
123       <div slot="footer" class="dialog-footer">
124         <el-button type="primary" @click="submitUpdateDelegateForm">确 定</el-button>
125         <el-button @click="cancelUpdateDelegateForm">取 消</el-button>
126       </div>
127     </el-dialog>
128     <!--退回流程-->
129     <el-dialog title="退回流程" :visible.sync="returnOpen" width="40%" append-to-body>
130       <el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="110px">
131         <el-form-item label="退回节点" prop="targetDefinitionKey">
132           <el-select v-model="formData.targetDefinitionKey" clearable style="width: 100%">
133             <el-option
134               v-for="item in returnList"
135               :key="item.definitionKey"
136               :label="item.name"
137               :value="item.definitionKey"
138             />
139           </el-select>
140         </el-form-item>
141         <el-form-item label="回退理由" prop="reason">
142           <el-input v-model="formData.reason" clearable placeholder="请输入回退理由"/>
143         </el-form-item>
144       </el-form>
145       <span slot="footer" class="dialog-footer">
146         <el-button @click="returnOpen = false">取 消</el-button>
147         <el-button :disabled="formLoading" type="primary" @click="submitReturn">确 定</el-button>
148       </span>
149     </el-dialog>
150   </div>
151 </template>
152
153 <script>
154 import {getProcessDefinitionBpmnXML} from "@/api/bpm/definition";
155 import store from "@/store";
156 import {decodeFields} from "@/utils/formGenerator";
157 import Parser from '@/components/parser/Parser'
158 import {getProcessInstance} from "@/api/bpm/processInstance";
159 import {
160   approveTask,
161   delegateTask,
162   getReturnList,
163   getTaskListByProcessInstanceId,
164   rejectTask,
165   returnTask,
166   updateTaskAssignee,
167 } from "@/api/bpm/task";
168 import {getDate} from "@/utils/dateUtils";
169 import {listSimpleUsers} from "@/api/system/user";
170 import {getActivityList} from "@/api/bpm/activity";
171 import Vue from "vue";
172
173 // 流程实例的详情页,可用于审批
174 export default {
175   name: "ProcessInstanceDetail",
176   components: {
177     Parser
178   },
179   data() {
180     return {
181       // 遮罩层
182       processInstanceLoading: true,
183       // 流程实例
184       id: undefined, // 流程实例的编号
185       processInstance: {},
186       formLoading: false,
187       // 流程表单详情
188       detailForm: {
189         fields: []
190       },
191       //回退列表数据
192       returnList: [],
193       formData: {
194         id: '',
195         targetDefinitionKey: undefined,
196         reason: ''
197       },
198       formRules: {
199         targetDefinitionKey: [{required: true, message: '必须选择回退节点', trigger: 'change'}],
200         reason: [{required: true, message: '回退理由不能为空', trigger: 'blur'}]
201       },
202       returnOpen: false,
203       // BPMN 数据
204       bpmnXML: null,
205       bpmnControlForm: {
206         prefix: "flowable"
207       },
208       activityList: [],
209
210       // 审批记录
211       tasksLoad: true,
212       tasks: [],
213
214       // 审批表单
215       runningTasks: [],
216       auditForms: [],
217       auditRule: {
218         reason: [{required: true, message: "审批建议不能为空", trigger: "blur"}],
219       },
220       // 转派审批人
221       userOptions: [],
222       updateAssignee: {
223         open: false,
224         form: {
225           assigneeUserId: undefined,
226         },
227         rules: {
228           assigneeUserId: [{required: true, message: "新审批人不能为空", trigger: "change"}],
229         }
230       },
231       updateDelegate: {
232         open: false,
233         form: {
234           delegateUserId: undefined,
235           reason: ''
236         },
237         rules: {
238           delegateUserId: [{required: true, message: "新审批人不能为空", trigger: "change"}],
239           reason: [{required: true, message: '委派理由不能为空', trigger: 'blur'}]
240         }
241       }
242     };
243   },
244   created() {
245     this.id = this.$route.query.id;
246     if (!this.id) {
247       this.$message.error('未传递 id 参数,无法查看流程信息');
248       return;
249     }
250     this.getDetail();
251
252     // 获得用户列表
253     this.userOptions = [];
254     listSimpleUsers().then(response => {
255       this.userOptions.push(...response.data);
256     });
257   },
258   methods: {
259     /** 获得流程实例 */
260     getDetail() {
261       // 获得流程实例相关
262       this.processInstanceLoading = true;
263       getProcessInstance(this.id).then(response => {
264         if (!response.data) {
265           this.$message.error('查询不到流程信息!');
266           return;
267         }
268         // 设置流程信息
269         this.processInstance = response.data;
270
271         //将业务表单,注册为动态组件
272         const path = this.processInstance.processDefinition.formCustomViewPath;
273         Vue.component("async-biz-form-component", function (resolve) {
274           require([`@/views${path}`], resolve);
275         });
276
277         // 设置表单信息
278         if (this.processInstance.processDefinition.formType === 10) {
279           this.detailForm = {
280             ...JSON.parse(this.processInstance.processDefinition.formConf),
281             disabled: true, // 表单禁用
282             formBtns: false, // 按钮隐藏
283             fields: decodeFields(this.processInstance.processDefinition.formFields)
284           }
285           // 设置表单的值
286           this.detailForm.fields.forEach(item => {
287             const val = this.processInstance.formVariables[item.__vModel__]
288             if (val) {
289               item.__config__.defaultValue = val
290             }
291           });
292         }
293
294         // 加载流程图
295         getProcessDefinitionBpmnXML(this.processInstance.processDefinition.id).then(response => {
296           this.bpmnXML = response.data
297         });
298         // 加载活动列表
299         getActivityList({
300           processInstanceId: this.processInstance.id
301         }).then(response => {
302           this.activityList = response.data;
303         });
304
305         // 取消加载中
306         this.processInstanceLoading = false;
307       });
308
309       // 获得流程任务列表(审批记录)
310       this.tasksLoad = true;
311       this.runningTasks = [];
312       this.auditForms = [];
313       getTaskListByProcessInstanceId(this.id).then(response => {
314         // 审批记录
315         this.tasks = [];
316         // 移除已取消的审批
317         response.data.forEach(task => {
318           if (task.result !== 4) {
319             this.tasks.push(task);
320           }
321         });
322         // 排序,将未完成的排在前面,已完成的排在后面;
323         this.tasks.sort((a, b) => {
324           // 有已完成的情况,按照完成时间倒序
325           if (a.endTime && b.endTime) {
326             return b.endTime - a.endTime;
327           } else if (a.endTime) {
328             return 1;
329           } else if (b.endTime) {
330             return -1;
331             // 都是未完成,按照创建时间倒序
332           } else {
333             return b.createTime - a.createTime;
334           }
335         });
336
337         // 需要审核的记录
338         const userId = store.getters.userId;
339         this.tasks.forEach(task => {
340           if (task.result !== 1 && task.result !== 6) { // 只有待处理才需要
341             return;
342           }
343           if (!task.assigneeUser || task.assigneeUser.id !== userId) { // 自己不是处理人
344             return;
345           }
346           this.runningTasks.push({...task});
347           this.auditForms.push({
348             reason: ''
349           })
350         });
351
352         // 取消加载中
353         this.tasksLoad = false;
354       });
355     },
356     getDateStar(ms) {
357       return getDate(ms);
358     },
359     getTimelineItemIcon(item) {
360       if (item.result === 1) {
361         return 'el-icon-time';
362       }
363       if (item.result === 2) {
364         return 'el-icon-check';
365       }
366       if (item.result === 3) {
367         return 'el-icon-close';
368       }
369       if (item.result === 4) {
370         return 'el-icon-remove-outline';
371       }
372       if (item.result === 5) {
373         return 'el-icon-back'
374       }
375       return '';
376     },
377     getTimelineItemType(item) {
378       if (item.result === 1) {
379         return 'primary';
380       }
381       if (item.result === 2) {
382         return 'success';
383       }
384       if (item.result === 3) {
385         return 'danger';
386       }
387       if (item.result === 4) {
388         return 'info';
389       }
390       if (item.result === 5) {
391         return 'warning';
392       }
393       if (item.result === 6) {
394         return 'default'
395       }
396       return '';
397     },
398     /** 处理审批通过和不通过的操作 */
399     handleAudit(task, pass) {
400       const index = this.runningTasks.indexOf(task);
401       this.$refs['form' + index][0].validate(valid => {
402         if (!valid) {
403           return;
404         }
405         const data = {
406           id: task.id,
407           reason: this.auditForms[index].reason
408         }
409         if (pass) {
410           approveTask(data).then(response => {
411             this.$modal.msgSuccess("审批通过成功!");
412             this.getDetail(); // 获得最新详情
413           });
414         } else {
415           rejectTask(data).then(response => {
416             this.$modal.msgSuccess("审批不通过成功!");
417             this.getDetail(); // 获得最新详情
418           });
419         }
420       });
421     },
422     /** 处理转办审批人 */
423     handleUpdateAssignee(task) {
424       // 设置表单
425       this.resetUpdateAssigneeForm();
426       this.updateAssignee.form.id = task.id;
427       // 设置为打开
428       this.updateAssignee.open = true;
429     },
430     /** 提交转办审批人 */
431     submitUpdateAssigneeForm() {
432       this.$refs['updateAssigneeForm'].validate(valid => {
433         if (!valid) {
434           return;
435         }
436         updateTaskAssignee(this.updateAssignee.form).then(response => {
437           this.$modal.msgSuccess("转办任务成功!");
438           this.updateAssignee.open = false;
439           this.getDetail(); // 获得最新详情
440         });
441       });
442     },
443     /** 取消转办审批人 */
444     cancelUpdateAssigneeForm() {
445       this.updateAssignee.open = false;
446       this.resetUpdateAssigneeForm();
447     },
448     /** 重置转办审批人 */
449     resetUpdateAssigneeForm() {
450       this.updateAssignee.form = {
451         id: undefined,
452         assigneeUserId: undefined,
453       };
454       this.resetForm("updateAssigneeForm");
455     },
456     /** 处理审批委派的操作 */
457     handleDelegate(task) {
458       //this.$modal.msgError("暂不支持【委派】功能,可以使用【转派】替代!");
459       this.resetUpdateDelegateForm();
460       this.updateDelegate.form.id = task.id;
461       // 设置为打开
462       this.updateDelegate.open = true;
463     },
464     /** 提交委派审批人 */
465     submitUpdateDelegateForm() {
466       this.$refs['updateDelegateForm'].validate(valid => {
467         if (!valid) {
468           return;
469         }
470         delegateTask(this.updateDelegate.form).then(response => {
471           this.$modal.msgSuccess("委派任务成功!");
472           this.updateDelegate.open = false;
473           this.getDetail(); // 获得最新详情
474         });
475       });
476     },
477     /** 取消委派审批人 */
478     cancelUpdateDelegateForm() {
479       this.updateDelegate.open = false;
480       this.resetUpdateDelegateForm();
481     },
482     /** 重置委派审批人 */
483     resetUpdateDelegateForm() {
484       this.updateDelegate.form = {
485         id: undefined,
486         delegateUserId: undefined,
487       };
488       this.resetForm("updateDelegateForm");
489     },
490     /** 处理审批退回的操作 */
491     /** 返回退回节点列表 */
492     handleBackList(task) {
493       // 可参考 http://blog.wya1.com/article/636697030/details/7296
494       getReturnList(task.id).then(response => {
495         this.returnList = response.data;
496         if (this.returnList.length == 0) {
497           this.$modal.msgError("当前没有可回退的节点!");
498           return;
499         }
500         this.formData.id = task.id;
501         this.returnOpen = true;
502       });
503
504     },
505     /** 提交退回任务 */
506     submitReturn() {
507       if (!this.formData.targetDefinitionKey) {
508         this.$modal.msgError("请选择退回节点!");
509       }
510       this.$refs['formRef'].validate(valid => {
511         if (!valid) {
512           return;
513         }
514         returnTask(this.formData).then(res => {
515           if (res.data) {
516             this.$modal.msgSuccess("回退成功!");
517             this.returnOpen = false;
518             this.getDetail();
519           }
520         });
521       });
522     }
523   }
524 };
525 </script>
526
527 <style lang="scss">
528 .my-process-designer {
529   height: calc(100vh - 200px);
530 }
531
532 .box-card {
533   width: 100%;
534   margin-bottom: 20px;
535 }
536 </style>