diff --git a/src/api/bpm/task/index.ts b/src/api/bpm/task/index.ts index ccd5c4ee..f1359194 100644 --- a/src/api/bpm/task/index.ts +++ b/src/api/bpm/task/index.ts @@ -58,3 +58,24 @@ export const returnTask = async (data) => { export const delegateTask = async (data) => { return await request.put({ url: '/bpm/task/delegate', data }) } + +/** + * 加签 + */ +export const taskAddSign = async (data) => { + return await request.put({ url: '/bpm/task/add-sign', data }) +} + +/** + * 获取减签任务列表 + */ +export const getChildrenTaskList = async (id: string) => { + return await request.get({ url: '/bpm/task/get-children-task-list?taskId=' + id }) +} + +/** + * 减签 + */ +export const taskSubSign = async (data) => { + return await request.put({ url: '/bpm/task/sub-sign', data }) +} diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue index efb57503..a7958adb 100644 --- a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue +++ b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue @@ -250,6 +250,12 @@ const getResultCss = (result) => { } else if (result === 5) { // 退回 return 'highlight-return' + } else if (result === 6) { + // 委派 + return 'highlight-return' + } else if (result === 7 || result === 8 || result === 9) { + // 待后加签任务完成/待前加签任务完成/待前置任务完成 + return 'highlight-return' } return '' } @@ -362,7 +368,7 @@ const elementHover = (element) => { } } console.log(html, 'html111111111111111') - elementOverlayIds.value[element.value.id] = toRaw(overlays.value).add(element.value, { + elementOverlayIds.value[element.value.id] = toRaw(overlays.value)?.add(element.value, { position: { left: 0, bottom: 0 }, html: `<div class="element-overlays">${html}</div>` }) diff --git a/src/utils/is.ts b/src/utils/is.ts index 8ac2e50d..eec86a93 100644 --- a/src/utils/is.ts +++ b/src/utils/is.ts @@ -19,6 +19,9 @@ export const isObject = (val: any): val is Record<any, any> => { } export const isEmpty = <T = unknown>(val: T): val is T => { + if (val === null) { + return true + } if (isArray(val) || isString(val)) { return val.length === 0 } diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceChildrenTaskList.vue b/src/views/bpm/processInstance/detail/ProcessInstanceChildrenTaskList.vue new file mode 100644 index 00000000..f162d1fb --- /dev/null +++ b/src/views/bpm/processInstance/detail/ProcessInstanceChildrenTaskList.vue @@ -0,0 +1,99 @@ +<template> + <el-drawer v-model="drawerVisible" title="子任务" size="70%"> + <template #header> + <h4>【{{ baseTask.name }} 】审批人:{{ baseTask.assigneeUser?.nickname }}</h4> + <el-button style="margin-left: 5px" v-if="showSubSignButton(baseTask)" type="danger" plain @click="handleSubSign(baseTask)"> + <Icon icon="ep:remove" /> + 减签 + </el-button> + </template> + <el-table :data="tableData" style="width: 100%" row-key="id" border> + <el-table-column prop="assigneeUser.nickname" label="审批人" /> + <el-table-column prop="assigneeUser.deptName" label="所在部门" /> + <el-table-column label="审批状态" prop="result"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" /> + </template> + </el-table-column> + <el-table-column + label="提交时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column + label="结束时间" + align="center" + prop="endTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" prop="operation"> + <template #default="scope"> + <el-button + v-if="showSubSignButton(scope.row)" + type="danger" + plain + @click="handleSubSign(scope.row)" + > + <Icon icon="ep:remove" /> + 减签 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 减签 --> + <TaskSubSignDialogForm ref="taskSubSignDialogForm" /> + </el-drawer> +</template> +<script lang="ts" setup> +import { isEmpty } from '@/utils/is' +import { DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import TaskSubSignDialogForm from './TaskSubSignDialogForm.vue' + +const message = useMessage() // 消息弹窗 +defineOptions({ name: 'ProcessInstancechildrenList' }) + +const drawerVisible = ref(false) // 抽屉的是否展示 + +const tableData = ref<any[]>([]) //表格数据 +const baseTask = ref<object>({}) +/** 打开弹窗 */ +const open = async (task: any) => { + if (isEmpty(task.children)) { + message.warning('该任务没有子任务') + return + } + baseTask.value = task + //设置表格数据 + tableData.value = task.children + //展开抽屉 + drawerVisible.value = true +} +defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗 + +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 + +/** + * 减签 + */ +const taskSubSignDialogForm = ref() +const handleSubSign = (item) => { + taskSubSignDialogForm.value.open(item.id) +} + +/** + * 显示减签按钮 + * @param task + */ +const showSubSignButton = (task:any) => { + if(!isEmpty(task.children)){ + //有子任务,且子任务有任意一个是 待处理 和 待前置任务完成 则显示减签按钮 + const subTask = task.children.find((item) => item.result === 1 || item.result === 9) + return !isEmpty(subTask) + } + return false +} +</script> diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue index 6f4557ae..97287e99 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue @@ -12,7 +12,18 @@ :icon="getTimelineItemIcon(item)" :type="getTimelineItemType(item)" > - <p style="font-weight: 700">任务:{{ item.name }}</p> + <p style="font-weight: 700"> + 任务:{{ item.name }} + <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="item.result" /> + <el-button + style="margin-left: 5px" + v-if="!isEmpty(item.children)" + @click="openChildrenTask(item)" + > + <Icon icon="ep:memo" /> + 子任务 + </el-button> + </p> <el-card :body-style="{ padding: '10px' }"> <label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal"> 审批人:{{ item.assigneeUser.nickname }} @@ -42,11 +53,16 @@ </el-timeline> </div> </el-col> + <!-- 子任务 --> + <ProcessInstanceChildrenTaskList ref="processInstanceChildrenTaskList" /> </el-card> </template> <script lang="ts" setup> import { formatDate, formatPast2 } from '@/utils/formatTime' import { propTypes } from '@/utils/propTypes' +import { DICT_TYPE } from '@/utils/dict' +import { isEmpty } from '@/utils/is' +import ProcessInstanceChildrenTaskList from './ProcessInstanceChildrenTaskList.vue' defineOptions({ name: 'BpmProcessInstanceTaskList' }) @@ -95,6 +111,18 @@ const getTimelineItemType = (item) => { if (item.result === 6) { return 'default' } + if (item.result === 7 || item.result === 8) { + return 'warning' + } return '' } + +/** + * 子任务 + */ +const processInstanceChildrenTaskList = ref() + +const openChildrenTask = (item) => { + processInstanceChildrenTaskList.value.open(item) +} </script> diff --git a/src/views/bpm/processInstance/detail/TaskAddSignDialogForm.vue b/src/views/bpm/processInstance/detail/TaskAddSignDialogForm.vue new file mode 100644 index 00000000..4b91c9b9 --- /dev/null +++ b/src/views/bpm/processInstance/detail/TaskAddSignDialogForm.vue @@ -0,0 +1,97 @@ +<template> + <Dialog v-model="dialogVisible" title="加签" width="500"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="110px" + > + <el-form-item label="加签处理人" prop="userIdList"> + <el-select v-model="formData.userIdList" multiple clearable style="width: 100%"> + <el-option + v-for="item in userList" + :key="item.id" + :label="item.nickname" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="加签理由" prop="reason"> + <el-input v-model="formData.reason" clearable placeholder="请输入加签理由" /> + </el-form-item> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm('before')" + >向前加签</el-button + > + <el-button :disabled="formLoading" type="primary" @click="submitForm('after')" + >向后加签</el-button + > + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import * as TaskApi from '@/api/bpm/task' +import * as UserApi from '@/api/system/user' + +const message = useMessage() // 消息弹窗 +defineOptions({ name: 'BpmTaskUpdateAssigneeForm' }) + +const dialogVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中 +const formData = ref({ + id: '', + userIdList: [], + type: '' +}) +const formRules = ref({ + userIdList: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }], + reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }] +}) + +const formRef = ref() // 表单 Ref +const userList = ref<any[]>([]) // 用户列表 + +/** 打开弹窗 */ +const open = async (id: string) => { + dialogVisible.value = true + resetForm() + formData.value.id = id + // 获得用户列表 + userList.value = await UserApi.getSimpleUserList() +} +defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async (type: string) => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + formData.value.type = type + try { + await TaskApi.taskAddSign(formData.value) + message.success('加签成功') + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: '', + userIdList: [], + type: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/processInstance/detail/TaskSubSignDialogForm.vue b/src/views/bpm/processInstance/detail/TaskSubSignDialogForm.vue new file mode 100644 index 00000000..61f7d68c --- /dev/null +++ b/src/views/bpm/processInstance/detail/TaskSubSignDialogForm.vue @@ -0,0 +1,85 @@ +<template> + <Dialog v-model="dialogVisible" title="减签" width="500"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="110px" + > + <el-form-item label="减签任务" prop="id"> + <el-radio-group v-model="formData.id"> + <el-radio-button v-for="item in subTaskList" :key="item.id" :label="item.id"> + {{ item.name }}({{ item.assigneeUser.deptName }}{{ item.assigneeUser.nickname }}--审批) + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item label="减签理由" prop="reason"> + <el-input v-model="formData.reason" clearable placeholder="请输入减签理由" /> + </el-form-item> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" name="TaskRollbackDialogForm" setup> +import * as TaskApi from '@/api/bpm/task' +import { isEmpty } from '@/utils/is' + +const message = useMessage() // 消息弹窗 +const dialogVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中 +const formData = ref({ + id: '', + reason: '' +}) +const formRules = ref({ + id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }], + reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }] +}) + +const formRef = ref() // 表单 Ref +const subTaskList = ref([]) +/** 打开弹窗 */ +const open = async (id: string) => { + subTaskList.value = await TaskApi.getChildrenTaskList(id) + if (isEmpty(subTaskList.value)) { + message.warning('当前没有可减签的任务') + return false + } + dialogVisible.value = true + resetForm() +} +defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + await TaskApi.taskSubSign(formData.value) + message.success('减签成功') + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: '', + reason: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index 585c60db..f9c5452b 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -49,6 +49,10 @@ <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" /> 回退 @@ -95,6 +99,8 @@ <TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" /> <!-- 委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中--> <TaskDelegateForm ref="taskDelegateForm" @success="getDetail" /> + <!-- 加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 --> + <TaskAddSignDialogForm ref="taskAddSignDialogForm" @success="getDetail" /> </ContentWrap> </template> <script lang="ts" setup> @@ -109,7 +115,9 @@ import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue' import TaskReturnDialog from './TaskReturnDialogForm.vue' import TaskDelegateForm from './taskDelegateForm.vue' +import TaskAddSignDialogForm from './TaskAddSignDialogForm.vue' import { registerComponent } from '@/utils/routerHelper' +import { isEmpty } from '@/utils/is' defineOptions({ name: 'BpmProcessInstanceDetail' }) @@ -185,6 +193,12 @@ const handleBack = async (task) => { taskReturnDialogRef.value.open(task.id) } +const taskAddSignDialogForm = ref() +/** 处理审批加签的操作 */ +const handleSign = async (task) => { + taskAddSignDialogForm.value.open(task.id) +} + /** 获得详情 */ const getDetail = () => { // 1. 获得流程实例相关 @@ -261,26 +275,36 @@ const getTaskList = async () => { // 获得需要自己审批的任务 runningTasks.value = [] auditForms.value = [] - tasks.value.forEach((task) => { - // 2.1 只有待处理才需要 - if (task.result !== 1 && task.result !== 6) { - return - } - // 2.2 自己不是处理人 - if (!task.assigneeUser || task.assigneeUser.id !== userId) { - return - } - // 2.3 添加到处理任务 - runningTasks.value.push({ ...task }) - auditForms.value.push({ - reason: '' - }) - }) + loadRunningTask(tasks.value) } finally { tasksLoad.value = false } } +/** + * 设置 runningTasks 中的任务 + */ +const loadRunningTask = (tasks) => { + tasks.forEach((task) => { + if (!isEmpty(task.children)) { + loadRunningTask(task.children) + } + // 2.1 只有待处理才需要 + if (task.result !== 1 && task.result !== 6) { + return + } + // 2.2 自己不是处理人 + if (!task.assigneeUser || task.assigneeUser.id !== userId) { + return + } + // 2.3 添加到处理任务 + runningTasks.value.push({ ...task }) + auditForms.value.push({ + reason: '' + }) + }) +} + /** 初始化 */ onMounted(() => { getDetail()