diff --git a/src/api/bpm/definition/index.ts b/src/api/bpm/definition/index.ts index c0e51fab..cb6d4271 100644 --- a/src/api/bpm/definition/index.ts +++ b/src/api/bpm/definition/index.ts @@ -1,8 +1,9 @@ import request from '@/config/axios' -export const getProcessDefinitionBpmnXML = async (id: number) => { +export const getProcessDefinition = async (id: number, key: string) => { return await request.get({ - url: '/bpm/process-definition/get-bpmn-xml?id=' + id + url: '/bpm/process-definition/get', + params: { id, key } }) } diff --git a/src/api/bpm/processExpression/index.ts b/src/api/bpm/processExpression/index.ts new file mode 100644 index 00000000..af6a7372 --- /dev/null +++ b/src/api/bpm/processExpression/index.ts @@ -0,0 +1,42 @@ +import request from '@/config/axios' + +// BPM 流程表达式 VO +export interface ProcessExpressionVO { + id: number // 编号 + name: string // 表达式名字 + status: number // 表达式状态 + expression: string // 表达式 +} + +// BPM 流程表达式 API +export const ProcessExpressionApi = { + // 查询BPM 流程表达式分页 + getProcessExpressionPage: async (params: any) => { + return await request.get({ url: `/bpm/process-expression/page`, params }) + }, + + // 查询BPM 流程表达式详情 + getProcessExpression: async (id: number) => { + return await request.get({ url: `/bpm/process-expression/get?id=` + id }) + }, + + // 新增BPM 流程表达式 + createProcessExpression: async (data: ProcessExpressionVO) => { + return await request.post({ url: `/bpm/process-expression/create`, data }) + }, + + // 修改BPM 流程表达式 + updateProcessExpression: async (data: ProcessExpressionVO) => { + return await request.put({ url: `/bpm/process-expression/update`, data }) + }, + + // 删除BPM 流程表达式 + deleteProcessExpression: async (id: number) => { + return await request.delete({ url: `/bpm/process-expression/delete?id=` + id }) + }, + + // 导出BPM 流程表达式 Excel + exportProcessExpression: async (params) => { + return await request.download({ url: `/bpm/process-expression/export-excel`, params }) + } +} \ No newline at end of file diff --git a/src/api/bpm/processInstance/index.ts b/src/api/bpm/processInstance/index.ts index d5d0c05c..81640625 100644 --- a/src/api/bpm/processInstance/index.ts +++ b/src/api/bpm/processInstance/index.ts @@ -31,20 +31,32 @@ export type ProcessInstanceCopyVO = { reason: string } -export const getMyProcessInstancePage = async (params) => { +export const getProcessInstanceMyPage = async (params: any) => { return await request.get({ url: '/bpm/process-instance/my-page', params }) } +export const getProcessInstanceManagerPage = async (params: any) => { + return await request.get({ url: '/bpm/process-instance/manager-page', params }) +} + export const createProcessInstance = async (data) => { return await request.post({ url: '/bpm/process-instance/create', data: data }) } -export const cancelProcessInstance = async (id: number, reason: string) => { +export const cancelProcessInstanceByStartUser = async (id: number, reason: string) => { const data = { id: id, reason: reason } - return await request.delete({ url: '/bpm/process-instance/cancel', data: data }) + return await request.delete({ url: '/bpm/process-instance/cancel-by-start-user', data: data }) +} + +export const cancelProcessInstanceByAdmin = async (id: number, reason: string) => { + const data = { + id: id, + reason: reason + } + return await request.delete({ url: '/bpm/process-instance/cancel-by-admin', data: data }) } export const getProcessInstance = async (id: string) => { diff --git a/src/api/bpm/processListener/index.ts b/src/api/bpm/processListener/index.ts new file mode 100644 index 00000000..dabaa476 --- /dev/null +++ b/src/api/bpm/processListener/index.ts @@ -0,0 +1,40 @@ +import request from '@/config/axios' + +// BPM 流程监听器 VO +export interface ProcessListenerVO { + id: number // 编号 + name: string // 监听器名字 + type: string // 监听器类型 + status: number // 监听器状态 + event: string // 监听事件 + valueType: string // 监听器值类型 + value: string // 监听器值 +} + +// BPM 流程监听器 API +export const ProcessListenerApi = { + // 查询流程监听器分页 + getProcessListenerPage: async (params: any) => { + return await request.get({ url: `/bpm/process-listener/page`, params }) + }, + + // 查询流程监听器详情 + getProcessListener: async (id: number) => { + return await request.get({ url: `/bpm/process-listener/get?id=` + id }) + }, + + // 新增流程监听器 + createProcessListener: async (data: ProcessListenerVO) => { + return await request.post({ url: `/bpm/process-listener/create`, data }) + }, + + // 修改流程监听器 + updateProcessListener: async (data: ProcessListenerVO) => { + return await request.put({ url: `/bpm/process-listener/update`, data }) + }, + + // 删除流程监听器 + deleteProcessListener: async (id: number) => { + return await request.delete({ url: `/bpm/process-listener/delete?id=` + id }) + } +} diff --git a/src/api/bpm/task/index.ts b/src/api/bpm/task/index.ts index 6592542d..f3cda9f7 100644 --- a/src/api/bpm/task/index.ts +++ b/src/api/bpm/task/index.ts @@ -4,14 +4,18 @@ export type TaskVO = { id: number } -export const getTodoTaskPage = async (params: any) => { +export const getTaskTodoPage = async (params: any) => { return await request.get({ url: '/bpm/task/todo-page', params }) } -export const getDoneTaskPage = async (params: any) => { +export const getTaskDonePage = async (params: any) => { return await request.get({ url: '/bpm/task/done-page', params }) } +export const getTaskManagerPage = async (params: any) => { + return await request.get({ url: '/bpm/task/manager-page', params }) +} + export const approveTask = async (data: any) => { return await request.put({ url: '/bpm/task/approve', data }) } diff --git a/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue b/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue index 5e77c948..5ad2ff4b 100644 --- a/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue +++ b/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue @@ -139,6 +139,14 @@ const updateBaseInfo = (key) => { } } +onMounted(() => { + // 针对上传的 bpmn 流程图时,需要延迟 1 毫秒的时间,保证 key 和 name 的更新 + setTimeout(() => { + handleKeyUpdate(props.model.key) + handleNameUpdate(props.model.name) + }, 110) +}) + watch( () => props.businessObject, (val) => { diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue index 45ee8f93..de5445c8 100644 --- a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue @@ -26,8 +26,16 @@ type="primary" preIcon="ep:plus" title="添加监听器" + size="small" @click="openListenerForm(null)" /> + @@ -240,11 +248,21 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue index 9464883c..76e0c809 100644 --- a/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue @@ -39,6 +39,13 @@ title="添加监听器" @click="openListenerForm(null)" /> + @@ -286,11 +293,22 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue index 6431eca1..0dffeb0f 100644 --- a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue @@ -5,7 +5,7 @@ v-model="userTaskForm.candidateStrategy" clearable style="width: 100%" - @change="changecandidateStrategy" + @change="changeCandidateStrategy" > + 选择表达式 + + @@ -133,6 +134,7 @@ import * as DeptApi from '@/api/system/dept' import * as PostApi from '@/api/system/post' import * as UserApi from '@/api/system/user' import * as UserGroupApi from '@/api/bpm/userGroup' +import ProcessExpressionDialog from './ProcessExpressionDialog.vue' defineOptions({ name: 'UserTask' }) const props = defineProps({ @@ -177,7 +179,7 @@ const resetTaskForm = () => { } /** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */ -const changecandidateStrategy = () => { +const changeCandidateStrategy = () => { userTaskForm.value.candidateParam = [] updateElementTask() } @@ -190,6 +192,15 @@ const updateElementTask = () => { }) } +// 打开监听器弹窗 +const processExpressionDialogRef = ref() +const openProcessExpressionDialog = async () => { + processExpressionDialogRef.value.open() +} +const selectProcessExpression = (expression) => { + userTaskForm.value.candidateParam = [expression.expression] +} + watch( () => props.id, () => { diff --git a/src/components/bpmnProcessDesigner/package/utils.ts b/src/components/bpmnProcessDesigner/package/utils.ts index bb6c5d52..8996788b 100644 --- a/src/components/bpmnProcessDesigner/package/utils.ts +++ b/src/components/bpmnProcessDesigner/package/utils.ts @@ -2,6 +2,7 @@ import { toRaw } from 'vue' const bpmnInstances = () => (window as any)?.bpmnInstances // 创建监听器实例 export function createListenerObject(options, isTask, prefix) { + debugger const listenerObj = Object.create(null) listenerObj.event = options.event isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段 diff --git a/src/utils/dict.ts b/src/utils/dict.ts index f7d337cb..2284ff13 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -141,6 +141,8 @@ export enum DICT_TYPE { BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status', BPM_TASK_STATUS = 'bpm_task_status', BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type', + BPM_PROCESS_LISTENER_TYPE = 'bpm_process_listener_type', + BPM_PROCESS_LISTENER_VALUE_TYPE = 'bpm_process_listener_value_type', // ========== PAY 模块 ========== PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型 @@ -155,7 +157,7 @@ export enum DICT_TYPE { MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型 MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型 - // ========== MALL - 会员模块 ========== + // ========== Member 会员模块 ========== MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型 MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型 diff --git a/src/views/bpm/definition/index.vue b/src/views/bpm/definition/index.vue index 9ebd28b1..1e7794b3 100644 --- a/src/views/bpm/definition/index.vue +++ b/src/views/bpm/definition/index.vue @@ -72,8 +72,8 @@ @@ -133,12 +133,12 @@ const handleFormDetail = async (row) => { /** 流程图的详情按钮操作 */ const bpmnDetailVisible = ref(false) -const bpmnXML = ref(null) +const bpmnXml = ref(null) const bpmnControlForm = ref({ prefix: 'flowable' }) const handleBpmnDetail = async (row) => { - bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id) + bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml bpmnDetailVisible.value = true } diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue index 0e5b0521..ce60edca 100644 --- a/src/views/bpm/model/ModelForm.vue +++ b/src/views/bpm/model/ModelForm.vue @@ -50,6 +50,9 @@ /> + + + @@ -141,15 +144,17 @@ const formData = ref({ formType: 10, name: '', category: undefined, + icon: undefined, description: '', formId: '', formCustomCreatePath: '', formCustomViewPath: '' }) const formRules = reactive({ - category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }], name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }], key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }], + category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }], + icon: [{ required: true, message: '参数图标不能为空', trigger: 'blur' }], value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }], visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }] }) @@ -223,6 +228,7 @@ const resetForm = () => { formType: 10, name: '', category: undefined, + icon: '', description: '', formId: '', formCustomCreatePath: '', diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index 47d24ea9..d616f2c0 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -72,6 +72,11 @@ + + + + + diff --git a/src/views/bpm/oa/leave/create.vue b/src/views/bpm/oa/leave/create.vue index a22392f9..28a15af7 100644 --- a/src/views/bpm/oa/leave/create.vue +++ b/src/views/bpm/oa/leave/create.vue @@ -37,6 +37,36 @@ + + + 指定审批人 + + + + + + + + + 确 定 @@ -46,10 +76,15 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as LeaveApi from '@/api/bpm/leave' import { useTagsViewStore } from '@/store/modules/tagsView' +import * as DefinitionApi from '@/api/bpm/definition' +import * as UserApi from '@/api/system/user' defineOptions({ name: 'BpmOALeaveCreate' }) const message = useMessage() // 消息弹窗 +const { delView } = useTagsViewStore() // 视图操作 +const { push, currentRoute } = useRouter() // 路由 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formData = ref({ type: undefined, @@ -64,18 +99,34 @@ const formRules = reactive({ endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }] }) const formRef = ref() // 表单 Ref -const { delView } = useTagsViewStore() // 视图操作 -const { push, currentRoute } = useRouter() // 路由 + +// 指定审批人 +const processDefineKey = 'oa_leave' // 流程定义 Key +const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表 +const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据 +const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref +const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules +const userList = ref([]) // 用户列表 + /** 提交表单 */ const submitForm = async () => { // 校验表单 if (!formRef) return const valid = await formRef.value.validate() if (!valid) return + // 校验指定审批人 + if (startUserSelectTasks.value?.length > 0) { + await startUserSelectAssigneesFormRef.value.validate() + } + // 提交请求 formLoading.value = true try { - const data = formData.value as unknown as LeaveApi.LeaveVO + const data = { ...formData.value } as unknown as LeaveApi.LeaveVO + // 设置指定审批人 + if (startUserSelectTasks.value?.length > 0) { + data.startUserSelectAssignees = startUserSelectAssignees.value + } await LeaveApi.createLeave(data) message.success('发起成功') // 关闭当前 Tab @@ -85,4 +136,29 @@ const submitForm = async () => { formLoading.value = false } } + +/** 初始化 */ +onMounted(async () => { + const processDefinitionDetail = await DefinitionApi.getProcessDefinition( + undefined, + processDefineKey + ) + if (!processDefinitionDetail) { + message.error('OA 请假的流程模型未配置,请检查!') + return + } + startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks + // 设置指定审批人 + if (startUserSelectTasks.value?.length > 0) { + // 设置校验规则 + for (const userTask of startUserSelectTasks.value) { + startUserSelectAssignees.value[userTask.id] = [] + startUserSelectAssigneesFormRules.value[userTask.id] = [ + { required: true, message: '请选择审批人', trigger: 'blur' } + ] + } + // 加载用户列表 + userList.value = await UserApi.getSimpleUserList() + } +}) diff --git a/src/views/bpm/oa/leave/index.vue b/src/views/bpm/oa/leave/index.vue index 4af7ad3c..fe96a498 100644 --- a/src/views/bpm/oa/leave/index.vue +++ b/src/views/bpm/oa/leave/index.vue @@ -226,7 +226,7 @@ const cancelLeave = async (row) => { inputErrorMessage: '取消原因不能为空' }) // 发起取消 - await ProcessInstanceApi.cancelProcessInstance(row.id, value) + await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value) message.success('取消成功') // 刷新列表 await getList() diff --git a/src/views/bpm/processExpression/ProcessExpressionForm.vue b/src/views/bpm/processExpression/ProcessExpressionForm.vue new file mode 100644 index 00000000..acf0667c --- /dev/null +++ b/src/views/bpm/processExpression/ProcessExpressionForm.vue @@ -0,0 +1,114 @@ + + + + + + + + + + {{ dict.label }} + + + + + + + + + 确 定 + 取 消 + + + + diff --git a/src/views/bpm/processExpression/index.vue b/src/views/bpm/processExpression/index.vue new file mode 100644 index 00000000..194a4d85 --- /dev/null +++ b/src/views/bpm/processExpression/index.vue @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + 搜索 + 重置 + + 新增 + + + + + + + + + + + + + + + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + diff --git a/src/views/bpm/processInstance/create/index.vue b/src/views/bpm/processInstance/create/index.vue index bd782fef..d9fee5d2 100644 --- a/src/views/bpm/processInstance/create/index.vue +++ b/src/views/bpm/processInstance/create/index.vue @@ -23,11 +23,7 @@ > - - + {{ definition.name }} @@ -54,7 +50,40 @@ v-model="detailForm.value" :option="detailForm.option" @submit="submitForm" - /> + > + + + + 指定审批人 + + + + + + + + + + + @@ -69,6 +98,7 @@ import type { ApiAttrs } from '@form-create/element-ui/types/config' import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue' import { CategoryApi } from '@/api/bpm/category' import { useTagsViewStore } from '@/store/modules/tagsView' +import * as UserApi from '@/api/system/user' defineOptions({ name: 'BpmProcessInstanceCreate' }) @@ -124,7 +154,6 @@ const categoryProcessDefinitionList = computed(() => { }) // ========== 表单相关 ========== -const bpmnXML = ref(null) // BPMN 数据 const fApi = ref() const detailForm = ref({ rule: [], @@ -133,17 +162,53 @@ const detailForm = ref({ }) // 流程表单详情 const selectProcessDefinition = ref() // 选择的流程定义 +// 指定审批人 +const bpmnXML = ref(null) // BPMN 数据 +const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表 +const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据 +const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref +const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules +const userList = ref([]) // 用户列表 + /** 处理选择流程的按钮操作 **/ const handleSelect = async (row, formVariables) => { // 设置选择的流程 selectProcessDefinition.value = row + // 重置指定审批人 + startUserSelectTasks.value = [] + startUserSelectAssignees.value = {} + startUserSelectAssigneesFormRules.value = {} + // 情况一:流程表单 if (row.formType == 10) { // 设置表单 setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables) // 加载流程图 - bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id) + const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id) + if (processDefinitionDetail) { + bpmnXML.value = processDefinitionDetail.bpmnXml + startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks + + // 设置指定审批人 + if (startUserSelectTasks.value?.length > 0) { + detailForm.value.rule.push({ + type: 'startUserSelect', + props: { + title: '指定审批人' + } + }) + // 设置校验规则 + for (const userTask of startUserSelectTasks.value) { + startUserSelectAssignees.value[userTask.id] = [] + startUserSelectAssigneesFormRules.value[userTask.id] = [ + { required: true, message: '请选择审批人', trigger: 'blur' } + ] + } + // 加载用户列表 + userList.value = await UserApi.getSimpleUserList() + } + } // 情况二:业务表单 } else if (row.formCustomCreatePath) { await push({ @@ -158,19 +223,25 @@ const submitForm = async (formData) => { if (!fApi.value || !selectProcessDefinition.value) { return } + // 如果有指定审批人,需要校验 + if (startUserSelectTasks.value?.length > 0) { + await startUserSelectAssigneesFormRef.value.validate() + } + // 提交请求 fApi.value.btn.loading(true) try { await ProcessInstanceApi.createProcessInstance({ processDefinitionId: selectProcessDefinition.value.id, - variables: formData + variables: formData, + startUserSelectAssignees: startUserSelectAssignees.value }) // 提示 message.success('发起流程成功') // 跳转回去 delView(unref(currentRoute)) await push({ - name: 'BpmProcessInstance' + name: 'BpmProcessInstanceMy' }) } finally { fApi.value.btn.loading(false) diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue index dcf3bcc4..8912593a 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue @@ -34,14 +34,17 @@ const bpmnControlForm = ref({ }) const activityList = ref([]) // 任务列表 -/** 初始化 */ -onMounted(async () => { - if (props.id) { - activityList.value = await ActivityApi.getActivityList({ - processInstanceId: props.id - }) +/** 只有 loading 完成时,才去加载流程列表 */ +watch( + () => props.loading, + async (value) => { + if (value && props.id) { + activityList.value = await ActivityApi.getActivityList({ + processInstanceId: props.id + }) + } } -}) +)