Commit 0aa72b320033625d6db0be60168fa8e9a4b1bd34
1 parent
2774671a
bpm:流程详情的时间轴
Showing
4 changed files
with
167 additions
and
242 deletions
yudao-ui-admin-vue3/src/utils/formatTime.ts
| ... | ... | @@ -147,3 +147,30 @@ export function formatAxis(param: Date): string { |
| 147 | 147 | else if (hour < 22) return '晚上好' |
| 148 | 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 | 25 | processInstance.businessKey |
| 26 | 26 | " |
| 27 | 27 | > |
| 28 | - <el-button type="primary">点击查看</el-button> | |
| 28 | + <XButton type="primary" preIcon="ep:view" title="点击查看" /> | |
| 29 | 29 | </router-link> |
| 30 | 30 | </div> |
| 31 | 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 | 78 | </ContentWrap> |
| 33 | 79 | </template> |
| 34 | 80 | <script setup lang="ts"> |
| 81 | +import dayjs from 'dayjs' | |
| 35 | 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 | 86 | const { query } = useRoute() // 查询参数 |
| 38 | 87 | const message = useMessage() // 消息弹窗 |
| ... | ... | @@ -41,6 +90,11 @@ const message = useMessage() // 消息弹窗 |
| 41 | 90 | const id = query.id as unknown as number |
| 42 | 91 | const processInstanceLoading = ref(false) // 流程实例的加载中 |
| 43 | 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 | 100 | import { setConfAndFields2 } from '@/utils/formCreate' |
| ... | ... | @@ -53,6 +107,41 @@ const detailForm = ref({ |
| 53 | 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 | 146 | onMounted(() => { |
| 58 | 147 | // 1. 获得流程实例相关 |
| ... | ... | @@ -88,7 +177,53 @@ onMounted(() => { |
| 88 | 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 | 228 | </script> |
| 94 | 229 | ... | ... |
yudao-ui-admin-vue3/src/views/bpm/processInstance/detail2.vue
| ... | ... | @@ -71,86 +71,8 @@ |
| 71 | 71 | </div> |
| 72 | 72 | </el-col> |
| 73 | 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 | 78 | <el-card class="box-card" v-loading="processInstanceLoading"> |
| ... | ... | @@ -209,21 +131,9 @@ import { getActivityList } from "@/api/bpm/activity" |
| 209 | 131 | // 流程实例的详情页,可用于审批 |
| 210 | 132 | export default { |
| 211 | 133 | name: "ProcessInstanceDetail", |
| 212 | - components: { | |
| 213 | - Parser | |
| 214 | - }, | |
| 215 | 134 | data () { |
| 216 | 135 | return { |
| 217 | 136 | |
| 218 | - // 流程实例 | |
| 219 | - id: undefined, // 流程实例的编号 | |
| 220 | - processInstance: {}, | |
| 221 | - | |
| 222 | - // 流程表单详情 | |
| 223 | - detailForm: { | |
| 224 | - fields: [] | |
| 225 | - }, | |
| 226 | - | |
| 227 | 137 | // BPMN 数据 |
| 228 | 138 | bpmnXML: null, |
| 229 | 139 | bpmnControlForm: { |
| ... | ... | @@ -231,17 +141,6 @@ export default { |
| 231 | 141 | }, |
| 232 | 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 | 145 | userOptions: [], |
| 247 | 146 | updateAssignee: { |
| ... | ... | @@ -278,142 +177,23 @@ export default { |
| 278 | 177 | // 获得流程实例相关 |
| 279 | 178 | this.processInstanceLoading = true |
| 280 | 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 | 181 | getProcessDefinitionBpmnXML(this.processInstance.processDefinition.id).then(response => { |
| 307 | 182 | this.bpmnXML = response.data |
| 308 | 183 | }) |
| 184 | + | |
| 309 | 185 | // 加载活动列表 |
| 310 | 186 | getActivityList({ |
| 311 | 187 | processInstanceId: this.processInstance.id |
| 312 | 188 | }).then(response => { |
| 313 | 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 | 193 | getDateStar (ms) { |
| 385 | 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 | 198 | handleAudit (task, pass) { |
| 419 | 199 | const index = this.runningTasks.indexOf(task) | ... | ... |
yudao-ui-admin/src/views/bpm/processInstance/detail.vue
| ... | ... | @@ -275,23 +275,6 @@ export default { |
| 275 | 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 | 278 | getDateStar(ms) { |
| 296 | 279 | return getDate(ms); |
| 297 | 280 | }, | ... | ... |