Commit 0aa72b320033625d6db0be60168fa8e9a4b1bd34

Authored by YunaiV
1 parent 2774671a

bpm:流程详情的时间轴

yudao-ui-admin-vue3/src/utils/formatTime.ts
@@ -147,3 +147,30 @@ export function formatAxis(param: Date): string { @@ -147,3 +147,30 @@ export function formatAxis(param: Date): string {
147 else if (hour < 22) return '晚上好' 147 else if (hour < 22) return '晚上好'
148 else return '夜里好' 148 else return '夜里好'
149 } 149 }
  150 +
  151 +/**
  152 + * 将毫秒,转换成时间字符串。例如说,xx 分钟
  153 + *
  154 + * @param ms 毫秒
  155 + * @returns {string} 字符串
  156 + */
  157 +export function formatPast2(ms) {
  158 + const day = Math.floor(ms / (24 * 60 * 60 * 1000))
  159 + const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24)
  160 + const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60)
  161 + const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60)
  162 + if (day > 0) {
  163 + return day + '天' + hour + '小时' + minute + '分钟'
  164 + }
  165 + if (hour > 0) {
  166 + return hour + '小时' + minute + '分钟'
  167 + }
  168 + if (minute > 0) {
  169 + return minute + '分钟'
  170 + }
  171 + if (second > 0) {
  172 + return second + '秒'
  173 + } else {
  174 + return 0 + '秒'
  175 + }
  176 +}
yudao-ui-admin-vue3/src/views/bpm/processInstance/detail.vue
@@ -25,14 +25,63 @@ @@ -25,14 +25,63 @@
25 processInstance.businessKey 25 processInstance.businessKey
26 " 26 "
27 > 27 >
28 - <el-button type="primary">点击查看</el-button> 28 + <XButton type="primary" preIcon="ep:view" title="点击查看" />
29 </router-link> 29 </router-link>
30 </div> 30 </div>
31 </el-card> 31 </el-card>
  32 +
  33 + <!-- 审批记录 -->
  34 + <el-card class="box-card" v-loading="tasksLoad">
  35 + <template #header>
  36 + <span class="el-icon-picture-outline">审批记录</span>
  37 + </template>
  38 + <el-col :span="16" :offset="4">
  39 + <div class="block">
  40 + <el-timeline>
  41 + <el-timeline-item
  42 + v-for="(item, index) in tasks"
  43 + :key="index"
  44 + :icon="getTimelineItemIcon(item)"
  45 + :type="getTimelineItemType(item)"
  46 + >
  47 + <p style="font-weight: 700">任务:{{ item.name }}</p>
  48 + <el-card :body-style="{ padding: '10px' }">
  49 + <label v-if="item.assigneeUser" style="font-weight: normal; margin-right: 30px">
  50 + 审批人:{{ item.assigneeUser.nickname }}
  51 + <el-tag type="info" size="mini">{{ item.assigneeUser.deptName }}</el-tag>
  52 + </label>
  53 + <label style="font-weight: normal" v-if="item.createTime">创建时间:</label>
  54 + <label style="color: #8a909c; font-weight: normal">
  55 + {{ dayjs(item?.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  56 + </label>
  57 + <label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
  58 + 审批时间:
  59 + </label>
  60 + <label v-if="item.endTime" style="color: #8a909c; font-weight: normal">
  61 + {{ dayjs(item?.endTime).format('YYYY-MM-DD HH:mm:ss') }}
  62 + </label>
  63 + <label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
  64 + 耗时:
  65 + </label>
  66 + <label v-if="item.durationInMillis" style="color: #8a909c; font-weight: normal">
  67 + {{ formatPast2(item?.durationInMillis) }}
  68 + </label>
  69 + <p v-if="item.reason">
  70 + <el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag>
  71 + </p>
  72 + </el-card>
  73 + </el-timeline-item>
  74 + </el-timeline>
  75 + </div>
  76 + </el-col>
  77 + </el-card>
32 </ContentWrap> 78 </ContentWrap>
33 </template> 79 </template>
34 <script setup lang="ts"> 80 <script setup lang="ts">
  81 +import dayjs from 'dayjs'
35 import * as ProcessInstanceApi from '@/api/bpm/processInstance' 82 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  83 +import * as TaskApi from '@/api/bpm/task'
  84 +import { formatPast2 } from '@/utils/formatTime'
36 85
37 const { query } = useRoute() // 查询参数 86 const { query } = useRoute() // 查询参数
38 const message = useMessage() // 消息弹窗 87 const message = useMessage() // 消息弹窗
@@ -41,6 +90,11 @@ const message = useMessage() // 消息弹窗 @@ -41,6 +90,11 @@ const message = useMessage() // 消息弹窗
41 const id = query.id as unknown as number 90 const id = query.id as unknown as number
42 const processInstanceLoading = ref(false) // 流程实例的加载中 91 const processInstanceLoading = ref(false) // 流程实例的加载中
43 const processInstance = ref({}) // 流程实例 92 const processInstance = ref({}) // 流程实例
  93 +const runningTasks = ref([]) // 运行中的任务
  94 +const auditForms = ref([]) // 审批任务的表单
  95 +// const auditRule = reactive({
  96 +// reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
  97 +// })
44 98
45 // ========== 申请信息 ========== 99 // ========== 申请信息 ==========
46 import { setConfAndFields2 } from '@/utils/formCreate' 100 import { setConfAndFields2 } from '@/utils/formCreate'
@@ -53,6 +107,41 @@ const detailForm = ref({ @@ -53,6 +107,41 @@ const detailForm = ref({
53 value: {} 107 value: {}
54 }) 108 })
55 109
  110 +// ========== 审批记录 ==========
  111 +const tasksLoad = ref(true)
  112 +const tasks = ref([])
  113 +
  114 +const getTimelineItemIcon = (item) => {
  115 + if (item.result === 1) {
  116 + return 'el-icon-time'
  117 + }
  118 + if (item.result === 2) {
  119 + return 'el-icon-check'
  120 + }
  121 + if (item.result === 3) {
  122 + return 'el-icon-close'
  123 + }
  124 + if (item.result === 4) {
  125 + return 'el-icon-remove-outline'
  126 + }
  127 + return ''
  128 +}
  129 +const getTimelineItemType = (item) => {
  130 + if (item.result === 1) {
  131 + return 'primary'
  132 + }
  133 + if (item.result === 2) {
  134 + return 'success'
  135 + }
  136 + if (item.result === 3) {
  137 + return 'danger'
  138 + }
  139 + if (item.result === 4) {
  140 + return 'info'
  141 + }
  142 + return ''
  143 +}
  144 +
56 // ========== 初始化 ========== 145 // ========== 初始化 ==========
57 onMounted(() => { 146 onMounted(() => {
58 // 1. 获得流程实例相关 147 // 1. 获得流程实例相关
@@ -88,7 +177,53 @@ onMounted(() =&gt; { @@ -88,7 +177,53 @@ onMounted(() =&gt; {
88 processInstanceLoading.value = false 177 processInstanceLoading.value = false
89 }) 178 })
90 179
91 - // 2. TODO 180 + // 2. 获得流程任务列表(审批记录)
  181 + tasksLoad.value = true
  182 + runningTasks.value = []
  183 + auditForms.value = []
  184 + TaskApi.getTaskListByProcessInstanceId(id)
  185 + .then((data) => {
  186 + // 审批记录
  187 + tasks.value = []
  188 + // 移除已取消的审批
  189 + data.forEach((task) => {
  190 + if (task.result !== 4) {
  191 + tasks.value.push(task)
  192 + }
  193 + })
  194 + // 排序,将未完成的排在前面,已完成的排在后面;
  195 + tasks.value.sort((a, b) => {
  196 + // 有已完成的情况,按照完成时间倒序
  197 + if (a.endTime && b.endTime) {
  198 + return b.endTime - a.endTime
  199 + } else if (a.endTime) {
  200 + return 1
  201 + } else if (b.endTime) {
  202 + return -1
  203 + // 都是未完成,按照创建时间倒序
  204 + } else {
  205 + return b.createTime - a.createTime
  206 + }
  207 + })
  208 +
  209 + // 需要审核的记录
  210 + // const userId = store.getters.userId
  211 + // this.tasks.forEach(task => {
  212 + // if (task.result !== 1) { // 只有待处理才需要
  213 + // return
  214 + // }
  215 + // if (!task.assigneeUser || task.assigneeUser.id !== userId) { // 自己不是处理人
  216 + // return
  217 + // }
  218 + // this.runningTasks.push({ ...task })
  219 + // this.auditForms.push({
  220 + // reason: ''
  221 + // })
  222 + // })
  223 + })
  224 + .finally(() => {
  225 + tasksLoad.value = false
  226 + })
92 }) 227 })
93 </script> 228 </script>
94 229
yudao-ui-admin-vue3/src/views/bpm/processInstance/detail2.vue
@@ -71,86 +71,8 @@ @@ -71,86 +71,8 @@
71 </div> 71 </div>
72 </el-col> 72 </el-col>
73 </el-card> 73 </el-card>
74 - <!-- 申请信息 -->  
75 - <el-card class="box-card" v-loading="processInstanceLoading">  
76 - <div slot="header" class="clearfix">  
77 - <span class="el-icon-document">申请信息【{{ processInstance.name }}】</span>  
78 - </div>  
79 - <el-col  
80 - v-if="  
81 - this.processInstance.processDefinition &&  
82 - this.processInstance.processDefinition.formType === 10  
83 - "  
84 - :span="16"  
85 - :offset="6"  
86 - >  
87 - <div>  
88 - <parser :key="new Date().getTime()" :form-conf="detailForm" />  
89 - </div>  
90 - </el-col>  
91 - <div  
92 - v-if="  
93 - this.processInstance.processDefinition &&  
94 - this.processInstance.processDefinition.formType === 20  
95 - "  
96 - >  
97 - <router-link  
98 - :to="  
99 - this.processInstance.processDefinition.formCustomViewPath +  
100 - '?id=' +  
101 - this.processInstance.businessKey  
102 - "  
103 - >  
104 - <el-button type="primary">点击查看</el-button>  
105 - </router-link>  
106 - </div>  
107 - </el-card>  
108 74
109 - <!-- 审批记录 -->  
110 - <el-card class="box-card" v-loading="tasksLoad">  
111 - <div slot="header" class="clearfix">  
112 - <span class="el-icon-picture-outline">审批记录</span>  
113 - </div>  
114 - <el-col :span="16" :offset="4">  
115 - <div class="block">  
116 - <el-timeline>  
117 - <el-timeline-item  
118 - v-for="(item, index) in tasks"  
119 - :key="index"  
120 - :icon="getTimelineItemIcon(item)"  
121 - :type="getTimelineItemType(item)"  
122 - >  
123 - <p style="font-weight: 700">任务:{{ item.name }}</p>  
124 - <el-card :body-style="{ padding: '10px' }">  
125 - <label v-if="item.assigneeUser" style="font-weight: normal; margin-right: 30px">  
126 - 审批人:{{ item.assigneeUser.nickname }}  
127 - <el-tag type="info" size="mini">{{ item.assigneeUser.deptName }}</el-tag>  
128 - </label>  
129 - <label style="font-weight: normal" v-if="item.createTime">创建时间:</label>  
130 - <label style="color: #8a909c; font-weight: normal">{{  
131 - parseTime(item.createTime)  
132 - }}</label>  
133 - <label v-if="item.endTime" style="margin-left: 30px; font-weight: normal"  
134 - >审批时间:</label  
135 - >  
136 - <label v-if="item.endTime" style="color: #8a909c; font-weight: normal">  
137 - {{ parseTime(item.endTime) }}</label  
138 - >  
139 - <label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal"  
140 - >耗时:</label  
141 - >  
142 - <label v-if="item.durationInMillis" style="color: #8a909c; font-weight: normal">  
143 - {{ getDateStar(item.durationInMillis) }}  
144 - </label>  
145 - <p v-if="item.reason">  
146 - <el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag>  
147 - </p>  
148 - </el-card>  
149 - </el-timeline-item>  
150 - </el-timeline>  
151 - </div>  
152 - </el-col>  
153 - </el-card> 75 +
154 76
155 <!-- 高亮流程图 --> 77 <!-- 高亮流程图 -->
156 <el-card class="box-card" v-loading="processInstanceLoading"> 78 <el-card class="box-card" v-loading="processInstanceLoading">
@@ -209,21 +131,9 @@ import { getActivityList } from &quot;@/api/bpm/activity&quot; @@ -209,21 +131,9 @@ import { getActivityList } from &quot;@/api/bpm/activity&quot;
209 // 流程实例的详情页,可用于审批 131 // 流程实例的详情页,可用于审批
210 export default { 132 export default {
211 name: "ProcessInstanceDetail", 133 name: "ProcessInstanceDetail",
212 - components: {  
213 - Parser  
214 - },  
215 data () { 134 data () {
216 return { 135 return {
217 136
218 - // 流程实例  
219 - id: undefined, // 流程实例的编号  
220 - processInstance: {},  
221 -  
222 - // 流程表单详情  
223 - detailForm: {  
224 - fields: []  
225 - },  
226 -  
227 // BPMN 数据 137 // BPMN 数据
228 bpmnXML: null, 138 bpmnXML: null,
229 bpmnControlForm: { 139 bpmnControlForm: {
@@ -231,17 +141,6 @@ export default { @@ -231,17 +141,6 @@ export default {
231 }, 141 },
232 activityList: [], 142 activityList: [],
233 143
234 - // 审批记录  
235 - tasksLoad: true,  
236 - tasks: [],  
237 -  
238 - // 审批表单  
239 - runningTasks: [],  
240 - auditForms: [],  
241 - auditRule: {  
242 - reason: [{ required: true, message: "审批建议不能为空", trigger: "blur" }],  
243 - },  
244 -  
245 // 转派审批人 144 // 转派审批人
246 userOptions: [], 145 userOptions: [],
247 updateAssignee: { 146 updateAssignee: {
@@ -278,142 +177,23 @@ export default { @@ -278,142 +177,23 @@ export default {
278 // 获得流程实例相关 177 // 获得流程实例相关
279 this.processInstanceLoading = true 178 this.processInstanceLoading = true
280 getProcessInstanceApi(this.id).then(response => { 179 getProcessInstanceApi(this.id).then(response => {
281 - if (!response.data) {  
282 - this.$message.error('查询不到流程信息!')  
283 - return  
284 - }  
285 - // 设置流程信息  
286 - this.processInstance = response.data  
287 -  
288 - // 设置表单信息  
289 - if (this.processInstance.processDefinition.formType === 10) {  
290 - this.detailForm = {  
291 - ...JSON.parse(this.processInstance.processDefinition.formConf),  
292 - disabled: true, // 表单禁用  
293 - formBtns: false, // 按钮隐藏  
294 - fields: decodeFields(this.processInstance.processDefinition.formFields)  
295 - }  
296 - // 设置表单的值  
297 - this.detailForm.fields.forEach(item => {  
298 - const val = this.processInstance.formVariables[item.__vModel__]  
299 - if (val) {  
300 - item.__config__.defaultValue = val  
301 - }  
302 - })  
303 - }  
304 -  
305 // 加载流程图 180 // 加载流程图
306 getProcessDefinitionBpmnXML(this.processInstance.processDefinition.id).then(response => { 181 getProcessDefinitionBpmnXML(this.processInstance.processDefinition.id).then(response => {
307 this.bpmnXML = response.data 182 this.bpmnXML = response.data
308 }) 183 })
  184 +
309 // 加载活动列表 185 // 加载活动列表
310 getActivityList({ 186 getActivityList({
311 processInstanceId: this.processInstance.id 187 processInstanceId: this.processInstance.id
312 }).then(response => { 188 }).then(response => {
313 this.activityList = response.data 189 this.activityList = response.data
314 }) 190 })
315 -  
316 - // 取消加载中  
317 - this.processInstanceLoading = false  
318 - })  
319 -  
320 - // 获得流程任务列表(审批记录)  
321 - this.tasksLoad = true  
322 - this.runningTasks = []  
323 - this.auditForms = []  
324 - getTaskListByProcessInstanceId(this.id).then(response => {  
325 - // 审批记录  
326 - this.tasks = []  
327 - // 移除已取消的审批  
328 - response.data.forEach(task => {  
329 - if (task.result !== 4) {  
330 - this.tasks.push(task)  
331 - }  
332 - })  
333 - // 排序,将未完成的排在前面,已完成的排在后面;  
334 - this.tasks.sort((a, b) => {  
335 - // 有已完成的情况,按照完成时间倒序  
336 - if (a.endTime && b.endTime) {  
337 - return b.endTime - a.endTime  
338 - } else if (a.endTime) {  
339 - return 1  
340 - } else if (b.endTime) {  
341 - return -1  
342 - // 都是未完成,按照创建时间倒序  
343 - } else {  
344 - return b.createTime - a.createTime  
345 - }  
346 - })  
347 -  
348 - // 需要审核的记录  
349 - const userId = store.getters.userId  
350 - this.tasks.forEach(task => {  
351 - if (task.result !== 1) { // 只有待处理才需要  
352 - return  
353 - }  
354 - if (!task.assigneeUser || task.assigneeUser.id !== userId) { // 自己不是处理人  
355 - return  
356 - }  
357 - this.runningTasks.push({ ...task })  
358 - this.auditForms.push({  
359 - reason: ''  
360 - })  
361 - })  
362 -  
363 - // 取消加载中  
364 - this.tasksLoad = false  
365 }) 191 })
366 }, 192 },
367 - /** 处理选择流程的按钮操作 **/  
368 - handleSelect (row) {  
369 - // 设置选择的流程  
370 - this.selectProcessInstance = row  
371 -  
372 - // 流程表单  
373 - if (row.formId) {  
374 - // 设置对应的表单  
375 - this.detailForm = {  
376 - ...JSON.parse(row.formConf),  
377 - fields: decodeFields(row.formFields)  
378 - }  
379 - } else if (row.formCustomCreatePath) {  
380 - this.$router.push({ path: row.formCustomCreatePath })  
381 - // 这里暂时无需加载流程图,因为跳出到另外个 Tab;  
382 - }  
383 - },  
384 getDateStar (ms) { 193 getDateStar (ms) {
385 return getDate(ms) 194 return getDate(ms)
386 }, 195 },
387 - getTimelineItemIcon (item) {  
388 - if (item.result === 1) {  
389 - return 'el-icon-time'  
390 - }  
391 - if (item.result === 2) {  
392 - return 'el-icon-check'  
393 - }  
394 - if (item.result === 3) {  
395 - return 'el-icon-close'  
396 - }  
397 - if (item.result === 4) {  
398 - return 'el-icon-remove-outline'  
399 - }  
400 - return ''  
401 - },  
402 - getTimelineItemType (item) {  
403 - if (item.result === 1) {  
404 - return 'primary'  
405 - }  
406 - if (item.result === 2) {  
407 - return 'success'  
408 - }  
409 - if (item.result === 3) {  
410 - return 'danger'  
411 - }  
412 - if (item.result === 4) {  
413 - return 'info'  
414 - }  
415 - return ''  
416 - }, 196 +
417 /** 处理审批通过和不通过的操作 */ 197 /** 处理审批通过和不通过的操作 */
418 handleAudit (task, pass) { 198 handleAudit (task, pass) {
419 const index = this.runningTasks.indexOf(task) 199 const index = this.runningTasks.indexOf(task)
yudao-ui-admin/src/views/bpm/processInstance/detail.vue
@@ -275,23 +275,6 @@ export default { @@ -275,23 +275,6 @@ export default {
275 this.tasksLoad = false; 275 this.tasksLoad = false;
276 }); 276 });
277 }, 277 },
278 - /** 处理选择流程的按钮操作 **/  
279 - handleSelect(row) {  
280 - // 设置选择的流程  
281 - this.selectProcessInstance = row;  
282 -  
283 - // 流程表单  
284 - if (row.formId) {  
285 - // 设置对应的表单  
286 - this.detailForm = {  
287 - ...JSON.parse(row.formConf),  
288 - fields: decodeFields(row.formFields)  
289 - }  
290 - } else if (row.formCustomCreatePath) {  
291 - this.$router.push({ path: row.formCustomCreatePath});  
292 - // 这里暂时无需加载流程图,因为跳出到另外个 Tab;  
293 - }  
294 - },  
295 getDateStar(ms) { 278 getDateStar(ms) {
296 return getDate(ms); 279 return getDate(ms);
297 }, 280 },