From 3e359e4550d018035406a97c190b98f83839ba7f Mon Sep 17 00:00:00 2001 From: houzhongjian <houzhongyi@126.com> Date: 星期五, 13 十二月 2024 10:41:05 +0800 Subject: [PATCH] 1、工作流重做更新 2、@import修改为@use以应对sass升级弃用@import 3、进入系统后自动redirect到home页 --- src/views/bpm/processInstance/detail/index.vue | 591 +++++++++++++++++++++++++++------------------------------- 1 files changed, 278 insertions(+), 313 deletions(-) diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index da54769..0461a20 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -1,174 +1,167 @@ <template> - <ContentWrap> - <!-- 审批信息 --> - <el-card - v-for="(item, index) in runningTasks" - :key="index" - v-loading="processInstanceLoading" - class="box-card" - > - <template #header> - <span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span> - </template> - <el-col :offset="6" :span="16"> - <el-form - :ref="'form' + index" - :model="auditForms[index]" - :rules="auditRule" - label-width="100px" - > - <el-form-item v-if="processInstance && processInstance.name" label="流程名"> - {{ processInstance.name }} - </el-form-item> - <el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人"> - {{ processInstance?.startUser.nickname }} - <el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag> - </el-form-item> - <el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px"> - <template #header> - <span class="el-icon-picture-outline"> - 填写表单【{{ runningTasks[index]?.formName }}】 - </span> - </template> - <form-create - v-model="approveForms[index].value" - v-model:api="approveFormFApis[index]" - :option="approveForms[index].option" - :rule="approveForms[index].rule" - /> - </el-card> - <el-form-item label="审批建议" prop="reason"> - <el-input - v-model="auditForms[index].reason" - placeholder="请输入审批建议" - type="textarea" - /> - </el-form-item> - <el-form-item label="抄送人" prop="copyUserIds"> - <el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人"> - <el-option - v-for="item in userOptions" - :key="item.id" - :label="item.nickname" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-form> - <div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px"> - <el-button type="success" @click="handleAudit(item, true)"> - <Icon icon="ep:select" /> - 通过 - </el-button> - <el-button type="danger" @click="handleAudit(item, false)"> - <Icon icon="ep:close" /> - 不通过 - </el-button> - <el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)"> - <Icon icon="ep:edit" /> - 转办 - </el-button> - <el-button type="primary" @click="handleDelegate(item)"> - <Icon icon="ep:position" /> - 委派 - </el-button> - <el-button type="primary" @click="handleSign(item)"> - <Icon icon="ep:plus" /> - 加签 - </el-button> - <el-button type="warning" @click="handleBack(item)"> - <Icon icon="ep:back" /> - 回退 - </el-button> - </div> - </el-col> - </el-card> - - <!-- 申请信息 --> - <el-card v-loading="processInstanceLoading" class="box-card"> - <template #header> - <span class="el-icon-document">申请信息【{{ processInstance.name }}】</span> - </template> - <!-- 情况一:流程表单 --> - <el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16"> - <form-create - v-model="detailForm.value" - v-model:api="fApi" - :option="detailForm.option" - :rule="detailForm.rule" + <ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative"> + <div class="processInstance-wrap-main"> + <el-scrollbar> + <img + class="position-absolute right-20px" + width="150" + :src="auditIconsMap[processInstance.status]" + alt="" /> - </el-col> - <!-- 情况二:业务表单 --> - <div v-if="processInstance?.processDefinition?.formType === 20"> - <BusinessFormComponent :id="processInstance.businessKey" /> - </div> - </el-card> + <div class="text-#878c93 h-15px">编号:{{ id }}</div> + <el-divider class="!my-8px" /> + <div class="flex items-center gap-5 mb-10px h-40px"> + <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div> + <dict-tag + v-if="processInstance.status" + :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" + :value="processInstance.status" + /> + </div> - <!-- 审批记录 --> - <ProcessInstanceTaskList - :loading="tasksLoad" - :process-instance="processInstance" - :tasks="tasks" - @refresh="getTaskList" - /> + <div class="flex items-center gap-5 mb-10px text-13px h-35px"> + <div + class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600" + > + <el-avatar + :size="28" + v-if="processInstance?.startUser?.avatar" + :src="processInstance?.startUser?.avatar" + /> + <el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname"> + {{ processInstance?.startUser?.nickname.substring(0, 1) }} + </el-avatar> + {{ processInstance?.startUser?.nickname }} + </div> + <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div> + </div> - <!-- 高亮流程图 --> - <ProcessInstanceBpmnViewer - :id="`${id}`" - :bpmn-xml="bpmnXml" - :loading="processInstanceLoading" - :process-instance="processInstance" - :tasks="tasks" - /> + <el-tabs v-model="activeTab"> + <!-- 表单信息 --> + <el-tab-pane label="审批详情" name="form"> + <div class="form-scroll-area"> + <el-scrollbar> + <el-row> + <el-col :span="17" class="!flex !flex-col formCol"> + <!-- 表单信息 --> + <div + v-loading="processInstanceLoading" + class="form-box flex flex-col mb-30px flex-1" + > + <!-- 情况一:流程表单 --> + <el-col v-if="processDefinition?.formType === 10"> + <form-create + v-model="detailForm.value" + v-model:api="fApi" + :option="detailForm.option" + :rule="detailForm.rule" + /> + </el-col> + <!-- 情况二:业务表单 --> + <div v-if="processDefinition?.formType === 20"> + <BusinessFormComponent :id="processInstance.businessKey" /> + </div> + </div> + </el-col> + <el-col :span="7"> + <!-- 审批记录时间线 --> + <ProcessInstanceTimeline :activity-nodes="activityNodes" /> + </el-col> + </el-row> + </el-scrollbar> + </div> + </el-tab-pane> - <!-- 弹窗:转派审批人 --> - <TaskTransferForm ref="taskTransferFormRef" @success="getDetail" /> - <!-- 弹窗:回退节点 --> - <TaskReturnForm ref="taskReturnFormRef" @success="getDetail" /> - <!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中--> - <TaskDelegateForm ref="taskDelegateForm" @success="getDetail" /> - <!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 --> - <TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" /> + <!-- 流程图 --> + <el-tab-pane label="流程图" name="diagram"> + <div class="form-scroll-area"> + <ProcessInstanceSimpleViewer + v-show=" + processDefinition.modelType && processDefinition.modelType === BpmModelType.SIMPLE + " + :loading="processInstanceLoading" + :model-view="processModelView" + /> + <ProcessInstanceBpmnViewer + v-show=" + processDefinition.modelType && processDefinition.modelType === BpmModelType.BPMN + " + :loading="processInstanceLoading" + :model-view="processModelView" + /> + </div> + </el-tab-pane> + + <!-- 流转记录 --> + <el-tab-pane label="流转记录" name="record"> + <div class="form-scroll-area"> + <el-scrollbar> + <ProcessInstanceTaskList :loading="processInstanceLoading" :id="id" /> + </el-scrollbar> + </div> + </el-tab-pane> + + <!-- 流转评论 TODO 待开发 --> + <el-tab-pane label="流转评论" name="comment" v-if="false"> + <div class="form-scroll-area"> + <el-scrollbar> 流转评论 </el-scrollbar> + </div> + </el-tab-pane> + </el-tabs> + + <div class="b-t-solid border-t-1px border-[var(--el-border-color)]"> + <!-- 操作栏按钮 --> + <ProcessInstanceOperationButton + ref="operationButtonRef" + :process-instance="processInstance" + :process-definition="processDefinition" + :userOptions="userOptions" + @success="refresh" + /> + </div> + </el-scrollbar> + </div> </ContentWrap> </template> <script lang="ts" setup> -import { useUserStore } from '@/store/modules/user' +import { formatDate } from '@/utils/formatTime' +import { DICT_TYPE } from '@/utils/dict' +import { BpmModelType } from '@/utils/constants' import { setConfAndFields2 } from '@/utils/formCreate' -import type { ApiAttrs } from '@form-create/element-ui/types/config' -import * as DefinitionApi from '@/api/bpm/definition' -import * as ProcessInstanceApi from '@/api/bpm/processInstance' -import * as TaskApi from '@/api/bpm/task' -import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' -import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue' -import TaskReturnForm from './dialog/TaskReturnForm.vue' -import TaskDelegateForm from './dialog/TaskDelegateForm.vue' -import TaskTransferForm from './dialog/TaskTransferForm.vue' -import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue' import { registerComponent } from '@/utils/routerHelper' -import { isEmpty } from '@/utils/is' +import type { ApiAttrs } from '@form-create/element-ui/types/config' +import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as UserApi from '@/api/system/user' +import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' +import ProcessInstanceSimpleViewer from './ProcessInstanceSimpleViewer.vue' +import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue' +import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue' +import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue' +import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts' +import { TaskStatusEnum } from '@/api/bpm/task' +import runningSvg from '@/assets/svgs/bpm/running.svg' +import approveSvg from '@/assets/svgs/bpm/approve.svg' +import rejectSvg from '@/assets/svgs/bpm/reject.svg' +import cancelSvg from '@/assets/svgs/bpm/cancel.svg' defineOptions({ name: 'BpmProcessInstanceDetail' }) - -const { query } = useRoute() // 查询参数 +const props = defineProps<{ + id: string // 流程实例的编号 + taskId?: string // 任务编号 + activityId?: string //流程活动编号,用于抄送查看 +}>() const message = useMessage() // 消息弹窗 -const { proxy } = getCurrentInstance() as any - -const userId = useUserStore().getUser.id // 当前登录的编号 -const id = query.id as unknown as string // 流程实例的编号 const processInstanceLoading = ref(false) // 流程实例的加载中 const processInstance = ref<any>({}) // 流程实例 -const bpmnXml = ref('') // BPMN XML -const tasksLoad = ref(true) // 任务的加载中 -const tasks = ref<any[]>([]) // 任务列表 -// ========== 审批信息 ========== -const runningTasks = ref<any[]>([]) // 运行中的任务 -const auditForms = ref<any[]>([]) // 审批任务的表单 -const auditRule = reactive({ - reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }] -}) -const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息 -const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi +const processDefinition = ref<any>({}) // 流程定义 +const processModelView = ref<any>({}) // 流程模型视图 +const operationButtonRef = ref() // 操作按钮组件 ref +const auditIconsMap = { + [TaskStatusEnum.RUNNING]: runningSvg, + [TaskStatusEnum.APPROVE]: approveSvg, + [TaskStatusEnum.REJECT]: rejectSvg, + [TaskStatusEnum.CANCEL]: cancelSvg +} // ========== 申请信息 ========== const fApi = ref<ApiAttrs>() // @@ -178,198 +171,123 @@ value: {} }) // 流程实例的表单详情 -/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */ -watch( - () => approveFormFApis.value, - (value) => { - value?.forEach((api) => { - api.btn.show(false) - api.resetBtn.show(false) - }) - }, - { - deep: true - } -) - -/** 处理审批通过和不通过的操作 */ -const handleAudit = async (task, pass) => { - // 1.1 获得对应表单 - const index = runningTasks.value.indexOf(task) - const auditFormRef = proxy.$refs['form' + index][0] - // 1.2 校验表单 - const elForm = unref(auditFormRef) - if (!elForm) return - const valid = await elForm.validate() - if (!valid) return - - // 2.1 提交审批 - const data = { - id: task.id, - reason: auditForms.value[index].reason, - copyUserIds: auditForms.value[index].copyUserIds - } - if (pass) { - // 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交 - const formCreateApi = approveFormFApis.value[index] - if (formCreateApi) { - await formCreateApi.validate() - data.variables = approveForms.value[index].value - } - await TaskApi.approveTask(data) - message.success('审批通过成功') - } else { - await TaskApi.rejectTask(data) - message.success('审批不通过成功') - } - // 2.2 加载最新数据 - getDetail() -} - -/** 转派审批人 */ -const taskTransferFormRef = ref() -const openTaskUpdateAssigneeForm = (id: string) => { - taskTransferFormRef.value.open(id) -} - -/** 处理审批退回的操作 */ -const taskDelegateForm = ref() -const handleDelegate = async (task) => { - taskDelegateForm.value.open(task.id) -} - -/** 处理审批退回的操作 */ -const taskReturnFormRef = ref() -const handleBack = async (task: any) => { - taskReturnFormRef.value.open(task.id) -} - -/** 处理审批加签的操作 */ -const taskSignCreateFormRef = ref() -const handleSign = async (task: any) => { - taskSignCreateFormRef.value.open(task.id) -} - /** 获得详情 */ const getDetail = () => { - // 1. 获得流程实例相关 - getProcessInstance() - // 2. 获得流程任务列表(审批记录) - getTaskList() + getApprovalDetail() + + getProcessModelView() } /** 加载流程实例 */ -const BusinessFormComponent = ref(null) // 异步组件 -const getProcessInstance = async () => { +const BusinessFormComponent = ref<any>(null) // 异步组件 +/** 获取审批详情 */ +const getApprovalDetail = async () => { + processInstanceLoading.value = true try { - processInstanceLoading.value = true - const data = await ProcessInstanceApi.getProcessInstance(id) + const param = { + processInstanceId: props.id, + activityId: props.activityId, + taskId: props.taskId + } + const data = await ProcessInstanceApi.getApprovalDetail(param) if (!data) { + message.error('查询不到审批详情信息!') + return + } + if (!data.processDefinition || !data.processInstance) { message.error('查询不到流程信息!') return } - processInstance.value = data + processInstance.value = data.processInstance + processDefinition.value = data.processDefinition // 设置表单信息 - const processDefinition = data.processDefinition - if (processDefinition.formType === 10) { - setConfAndFields2( - detailForm, - processDefinition.formConf, - processDefinition.formFields, - data.formVariables - ) + if (processDefinition.value.formType === 10) { + // 获取表单字段权限 + const formFieldsPermission = data.formFieldsPermission + + if (detailForm.value.rule.length > 0) { + // 避免刷新 form-create 显示不了 + detailForm.value.value = processInstance.value.formVariables + } else { + setConfAndFields2( + detailForm, + processDefinition.value.formConf, + processDefinition.value.formFields, + processInstance.value.formVariables + ) + } nextTick().then(() => { fApi.value?.btn.show(false) fApi.value?.resetBtn.show(false) + //@ts-ignore fApi.value?.disabled(true) + // 设置表单字段权限 + if (formFieldsPermission) { + Object.keys(data.formFieldsPermission).forEach((item) => { + setFieldPermission(item, formFieldsPermission[item]) + }) + } }) } else { // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath) } - // 加载流程图 - bpmnXml.value = ( - await DefinitionApi.getProcessDefinition(processDefinition.id as number) - )?.bpmnXml + // 获取审批节点,显示 Timeline 的数据 + activityNodes.value = data.activityNodes + + // 获取待办任务显示操作按钮 + operationButtonRef.value?.loadTodoTask(data.todoTask) } finally { processInstanceLoading.value = false } } -/** 加载任务列表 */ -const getTaskList = async () => { - runningTasks.value = [] - auditForms.value = [] - approveForms.value = [] - approveFormFApis.value = [] - try { - // 获得未取消的任务 - tasksLoad.value = true - const data = await TaskApi.getTaskListByProcessInstanceId(id) - tasks.value = [] - // 1.1 移除已取消的审批 - data.forEach((task) => { - if (task.status !== 4) { - tasks.value.push(task) - } - }) - // 1.2 排序,将未完成的排在前面,已完成的排在后面; - tasks.value.sort((a, b) => { - // 有已完成的情况,按照完成时间倒序 - if (a.endTime && b.endTime) { - return b.endTime - a.endTime - } else if (a.endTime) { - return 1 - } else if (b.endTime) { - return -1 - // 都是未完成,按照创建时间倒序 - } else { - return b.createTime - a.createTime - } - }) +/** 获取流程模型视图*/ +const getProcessModelView = async () => { + if (BpmModelType.BPMN === processDefinition.value?.modelType) { + // 重置,解决 BPMN 流程图刷新不会重新渲染问题 + processModelView.value = { + bpmnXml: '' + } + } + const data = await ProcessInstanceApi.getProcessInstanceBpmnModelView(props.id) + if (data) { + processModelView.value = data + } +} - // 获得需要自己审批的任务 - loadRunningTask(tasks.value) - } finally { - tasksLoad.value = false +// 审批节点信息 +const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) +/** + * 设置表单权限 + */ +const setFieldPermission = (field: string, permission: string) => { + if (permission === FieldPermissionType.READ) { + //@ts-ignore + fApi.value?.disabled(true, field) + } + if (permission === FieldPermissionType.WRITE) { + //@ts-ignore + fApi.value?.disabled(false, field) + } + if (permission === FieldPermissionType.NONE) { + //@ts-ignore + fApi.value?.hidden(true, field) } } /** - * 设置 runningTasks 中的任务 + * 操作成功后刷新 */ -const loadRunningTask = (tasks) => { - tasks.forEach((task) => { - if (!isEmpty(task.children)) { - loadRunningTask(task.children) - } - // 2.1 只有待处理才需要 - if (task.status !== 1 && task.status !== 6) { - return - } - // 2.2 自己不是处理人 - if (!task.assigneeUser || task.assigneeUser.id !== userId) { - return - } - // 2.3 添加到处理任务 - runningTasks.value.push({ ...task }) - auditForms.value.push({ - reason: '', - copyUserIds: [] - }) - - // 2.4 处理 approve 表单 - if (task.formId && task.formConf) { - const approveForm = {} - setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariables) - approveForms.value.push(approveForm) - } else { - approveForms.value.push({}) // 占位,避免为空 - } - }) +const refresh = () => { + // 重新获取详情 + getDetail() } + +/** 当前的Tab */ +const activeTab = ref('form') /** 初始化 */ const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 @@ -379,3 +297,50 @@ userOptions.value = await UserApi.getSimpleUserList() }) </script> + +<style lang="scss" scoped> +$wrap-padding-height: 20px; +$wrap-margin-height: 15px; +$button-height: 51px; +$process-header-height: 194px; + +.processInstance-wrap-main { + height: calc( + 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px + ); + max-height: calc( + 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px + ); + overflow: auto; + + .form-scroll-area { + height: calc( + 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px - + $process-header-height - 40px + ); + max-height: calc( + 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px - + $process-header-height - 40px + ); + overflow: auto; + display: flex; + flex-direction: column; + + :deep(.box-card) { + height: 100%; + flex: 1; + + .el-card__body { + height: 100%; + padding: 0; + } + } + } +} + +.form-box { + :deep(.el-card) { + border: none; + } +} +</style> -- Gitblit v1.9.3