From 0108df13340210bebd5ad6c693fc1a6bd1b061ff Mon Sep 17 00:00:00 2001 From: 疯狂的狮子Li <15040126243@163.com> Date: 星期二, 05 三月 2024 22:57:51 +0800 Subject: [PATCH] !88 合并flowable工作流功能 * merge 合并dev * add 添加抄送查询 * add 添加我的已办 * add 添加抄送 * add 添加附件下载 * update 优化类型 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * fix 修复 流程设计器打包部署报错问题 * add 添加审批附件上传 * update 修复固定值未在xml显示问题 * update 修复跳转条件回显Object问题 * update 优化面板文件名称 * update 调整key * remove 移除旧设计器,添加xml保存 * update 增加任务面板提示 * update 优化xml预览和svg预览 * update 优化xml预览和svg预览 * update 优化模型设计 * update 优化设计器 * update 优化设计器 * update 优化设计器 * update 删除旧设计器 * update 删除console.log * add 添加模型接口 * update 优化预览xml和svg样式被修改问题 * update 优化属性面板,增加展开动画 * update 去除开发模式 * update 优化任务栏样式 * update 优化图标渲染样式 * update 增加BpmnFactory类型 * update 增加BpmnFactory * update 移除users和group * update 移除无用类型 * update 优化页面类型 * update 去除多余属性 * update 完善流程线 * update 增加复杂网关 * update 完善流程 * update 完善网关 * update 优化网关汉化 * update 优化过期时间选择 * update 支持多实例 * update 增加类容提示 * update 支持选择组 * update 新增角色api * update 优化roleSelect 选中未确定,再次打开还保留选中的问题 * update 优化userSelect 选中未确定,再次打开还保留选中的问题 * update 优化userSelect 选中未确定,再次打开还保留选中的问题 * update 去掉modeler store多余属性 bpmnModel * update 优化属性面板,当面板未选中时默认展示流程面板 * update 优化TaskPanel类型,去掉roles属性 * update 优化用户api * update 优化用户选择器 * update 优化执行监听器 * update 优化任务监听器 * update 优化usePanel方法 * update 选人优化 * update 增加扩展节点信息 * update 增加usePanel默认方法 * update 去除处理事件 * update 扩展flowable userinfo属性 * update 全局modeler 改为非响应式 * update 增加hooks方法 * update 修改命名 * update 修改面板formData来源 * update 重写用户任务面板选择逻辑 * update 重写用户任务面板选择逻辑 * update 修改用户选择组件获取数据逻辑 * update 修改枚举类型 * update 修改默认配置列 * update 增加修改节点方法 * update 调整预览窗口大小 * update 优化用户选择组件 返回值 * update 优化用户选择组件 * update 新增通过ids 获取用户信息 * update 重写task面板选人 未完成 * update 升级用户选择 支持多选配置 * update 升级bpmnjs依赖版本 * update 增加useDialog类型 * update 调整全局样式 * update 代码高亮设置 * update 优化领用,归还加载 * update 增加选择角色 * update 新增角色选择组件 * update 新增过期时间选择组件 * update 调整任务面板样式 * update 调整全局dialog header 增加分割线 * update 代码高亮设置 * update 调整面板位置 * update 封装用户选择组件 * update 移除所有的节点描述 * update 删除分类 * update 调整面板位置 * update 修改命名,增加自定义渲染 * update 修改命名,增加自定义渲染 * update 增加 Element类型定义 * update 调整样式 * update 移除bpmn panel依赖,升级bpmn.js依赖到最新,修改汉化包 * update 调整类型声明文件 * update 调整类型声明文件 * update 优化面板工具 * update 优化面板工具 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * update 优化面板工具 * Merge branch 'future/flowable' of https://gitee.com/JavaLionLi/plus-ui… * add 添加修改办理人 * update 优化面板工具 * update 初始化流程数据 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * add 流程设计面板 * update 调整初始化xml * add 任务面板 * add 新增bpmn.js * update 优化request请求类判断请求头方式 * update 流程定义预览 优化 * update 流程定义预览 优化 * update 去掉console.log * update 优化工作流代码 * fix 修复待办任务 重置查询条件失效问题 * add 增加待办任务 接口类型,优化页面 * add 增加vite 启动预编译css * fix 修复i18n无感刷新问题 * Merge branch 'dev' into future/flowable * update 调整选择请假事件 * Merge branch 'dev' into future/flowable * 同步dev代码 * Merge branch 'dev' into future/flowable * 合并dev * remove 设计器无用代码 调整请假查询 * update 调整请假申请 * update 移动请假表单包结构,调整设计器选择引用表单请求错误 * remove 移除动态表单 * update 调整流程办理 * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * 合并 * update 调整请假申请流程提交 * update 修改业务单据流程提交 * update 调整业务单据流程提交 * 优化代码 * 优化工作流代码缩进 * 项目格式化配置修改 * 调整代码缩进 * Merge branch 'ts' into future/flowable * add 添加动态表单提交流程 * add 添加动态表单单据 * add 新增流程定义与表单关联 * update 调整点击左侧部门查询人员,部门刷新问题 * update 调整按钮图标 * 调整错别字 * update 调整流程定义图片预览 * add 添加流程实例迁移版本 * fix 修复我的单据无法提交问题 * Merge branch 'ts' into future/flowable * remove 还原代码后端解决 * update 流程设计器中分配发起人变量错误,先移除 * fix 修复设计器无法编辑问题 * update 调整设计器请求头 * Merge branch 'ts' into future/flowable * Merge branch 'ts' into future/flowable * add 添加流程定义历史列表 * update 审批记录v2改为v3 * update 调整请假必填项 * add 添加请假申请示例,添加流程定义文件部署 * update 移除流程表单 formConfig 属性,表单配置信息都放一起便于使用。 * add 添加任务加签,减签 * update 调整流程作废 * update 优化流程状态 * add 添加任务驳回 * add 添加查询当前租户所有待办,已办任务 * add 增加审批意见 * add 添加流程办理弹窗确认组件 * add 添加任务作废理由 * update 调整流程实例,流程定义检索 * add 添加我的单据页面 * add 添加任务归还认领 * add 添加流程实例,流程定义分类查询 * add 添加模型分类查询 * add 添加流程分类 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * add 添加流程表单管理页面 * add 集成vForm动态表单组件 * update xml调整超出滚动 * add 添加已办列表 * add 添加单据状态 * update 优化流程实例删除 * fix 修复流程实例查询挂起状态错误 * update 调整流程实例挂起激活状态 * add 添加流程实例列表 * update 调整流程定义弹窗提示 * add 添加流程定义列表,添加流程图,xml预览,添加简单流程启动,办理 * 调整审批记录悬浮逻辑 * 删除无用代码 * add 添加节点悬浮信息 * 调整流程预览 * 调整流程追踪 * add 添加审批记录 * add 模型设计的types * update 修改排版 * add lang=ts * update 修改ele的废弃api * fix调整审批记录图片不显示问题 * add 添加任务待办,流程图 * 调整设计器关闭 * add 添加待办 * remove 删除无用代码 * update types * 添加模型token验证 * 隐藏设计器验证按钮,隐藏表单,案例,应用程序等 * 添加模型部署 * 添加画图接口token,优化画图接口 * 添加工作流模型新增,修改,查询,删除,画图工具 --- src/views/workflow/category/index.vue | 263 + src/assets/icons/svg/caret-back.svg | 1 src/components/BpmnDesign/panel/property/ExecutionListener.vue | 305 + src/types/bpmn/index.d.ts | 15 src/api/workflow/workflowUser/index.ts | 53 src/directive/common/copyText.ts | 3 src/views/workflow/model/design.vue | 65 src/enums/bpmn/IndexEnums.ts | 17 src/components/BpmnDesign/assets/showConfig.ts | 50 src/views/workflow/processInstance/index.vue | 363 ++ src/components/BpmnDesign/index.vue | 409 ++ src/components/BpmnDesign/panel/SequenceFlowPanel.vue | 94 src/types/bpmn/editor/global.d.ts | 13 src/components/BpmnDesign/panel/ProcessPanel.vue | 71 src/components/BpmnDesign/panel/property/DueDate.vue | 252 + src/components/BpmnDesign/assets/moddle/flowable.ts | 1250 +++++++ src/components/BpmnDesign/panel/TaskPanel.vue | 467 ++ src/components/Process/approvalRecord.vue | 162 + src/views/workflow/processDefinition/index.vue | 403 ++ src/components/BpmnDesign/panel/property/ListenerParam.vue | 121 src/components/BpmnDesign/panel/property/TaskListener.vue | 307 + src/views/workflow/leave/index.vue | 381 ++ src/views/workflow/task/myDocument.vue | 256 + src/api/workflow/model/types.ts | 66 src/api/workflow/task/types.ts | 39 src/components/BpmnDesign/hooks/usePanel.ts | 132 src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts | 119 src/components/BpmnDesign/assets/defaultXML.ts | 23 src/views/workflow/model/index.vue | 360 ++ src/components/BpmnDesign/assets/style/index.scss | 230 + src/components/BpmnDesign/panel/index.vue | 104 src/store/modules/modeler.ts | 76 src/components/BpmnDesign/assets/module/index.ts | 17 src/types/bpmn/moddle.d.ts | 37 src/components/Process/submitVerify.vue | 165 + src/api/workflow/leave/types.ts | 22 src/components/BpmnDesign/assets/lang/zh.ts | 118 src/api/workflow/category/types.ts | 67 src/api/workflow/processDefinition/types.ts | 22 src/api/workflow/processInstance/index.ts | 115 src/api/workflow/model/index.ts | 91 src/hooks/useDialog.ts | 2 src/api/workflow/task/index.ts | 181 + src/views/workflow/task/allTaskWaiting.vue | 237 + src/views/workflow/task/taskFinish.vue | 128 vite.config.ts | 17 src/api/workflow/processInstance/types.ts | 27 src/api/workflow/category/index.ts | 63 src/api/workflow/processDefinition/index.ts | 123 src/components/BpmnDesign/assets/module/Translate/index.ts | 15 src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts | 138 src/components/Process/multiInstance-user.vue | 362 ++ src/components/Process/sys-user.vue | 301 + src/assets/styles/element-ui.scss | 19 src/views/workflow/task/taskCopyList.vue | 141 src/components/BpmnDesign/hooks/useParseElement.ts | 34 src/api/system/user/index.ts | 12 src/components/BpmnDesign/panel/StartEndPanel.vue | 40 src/components/UserSelect/index.vue | 2 src/main.ts | 7 src/api/workflow/leave/index.ts | 63 src/components/BpmnDesign/panel/GatewayPanel.vue | 68 src/views/workflow/task/taskWaiting.vue | 181 + src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts | 56 src/types/bpmn/panel.d.ts | 75 src/components/RoleSelect/index.vue | 2 package.json | 19 src/api/system/role/index.ts | 16 src/views/workflow/processDefinition/components/processPreview.vue | 57 src/assets/icons/svg/caret-forward.svg | 1 70 files changed, 9,502 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 7a47e4b..751bd6f 100644 --- a/package.json +++ b/package.json @@ -20,23 +20,33 @@ }, "dependencies": { "@element-plus/icons-vue": "2.3.1", + "@highlightjs/vue-plugin": "2.1.0", + "@lezer/common": "1.2.1", "@vueup/vue-quill": "1.2.0", "@vueuse/core": "10.7.2", "animate.css": "4.1.1", "await-to-js": "3.0.0", "axios": "1.6.5", + "bpmn-js": "16.4.0", + "camunda-bpmn-js-behaviors": "1.2.2", + "camunda-bpmn-moddle": "7.0.1", "crypto-js": "4.2.0", + "diagram-js": "12.3.0", + "didi": "9.0.2", "echarts": "5.4.3", "element-plus": "2.4.4", "file-saver": "2.0.5", "fuse.js": "7.0.0", + "highlight.js": "11.9.0", "image-conversion": "^2.1.1", "js-cookie": "3.0.5", "jsencrypt": "3.3.2", + "moddle": "6.2.3", "nprogress": "0.2.0", "path-browserify": "1.0.1", "path-to-regexp": "6.2.1", "pinia": "2.1.7", + "preact": "10.19.3", "screenfull": "6.0.2", "vform3-builds": "3.0.10", "vue": "3.4.13", @@ -44,7 +54,8 @@ "vue-i18n": "9.9.0", "vue-router": "4.2.5", "vue-types": "5.1.1", - "vxe-table": "4.5.18" + "vxe-table": "4.5.18", + "zeebe-bpmn-moddle": "1.0.0" }, "devDependencies": { "@iconify/json": "2.2.168", @@ -60,8 +71,8 @@ "@unocss/preset-attributify": "0.58.3", "@unocss/preset-icons": "0.58.3", "@unocss/preset-uno": "0.58.3", - "@vue/compiler-sfc": "3.4.13", "@vitejs/plugin-vue": "5.0.3", + "@vue/compiler-sfc": "3.4.13", "autoprefixer": "10.4.16", "eslint": "8.56.0", "eslint-config-prettier": "9.1.0", @@ -82,11 +93,11 @@ "unplugin-icons": "0.18.2", "unplugin-vue-components": "0.26.0", "unplugin-vue-setup-extend-plus": "1.0.0", + "vite": "5.0.11", "vite-plugin-compression": "0.5.1", "vite-plugin-svg-icons": "2.0.1", "vitest": "1.2.0", "vue-eslint-parser": "9.4.0", - "vue-tsc": "1.8.27", - "vite": "5.0.11" + "vue-tsc": "1.8.27" } } diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index 4e8b612..fb0fcab 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -13,6 +13,17 @@ }; /** + * 閫氳繃roleIds鏌ヨ瑙掕壊 + * @param roleIds + */ +export const optionSelect = (roleIds: (number | string)[]): AxiosPromise<RoleVO[]> => { + return request({ + url: '/system/role/optionselect?roleIds=' + roleIds, + method: 'get' + }); +}; + +/** * 鏌ヨ瑙掕壊璇︾粏 */ export const getRole = (roleId: string | number): AxiosPromise<RoleVO> => { @@ -142,3 +153,8 @@ method: 'get' }); }; + +export default { + optionSelect, + listRole +}; diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index fdcafc2..61a58f6 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -18,6 +18,17 @@ }; /** + * 閫氳繃鐢ㄦ埛ids鏌ヨ鐢ㄦ埛 + * @param userIds + */ +export const optionSelect = (userIds: (number | string)[]): AxiosPromise<UserVO[]> => { + return request({ + url: '/system/user/optionselect?userIds=' + userIds, + method: 'get' + }); +}; + +/** * 鑾峰彇鐢ㄦ埛璇︽儏 * @param userId */ @@ -199,6 +210,7 @@ export default { listUser, getUser, + optionSelect, addUser, updateUser, delUser, diff --git a/src/api/workflow/category/index.ts b/src/api/workflow/category/index.ts new file mode 100644 index 0000000..e9723b0 --- /dev/null +++ b/src/api/workflow/category/index.ts @@ -0,0 +1,63 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { CategoryVO, CategoryForm, CategoryQuery } from '@/api/workflow/category/types'; + +/** + * 鏌ヨ娴佺▼鍒嗙被鍒楄〃 + * @param query + * @returns {*} + */ + +export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => { + return request({ + url: '/workflow/category/list', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ娴佺▼鍒嗙被璇︾粏 + * @param id + */ +export const getCategory = (id: string | number): AxiosPromise<CategoryVO> => { + return request({ + url: '/workflow/category/' + id, + method: 'get' + }); +}; + +/** + * 鏂板娴佺▼鍒嗙被 + * @param data + */ +export const addCategory = (data: CategoryForm) => { + return request({ + url: '/workflow/category', + method: 'post', + data: data + }); +}; + +/** + * 淇敼娴佺▼鍒嗙被 + * @param data + */ +export const updateCategory = (data: CategoryForm) => { + return request({ + url: '/workflow/category', + method: 'put', + data: data + }); +}; + +/** + * 鍒犻櫎娴佺▼鍒嗙被 + * @param id + */ +export const delCategory = (id: string | number | Array<string | number>) => { + return request({ + url: '/workflow/category/' + id, + method: 'delete' + }); +}; diff --git a/src/api/workflow/category/types.ts b/src/api/workflow/category/types.ts new file mode 100644 index 0000000..414fa55 --- /dev/null +++ b/src/api/workflow/category/types.ts @@ -0,0 +1,67 @@ +export interface CategoryVO { + /** + * 涓婚敭 + */ + id: string; + + /** + * 鍒嗙被鍚嶇О + */ + categoryName: string; + + /** + * 鍒嗙被缂栫爜 + */ + categoryCode: string; + + /** + * 鐖剁骇id + */ + parentId: string | number; + + /** + * 鎺掑簭 + */ + sortNum: number; + + children?: CategoryVO[]; +} + +export interface CategoryForm extends BaseEntity { + /** + * 涓婚敭 + */ + id?: string | number; + + /** + * 鍒嗙被鍚嶇О + */ + categoryName?: string; + + /** + * 鍒嗙被缂栫爜 + */ + categoryCode?: string; + + /** + * 鐖剁骇id + */ + parentId?: string | number; + + /** + * 鎺掑簭 + */ + sortNum?: number; +} + +export interface CategoryQuery extends PageQuery { + /** + * 鍒嗙被鍚嶇О + */ + categoryName?: string; + + /** + * 鍒嗙被缂栫爜 + */ + categoryCode?: string; +} diff --git a/src/api/workflow/leave/index.ts b/src/api/workflow/leave/index.ts new file mode 100644 index 0000000..36c6fdf --- /dev/null +++ b/src/api/workflow/leave/index.ts @@ -0,0 +1,63 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { LeaveVO, LeaveQuery, LeaveForm } from '@/api/workflow/leave/types'; + +/** + * 鏌ヨ璇峰亣鍒楄〃 + * @param query + * @returns {*} + */ + +export const listLeave = (query?: LeaveQuery): AxiosPromise<LeaveVO[]> => { + return request({ + url: '/demo/leave/list', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ璇峰亣璇︾粏 + * @param id + */ +export const getLeave = (id: string | number): AxiosPromise<LeaveVO> => { + return request({ + url: '/demo/leave/' + id, + method: 'get' + }); +}; + +/** + * 鏂板璇峰亣 + * @param data + */ +export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => { + return request({ + url: '/demo/leave', + method: 'post', + data: data + }); +}; + +/** + * 淇敼璇峰亣 + * @param data + */ +export const updateLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => { + return request({ + url: '/demo/leave', + method: 'put', + data: data + }); +}; + +/** + * 鍒犻櫎璇峰亣 + * @param id + */ +export const delLeave = (id: string | number | Array<string | number>) => { + return request({ + url: '/demo/leave/' + id, + method: 'delete' + }); +}; diff --git a/src/api/workflow/leave/types.ts b/src/api/workflow/leave/types.ts new file mode 100644 index 0000000..9405257 --- /dev/null +++ b/src/api/workflow/leave/types.ts @@ -0,0 +1,22 @@ +export interface LeaveVO { + id: string | number; + leaveType: string; + startDate: string; + endDate: string; + leaveDays: number; + remark: string; +} + +export interface LeaveForm extends BaseEntity { + id?: string | number; + leaveType?: string; + startDate?: string; + endDate?: string; + leaveDays?: number; + remark?: string; +} + +export interface LeaveQuery extends PageQuery { + startLeaveDays?: number; + endLeaveDays?: number; +} diff --git a/src/api/workflow/model/index.ts b/src/api/workflow/model/index.ts new file mode 100644 index 0000000..f58cdd3 --- /dev/null +++ b/src/api/workflow/model/index.ts @@ -0,0 +1,91 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { ModelForm, ModelQuery, ModelVO } from '@/api/workflow/model/types'; + +/** + * 鏌ヨ妯″瀷鍒楄〃 + * @param query + * @returns {*} + */ +export const listModel = (query: ModelQuery): AxiosPromise<ModelVO[]> => { + return request({ + url: '/workflow/model/list', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ妯″瀷淇℃伅 + * @param query + * @returns {*} + */ +export const getInfo = (id: string): AxiosPromise<ModelForm> => { + return request({ + url: '/workflow/model/getInfo/'+id, + method: 'get' + }); +}; + +/** + * 鏂板妯″瀷 + * @param data + * @returns {*} + */ +export const addModel = (data: ModelForm): AxiosPromise<void> => { + return request({ + url: '/workflow/model/save', + method: 'post', + data: data + }); +}; + +/** + * 淇敼妯″瀷淇℃伅 + * @param data + * @returns {*} + */ +export function update(data: ModelForm): AxiosPromise<void> { + return request({ + url: '/workflow/model/update', + method: 'put', + data: data + }); +} + +/** + * 淇敼妯″瀷淇℃伅 + * @param data + * @returns {*} + */ +export function editModelXml(data: ModelForm): AxiosPromise<void> { + return request({ + url: '/workflow/model/editModelXml', + method: 'put', + data: data + }); +} + +/** + * 鎸塱d鍒犻櫎妯″瀷 + * @returns {*} + * @param id 妯″瀷id + */ +export function delModel(id: string | string[]): AxiosPromise<void> { + return request({ + url: '/workflow/model/' + id, + method: 'delete' + }); +} + +/** + * 妯″瀷閮ㄧ讲 + * @returns {*} + * @param id 妯″瀷id + */ +export const modelDeploy = (id: string): AxiosPromise<void> => { + return request({ + url: `/workflow/model/modelDeploy/${id}`, + method: 'post' + }); +}; diff --git a/src/api/workflow/model/types.ts b/src/api/workflow/model/types.ts new file mode 100644 index 0000000..40a0faa --- /dev/null +++ b/src/api/workflow/model/types.ts @@ -0,0 +1,66 @@ +export interface ModelForm { + id: string, + name: string; + key: string; + categoryCode: string; + xml:string, + svg:string, + description: string; +} + +export interface ModelQuery extends PageQuery { + name?: string; + key?: string; + categoryCode?: string; +} + +export interface OriginalPersistentState { + metaInfo: string; + editorSourceValueId: string; + createTime: string; + deploymentId?: string; + name: string; + tenantId: string; + category?: string; + version: number; + editorSourceExtraValueId?: string; + key: string; + lastUpdateTime: string; +} + +export interface PersistentState { + metaInfo: string; + editorSourceValueId: string; + createTime: string; + deploymentId?: string; + name: string; + tenantId: string; + category?: string; + version: number; + editorSourceExtraValueId?: string; + key: string; + lastUpdateTime: string; +} + +export interface ModelVO { + id: string; + revision: number; + originalPersistentState: OriginalPersistentState; + name: string; + key: string; + category?: string; + createTime: string; + lastUpdateTime: string; + version: number; + metaInfo: string; + deploymentId?: string; + editorSourceValueId: string; + editorSourceExtraValueId?: string; + tenantId: string; + persistentState: PersistentState; + revisionNext: number; + idPrefix: string; + inserted: boolean; + updated: boolean; + deleted: boolean; +} diff --git a/src/api/workflow/processDefinition/index.ts b/src/api/workflow/processDefinition/index.ts new file mode 100644 index 0000000..f64b531 --- /dev/null +++ b/src/api/workflow/processDefinition/index.ts @@ -0,0 +1,123 @@ +import request from '@/utils/request'; +import { ProcessDefinitionQuery, ProcessDefinitionVO, ProcessDefinitionXmlVO } from '@/api/workflow/processDefinition/types'; +import { AxiosPromise } from 'axios'; +const baseUrl = import.meta.env.VITE_APP_BASE_API; + +/** + * 鑾峰彇娴佺▼瀹氫箟鍒楄〃 + * @param query 娴佺▼瀹炰緥id + * @returns + */ +export const listProcessDefinition = (query: ProcessDefinitionQuery): AxiosPromise<ProcessDefinitionVO[]> => { + return request({ + url: `/workflow/processDefinition/list`, + method: 'get', + params: query + }); +}; +/** + * 鎸夌収娴佺▼瀹氫箟key鑾峰彇娴佺▼瀹氫箟 + * @param processInstanceId 娴佺▼瀹炰緥id + * @returns + */ +export const getProcessDefinitionListByKey = (key: string) => { + return request({ + url: `/workflow/processDefinition/getProcessDefinitionListByKey/${key}`, + method: 'get' + }); +}; + +/** + * 閫氳繃娴佺▼瀹氫箟id鑾峰彇娴佺▼鍥� + */ +export const processDefinitionImage = (processDefinitionId: string): AxiosPromise<any> => { + return request({ + url: `/workflow/processDefinition/processDefinitionImage/${processDefinitionId}` + '?t' + Math.random(), + method: 'get' + }); +}; + +/** + * 閫氳繃娴佺▼瀹氫箟id鑾峰彇xml + * @param processDefinitionId 娴佺▼瀹氫箟id + * @returns + */ +export const processDefinitionXml = (processDefinitionId: string): AxiosPromise<ProcessDefinitionXmlVO> => { + return request({ + url: `/workflow/processDefinition/processDefinitionXml/${processDefinitionId}`, + method: 'get' + }); +}; + +/** + * 鍒犻櫎娴佺▼瀹氫箟 + * @param processDefinitionId 娴佺▼瀹氫箟id + * @param deploymentId 閮ㄧ讲id + * @returns + */ +export const deleteProcessDefinition = (deploymentId: string, processDefinitionId: string) => { + return request({ + url: `/workflow/processDefinition/${deploymentId}/${processDefinitionId}`, + method: 'delete' + }); +}; + +/** + * 鎸傝捣/婵�娲� + * @param processDefinitionId 娴佺▼瀹氫箟id + * @returns + */ +export const updateProcessDefState = (processDefinitionId: string) => { + return request({ + url: `/workflow/processDefinition/updateProcessDefState/${processDefinitionId}`, + method: 'put' + }); +}; + +/** + * 娴佺▼瀹氫箟杞崲涓烘ā鍨� + * @param processDefinitionId 娴佺▼瀹氫箟id + * @returns + */ +export const convertToModel = (processDefinitionId: string) => { + return request({ + url: `/workflow/processDefinition/convertToModel/${processDefinitionId}`, + method: 'put' + }); +}; + +/** + * 閫氳繃zip鎴杧ml閮ㄧ讲娴佺▼瀹氫箟 + * @returns + */ +export function deployProcessFile(data: any) { + return request({ + url: '/workflow/processDefinition/deployByFile', + method: 'post', + data: data + }); +} + +/** + * 杩佺Щ娴佺▼ + * @param currentProcessDefinitionId + * @param fromProcessDefinitionId + * @returns + */ +export const migrationProcessDefinition = (currentProcessDefinitionId: string, fromProcessDefinitionId: string) => { + return request({ + url: `/workflow/processDefinition/migrationProcessDefinition/${currentProcessDefinitionId}/${fromProcessDefinitionId}`, + method: 'put' + }); +}; + +/** + * 鏌ヨ娴佺▼瀹氫箟鍒楄〃 + * @returns + */ +export const getProcessDefinitionList = () => { + return request({ + url: `/workflow/processDefinition/getProcessDefinitionList`, + method: 'get' + }); +}; diff --git a/src/api/workflow/processDefinition/types.ts b/src/api/workflow/processDefinition/types.ts new file mode 100644 index 0000000..8987b33 --- /dev/null +++ b/src/api/workflow/processDefinition/types.ts @@ -0,0 +1,22 @@ +export interface ProcessDefinitionQuery extends PageQuery { + key?: string; + name?: string; + categoryCode?: string; +} + +export interface ProcessDefinitionVO extends BaseEntity { + id: string; + name: string; + key: string; + version: number; + suspensionState: number; + resourceName: string; + diagramResourceName: string; + deploymentId: string; + deploymentTime: string; +} + +export interface ProcessDefinitionXmlVO { + xml: string[]; + xmlStr: string; +} diff --git a/src/api/workflow/processInstance/index.ts b/src/api/workflow/processInstance/index.ts new file mode 100644 index 0000000..295fc0d --- /dev/null +++ b/src/api/workflow/processInstance/index.ts @@ -0,0 +1,115 @@ +import request from '@/utils/request'; +import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types'; +import { AxiosPromise } from 'axios'; +import { string } from 'vue-types'; +const baseUrl = import.meta.env.VITE_APP_BASE_API; + +/** + * 鏌ヨ杩愯涓疄渚嬪垪琛� + * @param query + * @returns {*} + */ +export const getProcessInstanceRunningByPage = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => { + return request({ + url: '/workflow/processInstance/getProcessInstanceRunningByPage', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ宸插畬鎴愬疄渚嬪垪琛� + * @param query + * @returns {*} + */ +export const getProcessInstanceFinishByPage = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => { + return request({ + url: '/workflow/processInstance/getProcessInstanceFinishByPage', + method: 'get', + params: query + }); +}; + +/** + * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥� + */ +export const getHistoryProcessImage = (processInstanceId: string) => { + return request({ + url: `/workflow/processInstance/getHistoryProcessImage/${processInstanceId}` + '?t' + Math.random(), + method: 'get' + }); +}; + +/** + * 鑾峰彇瀹℃壒璁板綍 + * @param processInstanceId 娴佺▼瀹炰緥id + * @returns + */ +export const getHistoryRecord = (processInstanceId: string) => { + return request({ + url: `/workflow/processInstance/getHistoryRecord/${processInstanceId}`, + method: 'get' + }); +}; + +/** + * 浣滃簾 + * @param data 鍙傛暟 + * @returns + */ +export const deleteRuntimeProcessInst = (data: object) => { + return request({ + url: `/workflow/processInstance/deleteRuntimeProcessInst`, + method: 'post', + data: data + }); +}; + +/** + * 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅 + * @param processInstanceId 娴佺▼瀹炰緥id + * @returns + */ +export const deleteRuntimeProcessAndHisInst = (processInstanceId: string | string[]) => { + return request({ + url: `/workflow/processInstance/deleteRuntimeProcessAndHisInst/${processInstanceId}`, + method: 'delete' + }); +}; + +/** + * 宸插畬鎴愮殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅 + * @param processInstanceId 娴佺▼瀹炰緥id + * @returns + */ +export const deleteFinishProcessAndHisInst = (processInstanceId: string | string[]) => { + return request({ + url: `/workflow/processInstance/deleteFinishProcessAndHisInst/${processInstanceId}`, + method: 'delete' + }); +}; + +/** + * 鍒嗛〉鏌ヨ褰撳墠鐧诲綍浜哄崟鎹� + * @param query + * @returns {*} + */ +export const getCurrentSubmitByPage = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => { + return request({ + url: '/workflow/processInstance/getCurrentSubmitByPage', + method: 'get', + params: query + }); +}; + +/** + * 鎾ら攢娴佺▼ + * @param processInstanceId 娴佺▼瀹炰緥id + * @returns + */ +export const cancelProcessApply = (processInstanceId: string) => { + return request({ + url: `/workflow/processInstance/cancelProcessApply/${processInstanceId}`, + method: 'post' + }); +}; diff --git a/src/api/workflow/processInstance/types.ts b/src/api/workflow/processInstance/types.ts new file mode 100644 index 0000000..99d0511 --- /dev/null +++ b/src/api/workflow/processInstance/types.ts @@ -0,0 +1,27 @@ +import { TaskVO } from '@/api/workflow/task/types'; + +export interface ProcessInstanceQuery extends PageQuery { + categoryCode?: string; + name?: string; + key?: string; + startUserId?: string; + businessKey?: string; +} + +export interface ProcessInstanceVO extends BaseEntity { + id: string; + processDefinitionId: string; + processDefinitionName: string; + processDefinitionKey: string; + processDefinitionVersion: string; + deploymentId: string; + businessKey: string; + isSuspended?: any; + tenantId: string; + startTime: string; + endTime?: string; + startUserId: string; + businessStatus: string; + businessStatusName: string; + taskVoList: TaskVO[]; +} diff --git a/src/api/workflow/task/index.ts b/src/api/workflow/task/index.ts new file mode 100644 index 0000000..7be76f7 --- /dev/null +++ b/src/api/workflow/task/index.ts @@ -0,0 +1,181 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { TaskQuery, TaskVO } from '@/api/workflow/task/types'; +/** + * 鏌ヨ寰呭姙鍒楄〃 + * @param query + * @returns {*} + */ +export const getTaskWaitByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => { + return request({ + url: '/workflow/task/getTaskWaitByPage', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ宸插姙鍒楄〃 + * @param query + * @returns {*} + */ +export const getTaskFinishByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => { + return request({ + url: '/workflow/task/getTaskFinishByPage', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫佸垪琛� + * @param query + * @returns {*} + */ +export const getTaskCopyByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => { + return request({ + url: '/workflow/task/getTaskCopyByPage', + method: 'get', + params: query + }); +}; + +/** + * 褰撳墠绉熸埛鎵�鏈夊緟鍔炰换鍔� + * @param query + * @returns {*} + */ +export const getAllTaskWaitByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => { + return request({ + url: '/workflow/task/getAllTaskWaitByPage', + method: 'get', + params: query + }); +}; + +/** + * 褰撳墠绉熸埛鎵�鏈夊凡鍔炰换鍔� + * @param query + * @returns {*} + */ +export const getAllTaskFinishByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => { + return request({ + url: '/workflow/task/getAllTaskFinishByPage', + method: 'get', + params: query + }); +}; + +/** + * 鍚姩娴佺▼ + * @param data + * @returns {*} + */ +export const startWorkFlow = (data: object) => { + return request({ + url: '/workflow/task/startWorkFlow', + method: 'post', + data: data + }); +}; + +/** + * 鍔炵悊娴佺▼ + * @param data + * @returns {*} + */ +export const completeTask = (data: object) => { + return request({ + url: '/workflow/task/completeTask', + method: 'post', + data: data + }); +}; + +/** + * 璁ら浠诲姟 + * @param taskId + * @returns {*} + */ +export const claim = (taskId: string) => { + return request({ + url: '/workflow/task/claim/' + taskId, + method: 'post' + }); +}; + +/** + * 褰掕繕浠诲姟 + * @param taskId + * @returns {*} + */ +export const returnTask = (taskId: string) => { + return request({ + url: '/workflow/task/returnTask/' + taskId, + method: 'post' + }); +}; + +/** + * 浠诲姟椹冲洖 + * @param taskId + * @returns {*} + */ +export const backProcess = (data: object) => { + return request({ + url: '/workflow/task/backProcess', + method: 'post', + data: data + }); +}; + +/** + * 鑾峰彇娴佺▼鐘舵�� + * @param taskId + * @returns + */ +export const getBusinessStatus = (taskId: string) => { + return request({ + url: '/workflow/task/getBusinessStatus/' + taskId, + method: 'get' + }); +}; + +/** + * 鍔犵 + * @param data + * @returns + */ +export const addMultiInstanceExecution = (data: object) => { + return request({ + url: '/workflow/task/addMultiInstanceExecution', + method: 'post', + data: data + }); +}; + +/** + * 鍑忕 + * @param data + * @returns + */ +export const deleteMultiInstanceExecution = (data: object) => { + return request({ + url: '/workflow/task/deleteMultiInstanceExecution', + method: 'post', + data: data + }); +}; + +/** + * 淇敼浠诲姟鍔炵悊浜� + * @param taskIds + * @param userId + * @returns + */ +export const updateAssignee = (taskIds: Array<string>,userId: string) => { + return request({ + url: `/workflow/task/updateAssignee/${taskIds}/${userId}`, + method: 'put' + }); +}; diff --git a/src/api/workflow/task/types.ts b/src/api/workflow/task/types.ts new file mode 100644 index 0000000..11cc48b --- /dev/null +++ b/src/api/workflow/task/types.ts @@ -0,0 +1,39 @@ +export interface TaskQuery extends PageQuery { + name?: string; + processDefinitionKey?: string; + processDefinitionName?: string; +} + +export interface ParticipantVo { + groupIds?: string[] | number[]; + candidate: string[] | number[]; + candidateName: string[]; + claim: boolean; +} + +export interface TaskVO extends BaseEntity { + id: string; + name: string; + description?: string; + priority: number; + owner?: string; + assignee?: string | number; + assigneeName?: string; + processInstanceId: string; + executionId: string; + taskDefinitionId?: any; + processDefinitionId: string; + endTime?: string; + taskDefinitionKey: string; + dueDate?: string; + category?: any; + parentTaskId?: any; + tenantId: string; + claimTime?: string; + businessStatus: string; + businessStatusName: string; + processDefinitionName: string; + processDefinitionKey: string; + participantVo: ParticipantVo; + multiInstance: boolean; +} diff --git a/src/api/workflow/workflowUser/index.ts b/src/api/workflow/workflowUser/index.ts new file mode 100644 index 0000000..e3ed8d9 --- /dev/null +++ b/src/api/workflow/workflowUser/index.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { UserVO } from '@/api/system/user/types'; + +/** + * 鍒嗛〉鏌ヨ宸ヤ綔娴侀�夋嫨鍔犵浜哄憳 + * @param query + * @returns {*} + */ +export const getWorkflowAddMultiListByPage = (query: object) => { + return request({ + url: '/workflow/user/getWorkflowAddMultiListByPage', + method: 'get', + params: query + }); +}; + +/** + * 鏌ヨ宸ヤ綔娴侀�夋嫨鍑忕浜哄憳 + * @param query + * @returns {*} + */ +export const getWorkflowDeleteMultiInstanceList = (taskId: string) => { + return request({ + url: '/workflow/user/getWorkflowDeleteMultiInstanceList/' + taskId, + method: 'get' + }); +}; + +/** + * 鎸夌収鐢ㄦ埛id鏌ヨ鐢ㄦ埛 + * @param userIdList + * @returns {*} + */ +export const getUserListByIds = (userIdList: any[]): AxiosPromise<UserVO[]> => { + return request({ + url: '/workflow/user/getUserListByIds/' + userIdList, + method: 'get' + }); +}; + +/** + * 鍒嗛〉鏌ヨ鐢ㄦ埛 + * @param query + * @returns {*} + */ +export const getUserListByPage = (query: object) => { + return request({ + url: '/workflow/user/getUserListByPage', + method: 'get', + params: query + }); +}; diff --git a/src/assets/icons/svg/caret-back.svg b/src/assets/icons/svg/caret-back.svg new file mode 100644 index 0000000..9bae722 --- /dev/null +++ b/src/assets/icons/svg/caret-back.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M321.94 98L158.82 237.78a24 24 0 000 36.44L321.94 414c15.57 13.34 39.62 2.28 39.62-18.22v-279.6c0-20.5-24.05-31.56-39.62-18.18z"/></svg> \ No newline at end of file diff --git a/src/assets/icons/svg/caret-forward.svg b/src/assets/icons/svg/caret-forward.svg new file mode 100644 index 0000000..1ec3f7d --- /dev/null +++ b/src/assets/icons/svg/caret-forward.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M190.06 414l163.12-139.78a24 24 0 000-36.44L190.06 98c-15.57-13.34-39.62-2.28-39.62 18.22v279.6c0 20.5 24.05 31.56 39.62 18.18z"/></svg> \ No newline at end of file diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss index 43c093c..87326ee 100644 --- a/src/assets/styles/element-ui.scss +++ b/src/assets/styles/element-ui.scss @@ -1,4 +1,15 @@ -// cover some element-ui styles + +.el-collapse { + .collapse__title { + font-weight: 600; + padding: 0 8px; + font-size: 1.2em; + line-height: 1.1em; + } + .el-collapse-item__content { + padding: 0 8px; + } +} .el-divider--horizontal { margin-bottom: 10px; @@ -68,6 +79,12 @@ .el-dialog__body { padding: 15px !important; } + .el-dialog__header { + padding: 16px 16px 8px 16px; + box-sizing: border-box; + border-bottom: 1px solid #e8e8e8; + margin-right: 0; + } } } } diff --git a/src/components/BpmnDesign/assets/defaultXML.ts b/src/components/BpmnDesign/assets/defaultXML.ts new file mode 100644 index 0000000..dff0349 --- /dev/null +++ b/src/components/BpmnDesign/assets/defaultXML.ts @@ -0,0 +1,23 @@ +function generateRandomValue() { + // 鐢熸垚涓�涓殢鏈烘暟 + const randomValue = Math.random().toString(36).slice(2, 12); + return `Process_${randomValue}`; +} + +const cartage: string = 'default'; +export default `<?xml version="1.0" encoding="UTF-8"?> +<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef"> + <process id="process_${generateRandomValue()}" name="name_${generateRandomValue()}"> + <startEvent id="startNode1" name="寮�濮�" /> + </process> + <bpmndi:BPMNDiagram id="BPMNDiagram_flow"> + <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c"> + <bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke=""> + <omgdc:Bounds x="240" y="200" width="30" height="30" /> + <bpmndi:BPMNLabel> + <omgdc:Bounds x="242" y="237" width="23" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNShape> + </bpmndi:BPMNPlane> + </bpmndi:BPMNDiagram> +</definitions>`; diff --git a/src/components/BpmnDesign/assets/lang/zh.ts b/src/components/BpmnDesign/assets/lang/zh.ts new file mode 100644 index 0000000..61921a5 --- /dev/null +++ b/src/components/BpmnDesign/assets/lang/zh.ts @@ -0,0 +1,118 @@ +export const NodeName = { + 'bpmn:Process': '娴佺▼', + 'bpmn:StartEvent': '寮�濮嬩簨浠�', + 'bpmn:IntermediateThrowEvent': '涓棿浜嬩欢', + 'bpmn:Task': '浠诲姟', + 'bpmn:SendTask': '鍙戦�佷换鍔�', + 'bpmn:ReceiveTask': '鎺ユ敹浠诲姟', + 'bpmn:UserTask': '鐢ㄦ埛浠诲姟', + 'bpmn:ManualTask': '鎵嬪伐浠诲姟', + 'bpmn:BusinessRuleTask': '涓氬姟瑙勫垯浠诲姟', + 'bpmn:ServiceTask': '鏈嶅姟浠诲姟', + 'bpmn:ScriptTask': '鑴氭湰浠诲姟', + 'bpmn:EndEvent': '缁撴潫浜嬩欢', + 'bpmn:SequenceFlow': '娴佺▼绾�', + 'bpmn:ExclusiveGateway': '浜掓枼缃戝叧', + 'bpmn:ParallelGateway': '骞惰缃戝叧', + 'bpmn:InclusiveGateway': '鐩稿缃戝叧', + 'bpmn:ComplexGateway': '澶嶆潅缃戝叧', + 'bpmn:EventBasedGateway': '浜嬩欢缃戝叧' +}; + +export default { + 'Activate hand tool': '鍚姩鎵嬪姩宸ュ叿', + 'Activate lasso tool': '鍚姩 Lasso 宸ュ叿', + 'Activate create/remove space tool': '鍚姩鍒涘缓/鍒犻櫎绌洪棿宸ュ叿', + 'Activate global connect tool': '鍚姩鍏ㄥ眬杩炴帴宸ュ叿', + 'Ad-hoc': 'Ad-hoc', + 'Add lane above': '鍦ㄤ笂鏂规坊鍔犳吵閬�', + 'Add lane below': '鍦ㄤ笅鏂规坊鍔犳吵閬�', + 'Business rule task': '瑙勫垯浠诲姟', + 'Call activity': '寮曠敤娴佺▼', + 'Compensation end event': '缁撴潫琛ュ伩浜嬩欢', + 'Compensation intermediate throw event': '涓棿琛ュ伩鎶涘嚭浜嬩欢', + 'Complex gateway': '澶嶆潅缃戝叧', + 'Conditional intermediate catch event': '涓棿鏉′欢鎹曡幏浜嬩欢', + 'Conditional start event (non-interrupting)': '鏉′欢鍚姩浜嬩欢 (闈炰腑鏂�)', + 'Conditional start event': '鏉′欢鍚姩浜嬩欢', + 'Connect using association': '鏂囨湰鍏宠仈', + 'Connect using sequence/message flow or association': '娑堟伅鍏宠仈', + 'Change element': '鏇存敼鍏冪礌', + 'Change type': '鏇存敼绫诲瀷', + 'Create data object reference': '鍒涘缓鏁版嵁瀵硅薄寮曠敤', + 'Create data store reference': '鍒涘缓鏁版嵁瀛樺偍寮曠敤', + 'Create expanded sub-process': '鍒涘缓鍙姌鍙犲瓙娴佺▼', + 'Create pool/participant': '鍒涘缓姹�/鍙備笌鑰�', + 'Collection': '闆嗗悎', + 'Connect using data input association': '鏁版嵁杈撳叆鍏宠仈', + 'Data store reference': '鏁版嵁瀛樺偍寮曠敤', + 'Data object reference': '鏁版嵁瀵硅薄寮曠敤', + 'Divide into two lanes': '鍒嗘垚涓や釜娉抽亾', + 'Divide into three lanes': '鍒嗘垚涓変釜娉抽亾', + 'End event': '缁撴潫浜嬩欢', + 'Error end event': '缁撴潫閿欒浜嬩欢', + 'Escalation end event': '缁撴潫鍗囩骇浜嬩欢', + 'Escalation intermediate throw event': '涓棿鍗囩骇鎶涘嚭浜嬩欢', + 'Event sub-process': '浜嬩欢瀛愭祦绋�', + 'Event-based gateway': '浜嬩欢缃戝叧', + 'Exclusive gateway': '浜掓枼缃戝叧', + 'Empty pool/participant (removes content)': '娓呯┖姹�/鍙備笌鑰� (鍒犻櫎鍐呭)', + 'Inclusive gateway': '鐩稿缃戝叧', + 'Intermediate throw event': '涓棿鎶涘嚭浜嬩欢', + 'Loop': '寰幆', + 'Link intermediate catch event': '涓棿閾炬帴鎹曡幏浜嬩欢', + 'Link intermediate throw event': '涓棿閾炬帴鎶涘嚭浜嬩欢', + 'Manual task': '鎵嬪姩浠诲姟', + 'Message end event': '缁撴潫娑堟伅浜嬩欢', + 'Message intermediate catch event': '涓棿娑堟伅鎹曡幏浜嬩欢', + 'Message intermediate throw event': '涓棿娑堟伅鎶涘嚭浜嬩欢', + 'Message start event': '娑堟伅鍚姩浜嬩欢', + 'Parallel gateway': '骞惰缃戝叧', + 'Parallel multi-instance': '骞惰澶氬疄渚�', + 'Participant multiplicity': '鍙備笌鑰呭閲嶆��', + 'Receive task': '鎺ュ彈浠诲姟', + 'Remove': '绉婚櫎', + 'Script task': '鑴氭湰浠诲姟', + 'Send task': '鍙戦�佷换鍔�', + 'Sequential multi-instance': '涓茶澶氬疄渚�', + 'Service task': '鏈嶅姟浠诲姟', + 'Signal end event': '缁撴潫淇″彿浜嬩欢', + 'Signal intermediate catch event': '涓棿淇″彿鎹曡幏浜嬩欢', + 'Signal intermediate throw event': '涓棿淇″彿鎶涘嚭浜嬩欢', + 'Signal start event (non-interrupting)': '淇″彿鍚姩浜嬩欢 (闈炰腑鏂�)', + 'Signal start event': '淇″彿鍚姩浜嬩欢', + 'Start event': '寮�濮嬩簨浠�', + 'Sub-process (collapsed)': '鍙姌鍙犲瓙娴佺▼', + 'Sub-process (expanded)': '鍙睍寮�瀛愭祦绋�', + 'Sub rocess': '瀛愭祦绋�', + 'Task': '浠诲姟', + 'Transaction': '浜嬪姟', + 'Terminate end event': '缁堟杈圭晫浜嬩欢', + 'Timer intermediate catch event': '涓棿瀹氭椂鎹曡幏浜嬩欢', + 'Timer start event (non-interrupting)': '瀹氭椂鍚姩浜嬩欢 (闈炰腑鏂�)', + 'Timer start event': '瀹氭椂鍚姩浜嬩欢', + 'User task': '鐢ㄦ埛浠诲姟', + 'Create start event': '鍒涘缓寮�濮嬩簨浠�', + 'Create gateway': '鍒涘缓缃戝叧', + 'Create intermediate/boundary event': '鍒涘缓涓棿/杈圭晫浜嬩欢', + 'Create end event': '鍒涘缓缁撴潫浜嬩欢', + 'Create group': '鍒涘缓缁�', + 'Create startEvent': '寮�濮嬭妭鐐�', + 'Create endEvent': '缁撴潫鑺傜偣', + 'Create exclusiveGateway': '浜掓枼缃戝叧', + 'Create parallelGateway': '骞惰缃戝叧', + 'Create task': '浠诲姟鑺傜偣', + 'Create userTask': '鐢ㄦ埛浠诲姟鑺傜偣', + 'Condition type': '鏉′欢绫诲瀷', + 'Append end event': '杩藉姞缁撴潫浜嬩欢鑺傜偣', + 'Append gateway': '杩藉姞缃戝叧鑺傜偣', + 'Append task': '杩藉姞浠诲姟', + 'Append user task': '杩藉姞鐢ㄦ埛浠诲姟鑺傜偣', + 'Append text annotation': '杩藉姞鏂囨湰娉ㄩ噴', + 'Append intermediate/boundary event': '杩藉姞涓棿鎴栬竟鐣屼簨浠�', + 'Append receive task': '杩藉姞鎺ユ敹浠诲姟鑺傜偣', + 'Append message intermediate catch event': '杩藉姞涓棿娑堟伅鎹曡幏浜嬩欢', + 'Append timer intermediate catch event': '杩藉姞涓棿瀹氭椂鎹曡幏浜嬩欢', + 'Append conditional intermediate catch event': '杩藉姞涓棿鏉′欢鎹曡幏浜嬩欢', + 'Append signal intermediate catch event': '杩藉姞涓棿淇″彿鎹曡幏浜嬩欢' +}; diff --git a/src/components/BpmnDesign/assets/moddle/flowable.ts b/src/components/BpmnDesign/assets/moddle/flowable.ts new file mode 100644 index 0000000..de959a6 --- /dev/null +++ b/src/components/BpmnDesign/assets/moddle/flowable.ts @@ -0,0 +1,1250 @@ +export default { + 'name': 'Flowable', + 'uri': 'http://flowable.org/bpmn', + 'prefix': 'flowable', + 'xml': { + 'tagAlias': 'lowerCase' + }, + 'associations': [], + 'types': [ + { + 'name': 'flowable:extCandidateUsers', + 'isAbstract': true, + 'extends': [], + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['*'] + }, + 'properties': [ + { + 'name': 'body', + 'type': 'String', + 'isBody': true + } + ] + }, + { + 'name': 'flowable:extAssignee', + 'isAbstract': true, + 'extends': [], + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['*'] + }, + 'properties': [ + { + 'name': 'body', + 'type': 'String', + 'isBody': true + } + ] + }, + { + 'name': 'flowable:property', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'id', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'name', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'value', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'flowable:properties', + 'isAbstract': true, + 'extends': [], + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['*'] + }, + 'properties': [ + { + 'name': 'values', + 'type': 'flowable:property', + 'isMany': true + } + ] + }, + { + 'name': 'InOutBinding', + 'superClass': ['Element'], + 'isAbstract': true, + 'properties': [ + { + 'name': 'source', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'sourceExpression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'target', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'businessKey', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'local', + 'isAttr': true, + 'type': 'Boolean', + 'default': false + }, + { + 'name': 'variables', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'In', + 'superClass': ['InOutBinding'], + 'meta': { + 'allowedIn': ['bpmn:CallActivity'] + } + }, + { + 'name': 'Out', + 'superClass': ['InOutBinding'], + 'meta': { + 'allowedIn': ['bpmn:CallActivity'] + } + }, + { + 'name': 'AsyncCapable', + 'isAbstract': true, + 'extends': ['bpmn:Activity', 'bpmn:Gateway', 'bpmn:Event'], + 'properties': [ + { + 'name': 'async', + 'isAttr': true, + 'type': 'Boolean', + 'default': false + }, + { + 'name': 'asyncBefore', + 'isAttr': true, + 'type': 'Boolean', + 'default': false + }, + { + 'name': 'asyncAfter', + 'isAttr': true, + 'type': 'Boolean', + 'default': false + }, + { + 'name': 'exclusive', + 'isAttr': true, + 'type': 'Boolean', + 'default': true + } + ] + }, + { + 'name': 'flowable:in', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'source', + 'type': 'string', + 'isAttr': true + }, + { + 'name': 'target', + 'type': 'string', + 'isAttr': true + } + ] + }, + { + 'name': 'flowable:out', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'source', + 'type': 'string', + 'isAttr': true + }, + { + 'name': 'target', + 'type': 'string', + 'isAttr': true + } + ] + }, + { + 'name': 'BoundaryEvent', + 'superClass': ['CatchEvent'], + 'properties': [ + { + 'name': 'cancelActivity', + 'default': true, + 'isAttr': true, + 'type': 'Boolean' + }, + { + 'name': 'attachedToRef', + 'type': 'Activity', + 'isAttr': true, + 'isReference': true + } + ] + }, + { + 'name': 'JobPriorized', + 'isAbstract': true, + 'extends': ['bpmn:Process', 'flowable:AsyncCapable'], + 'properties': [ + { + 'name': 'jobPriority', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'SignalEventDefinition', + 'isAbstract': true, + 'extends': ['bpmn:SignalEventDefinition'], + 'properties': [ + { + 'name': 'async', + 'isAttr': true, + 'type': 'Boolean', + 'default': false + } + ] + }, + { + 'name': 'ErrorEventDefinition', + 'isAbstract': true, + 'extends': ['bpmn:ErrorEventDefinition'], + 'properties': [ + { + 'name': 'errorCodeVariable', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'errorMessageVariable', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Error', + 'isAbstract': true, + 'extends': ['bpmn:Error'], + 'properties': [ + { + 'name': 'flowable:errorMessage', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'PotentialStarter', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'resourceAssignmentExpression', + 'type': 'bpmn:ResourceAssignmentExpression' + } + ] + }, + { + 'name': 'UserTask', + 'isAbstract': true, + 'extends': ['bpmn:UserTask'], + 'properties': [ + { + 'name': 'timerEventDefinition', + 'type': 'Expression' + }, + { + 'name': 'multiInstanceLoopCharacteristics', + 'type': 'MultiInstanceLoopCharacteristics' + } + ] + }, + { + 'name': 'StartEvent', + 'isAbstract': true, + 'extends': ['bpmn:StartEvent'], + 'properties': [ + { + 'name': 'timerEventDefinition', + 'type': 'Expression' + } + ] + }, + { + 'name': 'FormSupported', + 'isAbstract': true, + 'extends': ['bpmn:StartEvent', 'bpmn:UserTask'], + 'properties': [ + { + 'name': 'formHandlerClass', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'formKey', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'TemplateSupported', + 'isAbstract': true, + 'extends': ['bpmn:Process', 'bpmn:FlowElement'], + 'properties': [ + { + 'name': 'modelerTemplate', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Initiator', + 'isAbstract': true, + 'extends': ['bpmn:StartEvent'], + 'properties': [ + { + 'name': 'initiator', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'ScriptTask', + 'isAbstract': true, + 'extends': ['bpmn:ScriptTask'], + 'properties': [ + { + 'name': 'resultVariable', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'resource', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Process', + 'isAbstract': true, + 'extends': ['bpmn:Process'], + 'properties': [ + { + 'name': 'candidateStarterGroups', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'candidateStarterUsers', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'versionTag', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'historyTimeToLive', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'isStartableInTasklist', + 'isAttr': true, + 'type': 'Boolean', + 'default': true + } + ] + }, + { + 'name': 'EscalationEventDefinition', + 'isAbstract': true, + 'extends': ['bpmn:EscalationEventDefinition'], + 'properties': [ + { + 'name': 'escalationCodeVariable', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'FormalExpression', + 'isAbstract': true, + 'extends': ['bpmn:FormalExpression'], + 'properties': [ + { + 'name': 'resource', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Assignable', + 'extends': ['bpmn:UserTask'], + 'properties': [ + { + 'name': 'candidateGroups', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'dueDate', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'followUpDate', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'priority', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'CallActivity', + 'extends': ['bpmn:CallActivity'], + 'properties': [ + { + 'name': 'calledElementBinding', + 'isAttr': true, + 'type': 'String', + 'default': 'latest' + }, + { + 'name': 'calledElementVersion', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'calledElementVersionTag', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'calledElementTenantId', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'caseRef', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'caseBinding', + 'isAttr': true, + 'type': 'String', + 'default': 'latest' + }, + { + 'name': 'caseVersion', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'caseTenantId', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'variableMappingClass', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'variableMappingDelegateExpression', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'ServiceTaskLike', + 'extends': ['bpmn:ServiceTask', 'bpmn:BusinessRuleTask', 'bpmn:SendTask', 'bpmn:MessageEventDefinition'], + 'properties': [ + { + 'name': 'expression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'class', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'delegateExpression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'resultVariable', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'ExclusiveGateway', + 'isAbstract': true, + 'extends': ['bpmn:ExclusiveGateway'], + 'properties': [ + { + 'name': 'serviceClass', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'DmnCapable', + 'extends': ['bpmn:BusinessRuleTask'], + 'properties': [ + { + 'name': 'decisionRef', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'decisionRefBinding', + 'isAttr': true, + 'type': 'String', + 'default': 'latest' + }, + { + 'name': 'decisionRefVersion', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'mapDecisionResult', + 'isAttr': true, + 'type': 'String', + 'default': 'resultList' + }, + { + 'name': 'decisionRefTenantId', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'ExternalCapable', + 'extends': ['flowable:ServiceTaskLike'], + 'properties': [ + { + 'name': 'type', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'topic', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'TaskPriorized', + 'extends': ['bpmn:Process', 'flowable:ExternalCapable'], + 'properties': [ + { + 'name': 'taskPriority', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Properties', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['*'] + }, + 'properties': [ + { + 'name': 'values', + 'type': 'Property', + 'isMany': true + } + ] + }, + { + 'name': 'Property', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'id', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'name', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'value', + 'type': 'String', + 'isAttr': true + } + ] + }, + { + 'name': 'Connector', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['flowable:ServiceTaskLike'] + }, + 'properties': [ + { + 'name': 'inputOutput', + 'type': 'InputOutput' + }, + { + 'name': 'connectorId', + 'type': 'String' + } + ] + }, + { + 'name': 'InputOutput', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['bpmn:FlowNode', 'flowable:Connector'] + }, + 'properties': [ + { + 'name': 'inputOutput', + 'type': 'InputOutput' + }, + { + 'name': 'connectorId', + 'type': 'String' + }, + { + 'name': 'inputParameters', + 'isMany': true, + 'type': 'InputParameter' + }, + { + 'name': 'outputParameters', + 'isMany': true, + 'type': 'OutputParameter' + } + ] + }, + { + 'name': 'InputOutputParameter', + 'properties': [ + { + 'name': 'name', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'value', + 'isBody': true, + 'type': 'String' + }, + { + 'name': 'definition', + 'type': 'InputOutputParameterDefinition' + } + ] + }, + { + 'name': 'InputOutputParameterDefinition', + 'isAbstract': true + }, + { + 'name': 'List', + 'superClass': ['InputOutputParameterDefinition'], + 'properties': [ + { + 'name': 'items', + 'isMany': true, + 'type': 'InputOutputParameterDefinition' + } + ] + }, + { + 'name': 'Map', + 'superClass': ['InputOutputParameterDefinition'], + 'properties': [ + { + 'name': 'entries', + 'isMany': true, + 'type': 'Entry' + } + ] + }, + { + 'name': 'Entry', + 'properties': [ + { + 'name': 'key', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'value', + 'isBody': true, + 'type': 'String' + }, + { + 'name': 'definition', + 'type': 'InputOutputParameterDefinition' + } + ] + }, + { + 'name': 'Value', + 'superClass': ['InputOutputParameterDefinition'], + 'properties': [ + { + 'name': 'id', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'name', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'value', + 'isBody': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Script', + 'superClass': ['InputOutputParameterDefinition'], + 'properties': [ + { + 'name': 'scriptFormat', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'resource', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'value', + 'isBody': true, + 'type': 'String' + } + ] + }, + { + 'name': 'Field', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['flowable:ServiceTaskLike', 'flowable:ExecutionListener', 'flowable:TaskListener'] + }, + 'properties': [ + { + 'name': 'name', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'expression', + 'isAttr': true, + 'type': 'expression' + }, + { + 'name': 'string', + 'type': 'string' + }, + { + 'name': 'stringValue', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'string', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['flowable:Field'] + }, + 'properties': [ + { + 'name': 'body', + 'isBody': true, + 'type': 'String' + } + ] + }, + { + 'name': 'expression', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['flowable:Field'] + }, + 'properties': [ + { + 'name': 'body', + 'isBody': true, + 'type': 'String' + } + ] + }, + { + 'name': 'InputParameter', + 'superClass': ['InputOutputParameter'] + }, + { + 'name': 'OutputParameter', + 'superClass': ['InputOutputParameter'] + }, + { + 'name': 'Collectable', + 'isAbstract': true, + 'extends': ['bpmn:MultiInstanceLoopCharacteristics'], + 'superClass': ['flowable:AsyncCapable'], + 'properties': [ + { + 'name': 'collection', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'elementVariable', + 'isAttr': true, + 'type': 'String' + } + ] + }, + { + 'name': 'SequenceFlow', + 'superClass': ['FlowElement'], + 'properties': [ + { + 'name': 'isImmediate', + 'isAttr': true, + 'type': 'Boolean' + }, + { + 'name': 'conditionExpression', + 'type': 'Expression' + }, + { + 'name': 'sourceRef', + 'type': 'FlowNode', + 'isAttr': true, + 'isReference': true + }, + { + 'name': 'targetRef', + 'type': 'FlowNode', + 'isAttr': true, + 'isReference': true + } + ] + }, + { + 'name': 'MultiInstanceLoopCharacteristics', + 'superClass': ['LoopCharacteristics'], + 'properties': [ + { + 'name': 'isSequential', + 'default': false, + 'isAttr': true, + 'type': 'Boolean' + }, + { + 'name': 'behavior', + 'type': 'MultiInstanceBehavior', + 'default': 'All', + 'isAttr': true + }, + { + 'name': 'loopCardinality', + 'type': 'Expression', + 'xml': { + 'serialize': 'xsi:type' + } + }, + { + 'name': 'loopDataInputRef', + 'type': 'ItemAwareElement', + 'isReference': true + }, + { + 'name': 'loopDataOutputRef', + 'type': 'ItemAwareElement', + 'isReference': true + }, + { + 'name': 'inputDataItem', + 'type': 'DataInput', + 'xml': { + 'serialize': 'property' + } + }, + { + 'name': 'outputDataItem', + 'type': 'DataOutput', + 'xml': { + 'serialize': 'property' + } + }, + { + 'name': 'complexBehaviorDefinition', + 'type': 'ComplexBehaviorDefinition', + 'isMany': true + }, + { + 'name': 'completionCondition', + 'type': 'Expression', + 'xml': { + 'serialize': 'xsi:type' + } + }, + { + 'name': 'oneBehaviorEventRef', + 'type': 'EventDefinition', + 'isAttr': true, + 'isReference': true + }, + { + 'name': 'noneBehaviorEventRef', + 'type': 'EventDefinition', + 'isAttr': true, + 'isReference': true + } + ] + }, + { + 'name': 'FailedJobRetryTimeCycle', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['flowable:AsyncCapable', 'bpmn:MultiInstanceLoopCharacteristics'] + }, + 'properties': [ + { + 'name': 'body', + 'isBody': true, + 'type': 'String' + } + ] + }, + { + 'name': 'ExecutionListener', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': [ + 'bpmn:Task', + 'bpmn:ServiceTask', + 'bpmn:UserTask', + 'bpmn:BusinessRuleTask', + 'bpmn:ScriptTask', + 'bpmn:ReceiveTask', + 'bpmn:ManualTask', + 'bpmn:ExclusiveGateway', + 'bpmn:SequenceFlow', + 'bpmn:ParallelGateway', + 'bpmn:InclusiveGateway', + 'bpmn:EventBasedGateway', + 'bpmn:StartEvent', + 'bpmn:IntermediateCatchEvent', + 'bpmn:IntermediateThrowEvent', + 'bpmn:EndEvent', + 'bpmn:BoundaryEvent', + 'bpmn:CallActivity', + 'bpmn:SubProcess', + 'bpmn:Process' + ] + }, + 'properties': [ + { + 'name': 'expression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'class', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'delegateExpression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'event', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'script', + 'type': 'Script' + }, + { + 'name': 'fields', + 'type': 'Field', + 'isMany': true + } + ] + }, + { + 'name': 'TaskListener', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['bpmn:UserTask'] + }, + 'properties': [ + { + 'name': 'expression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'class', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'delegateExpression', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'event', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'script', + 'type': 'Script' + }, + { + 'name': 'fields', + 'type': 'Field', + 'isMany': true + } + ] + }, + { + 'name': 'FormProperty', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['bpmn:StartEvent', 'bpmn:UserTask'] + }, + 'properties': [ + { + 'name': 'id', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'name', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'type', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'required', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'readable', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'writable', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'variable', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'expression', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'datePattern', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'default', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'values', + 'type': 'Value', + 'isMany': true + } + ] + }, + { + 'name': 'FormData', + 'superClass': ['Element'], + 'meta': { + 'allowedIn': ['bpmn:StartEvent', 'bpmn:UserTask'] + }, + 'properties': [ + { + 'name': 'fields', + 'type': 'FormField', + 'isMany': true + }, + { + 'name': 'businessKey', + 'type': 'String', + 'isAttr': true + } + ] + }, + { + 'name': 'FormField', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'id', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'label', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'type', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'datePattern', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'defaultValue', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'properties', + 'type': 'Properties' + }, + { + 'name': 'validation', + 'type': 'Validation' + }, + { + 'name': 'values', + 'type': 'Value', + 'isMany': true + } + ] + }, + { + 'name': 'Validation', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'constraints', + 'type': 'Constraint', + 'isMany': true + } + ] + }, + { + 'name': 'Constraint', + 'superClass': ['Element'], + 'properties': [ + { + 'name': 'name', + 'type': 'String', + 'isAttr': true + }, + { + 'name': 'config', + 'type': 'String', + 'isAttr': true + } + ] + }, + { + 'name': 'ConditionalEventDefinition', + 'isAbstract': true, + 'extends': ['bpmn:ConditionalEventDefinition'], + 'properties': [ + { + 'name': 'variableName', + 'isAttr': true, + 'type': 'String' + }, + { + 'name': 'variableEvent', + 'isAttr': true, + 'type': 'String' + } + ] + } + ], + 'emumerations': [] +}; diff --git a/src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts b/src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts new file mode 100644 index 0000000..9f12ff2 --- /dev/null +++ b/src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts @@ -0,0 +1,138 @@ +import ContextPadProvider from 'bpmn-js/lib/features/context-pad/ContextPadProvider'; +import { Injector } from 'didi'; +import EventBus from 'diagram-js/lib/core/EventBus'; +import ContextPad from 'diagram-js/lib/features/context-pad/ContextPad'; +import Modeling from 'bpmn-js/lib/features/modeling/Modeling.js'; +import ElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory'; +import Connect from 'diagram-js/lib/features/connect/Connect'; +import Create from 'diagram-js/lib/features/create/Create'; +import PopupMenu from 'diagram-js/lib/features/popup-menu/PopupMenu'; +import Canvas from 'diagram-js/lib/core/Canvas'; +import Rules from 'diagram-js/lib/features/rules/Rules'; +import { Element, Shape } from 'diagram-js/lib/model/Types'; +import BpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory'; +import modeler from '@/store/modules/modeler'; + +// @Description: 澧炲己鍏冪礌杩炵嚎浜嬩欢 + +class CustomContextPadProvider extends ContextPadProvider { + private _contextPad: ContextPad; + private _modeling: Modeling; + private _elementFactory: ElementFactory; + private _autoPlace: any; + private _connect: Connect; + private _create: Create; + private _popupMenu: PopupMenu; + private _canvas: Canvas; + private _rules: Rules; + + constructor( + config: any, + injector: Injector, + eventBus: EventBus, + contextPad: ContextPad, + modeling: Modeling, + elementFactory: ElementFactory, + connect: Connect, + create: Create, + popupMenu: PopupMenu, + canvas: Canvas, + rules: Rules, + translate + ) { + // @ts-ignore + super(config, injector, eventBus, contextPad, modeling, elementFactory, connect, create, popupMenu, canvas, rules, translate); + + this._contextPad = contextPad; + this._modeling = modeling; + this._elementFactory = elementFactory; + this._connect = connect; + this._create = create; + this._popupMenu = popupMenu; + this._canvas = canvas; + this._rules = rules; + + this._autoPlace = injector.get('autoPlace', false); + } + + getContextPadEntries(element: Element) { + const actions: Record<string, any> = {}; + + const appendUserTask = (event: Event, element: Shape) => { + const shape = this._elementFactory.createShape({ type: 'bpmn:UserTask' }); + this._create.start(event, shape, { + source: element + }); + }; + + const appendMultiInstanceUserTask = (event: Event, element: Shape) => { + const store = modeler(); + const bpmnFactory = store.getModeler().get('bpmnFactory') as BpmnFactory; + const businessObject = bpmnFactory.create('bpmn:UserTask', { + // name: '澶氬疄渚嬬敤鎴蜂换鍔�', + isForCompensation: false + }); + businessObject.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics'); + // 鍒涘缓 Shape + const shape = this._elementFactory.createShape({ + type: 'bpmn:UserTask', + businessObject: businessObject + }); + this._create.start(event, shape, { source: element }); + }; + + const appendTask = this._autoPlace + ? (event, element) => { + const bpmnFactory: BpmnFactory | undefined = modeler().getModeler().get('bpmnFactory'); + const businessObject = bpmnFactory.create('bpmn:UserTask', { + // name: '澶氬疄渚嬬敤鎴蜂换鍔�',// 鍙抽敭鍒涘缓鏄剧ず + isForCompensation: false + }); + + // 鍒涘缓澶氬疄渚嬪睘鎬у苟鍒嗛厤缁欑敤鎴蜂换鍔$殑 loopCharacteristics + businessObject.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics'); + + // 鍒涘缓 Shape + const shape = this._elementFactory.createShape({ + type: 'bpmn:UserTask', + businessObject: businessObject + }); + + this._autoPlace.append(element, shape); + } + : appendMultiInstanceUserTask; + + const append = this._autoPlace + ? (event: Event, element: Shape) => { + const shape = this._elementFactory.createShape({ type: 'bpmn:UserTask' }); + this._autoPlace.append(element, shape); + } + : appendUserTask; + + // // 娣诲姞鍒涘缓鐢ㄦ埛浠诲姟鎸夐挳 + actions['append.append-user-task'] = { + group: 'model', + className: 'bpmn-icon-user-task', + title: '鐢ㄦ埛浠诲姟', + action: { + dragstart: appendUserTask, + click: append + } + }; + + // 娣诲姞鍒涘缓澶氬疄渚嬬敤鎴蜂换鍔℃寜閽� + actions['append.append-multi-instance-user-task'] = { + group: 'model', + className: 'bpmn-icon-user', // 浣犲彲浠ヤ娇鐢ㄥ瀹炰緥鐢ㄦ埛浠诲姟鐨勫浘鏍� bpmn-icon-user bpmn-icon-user-task + title: '澶氬疄渚嬬敤鎴蜂换鍔�', + action: { + dragstart: appendMultiInstanceUserTask, + click: appendTask + } + }; + + return actions; + } +} + +export default CustomContextPadProvider; diff --git a/src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts b/src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts new file mode 100644 index 0000000..38f1cea --- /dev/null +++ b/src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts @@ -0,0 +1,119 @@ +import { assign } from 'min-dash'; +import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider'; +import ElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory'; +import Create from 'diagram-js/lib/features/create/Create'; +import SpaceTool from 'diagram-js/lib/features/space-tool/SpaceTool'; +import LassoTool from 'diagram-js/lib/features/lasso-tool/LassoTool'; +import HandTool from 'diagram-js/lib/features/hand-tool/HandTool'; +import GlobalConnect from 'diagram-js/lib/features/global-connect/GlobalConnect'; +import Palette from 'diagram-js/lib/features/palette/Palette'; +import modeler from '@/store/modules/modeler'; +import BpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory'; + +// @Description: 澧炲己宸︿晶闈㈡澘 +class CustomPaletteProvider extends PaletteProvider { + private readonly _palette: Palette; + private readonly _create: Create; + private readonly _elementFactory: ElementFactory; + private readonly _spaceTool: SpaceTool; + private readonly _lassoTool: LassoTool; + private readonly _handTool: HandTool; + private readonly _globalConnect: GlobalConnect; + private readonly _translate: any; + + constructor(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) { + super( + palette, + create, + elementFactory, + spaceTool, + lassoTool, + handTool, + globalConnect, + translate + // 2000 + ); + this._palette = palette; + this._create = create; + this._elementFactory = elementFactory; + this._spaceTool = spaceTool; + this._lassoTool = lassoTool; + this._handTool = handTool; + this._globalConnect = globalConnect; + this._translate = translate; + } + + getPaletteEntries() { + const actions = {}, + create = this._create, + elementFactory = this._elementFactory, + translate = this._translate; + + function createAction(type: string, group: string, className: string, title: string, options?: object) { + function createListener(event) { + const shape = elementFactory.createShape(assign({ type: type }, options)); + if (options) { + !shape.businessObject.di && (shape.businessObject.di = {}); + shape.businessObject.di.isExpanded = (options as { [key: string]: any }).isExpanded; + } + create.start(event, shape, null); + } + const shortType = type.replace(/^bpmn:/, ''); + return { + group: group, + className: className, + title: title || translate('Create {type}', { type: shortType }), + action: { + dragstart: createListener, + click: createListener + } + }; + } + + function createMultiInstanceUserTask(event) { + const bpmnFactory: BpmnFactory | undefined = modeler().getBpmnFactory(); + // 鍒涘缓涓�涓� bpmn:UserTask + const userTask = bpmnFactory.create('bpmn:UserTask', { + // name: '澶氬疄渚嬬敤鎴蜂换鍔�', // 鍦ㄧ敾鏉夸腑鏄剧ず瀛楁 + isForCompensation: false + }); + // 灏嗗瀹炰緥灞炴�у垎閰嶇粰 bpmn:UserTask 鐨� loopCharacteristics + userTask.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics'); + const customUserTask = elementFactory.createShape({ + type: 'bpmn:UserTask', + businessObject: userTask // 鍒嗛厤鍒涘缓鐨� userTask 鍒� businessObject + }); + create.start(event, customUserTask, {}); + } + + assign(actions, { + 'create.parallel-gateway': createAction('bpmn:ParallelGateway', 'gateway', 'bpmn-icon-gateway-parallel', '骞惰缃戝叧'), + 'create.event-base-gateway': createAction('bpmn:EventBasedGateway', 'gateway', 'bpmn-icon-gateway-eventbased', '浜嬩欢缃戝叧'), + // 鍒嗙粍绾� + 'gateway-separator': { + group: 'gateway', + separator: true + }, + 'create.user-task': createAction('bpmn:UserTask', 'activity', 'bpmn-icon-user-task', '鍒涘缓鐢ㄦ埛浠诲姟'), + 'create.multi-instance-user-task': { + group: 'activity', + type: 'bpmn:UserTask', + className: 'bpmn-icon-user task-multi-instance', + title: '鍒涘缓澶氬疄渚嬬敤鎴蜂换鍔�', + action: { + click: createMultiInstanceUserTask, + dragstart: createMultiInstanceUserTask + } + }, + 'task-separator': { + group: 'activity', + separator: true + } + }); + return actions; + } +} + +CustomPaletteProvider['$inject'] = ['palette', 'create', 'elementFactory', 'spaceTool', 'lassoTool', 'handTool', 'globalConnect', 'translate']; + +export default CustomPaletteProvider; diff --git a/src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts b/src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts new file mode 100644 index 0000000..6a4eb1a --- /dev/null +++ b/src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts @@ -0,0 +1,56 @@ +import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'; +import { + append as svgAppend, + attr as svgAttr, + create as svgCreate, + select as svgSelect, + selectAll as svgSelectAll, + clone as svgClone, + clear as svgClear, + remove as svgRemove +} from 'tiny-svg'; + +const HIGH_PRIORITY = 1500; +export default class CustomRenderer extends BaseRenderer { + bpmnRenderer: BaseRenderer; + modeling: any; + constructor(eventBus, bpmnRenderer, modeling) { + super(eventBus, HIGH_PRIORITY); + this.bpmnRenderer = bpmnRenderer; + this.modeling = modeling; + } + canRender(element) { + // ignore labels + return !element.labelTarget; + } + + /** + * 鑷畾涔夎妭鐐瑰浘褰� + * @param {*} parentNode 褰撳墠鍏冪礌鐨剆vgNode + * @param {*} element + * @returns + */ + drawShape(parentNode, element) { + const shape = this.bpmnRenderer.drawShape(parentNode, element); + const { type, width, height } = element; + // 寮�濮� 濉厖缁胯壊 + if (type === 'bpmn:StartEvent') { + svgAttr(shape, { fill: '#77DF6D' }); + return shape; + } + if (type === 'bpmn:EndEvent') { + svgAttr(shape, { fill: '#EE7B77' }); + return shape; + } + if (type === 'bpmn:UserTask') { + svgAttr(shape, { fill: '#A9C4F8' }); + return shape; + } + return shape; + } + + getShapePath(shape) { + return this.bpmnRenderer.getShapePath(shape); + } +} +CustomRenderer['$inject'] = ['eventBus', 'bpmnRenderer']; diff --git a/src/components/BpmnDesign/assets/module/Translate/index.ts b/src/components/BpmnDesign/assets/module/Translate/index.ts new file mode 100644 index 0000000..7324c77 --- /dev/null +++ b/src/components/BpmnDesign/assets/module/Translate/index.ts @@ -0,0 +1,15 @@ +import zh from '@/components/BpmnDesign/assets/lang/zh'; + +const customTranslate = (template: any, replacements: any) => { + replacements = replacements || {}; + template = zh[template] || template; + return template.replace(/{([^}]+)}/g, function (_: any, key: any) { + return replacements[key] || '{' + key + '}'; + }); +}; + +export const translateModule = { + translate: ['value', customTranslate] +}; + +export default translateModule; diff --git a/src/components/BpmnDesign/assets/module/index.ts b/src/components/BpmnDesign/assets/module/index.ts new file mode 100644 index 0000000..55f6b9f --- /dev/null +++ b/src/components/BpmnDesign/assets/module/index.ts @@ -0,0 +1,17 @@ +// 缈昏瘧妯″潡 +import TranslationModule from './Translate'; +import { ModuleDeclaration } from 'didi'; +import CustomPaletteProvider from './Palette/CustomPaletteProvider'; +import CustomRenderer from './Renderer/CustomRenderer'; +import CustomContextPadProvider from './ContextPad/CustomContextPadProvider'; + +const Module: ModuleDeclaration[] = [ + { + __init__: ['customPaletteProvider', 'customContextPadProvider', 'customRenderer'], + customPaletteProvider: ['type', CustomPaletteProvider], + customRenderer: ['type', CustomRenderer], + customContextPadProvider: ['type', CustomContextPadProvider] + }, + TranslationModule +]; +export default Module; diff --git a/src/components/BpmnDesign/assets/showConfig.ts b/src/components/BpmnDesign/assets/showConfig.ts new file mode 100644 index 0000000..853eb35 --- /dev/null +++ b/src/components/BpmnDesign/assets/showConfig.ts @@ -0,0 +1,50 @@ +export default { + 'bpmn:EndEvent': {}, + 'bpmn:StartEvent': { + initiator: true, + formKey: true + }, + 'bpmn:UserTask': { + allocationType: true, + specifyDesc: true, + multipleUserAuditType: true, + async: true, + priority: true, + skipExpression: true, + dueDate: true, + taskListener: true, + executionListener: true + }, + 'bpmn:ServiceTask': { + async: true, + skipExpression: true, + isForCompensation: true, + triggerable: true, + class: true + }, + 'bpmn:ScriptTask': { + async: true, + isForCompensation: true, + autoStoreVariables: true + }, + 'bpmn:ManualTask': { + async: true, + isForCompensation: true + }, + 'bpmn:ReceiveTask': { + async: true, + isForCompensation: true + }, + 'bpmn:SendTask': { + async: true, + isForCompensation: true + }, + 'bpmn:BusinessRuleTask': { + async: true, + isForCompensation: true, + ruleVariablesInput: true, + rules: true, + resultVariable: true, + exclude: true + } +}; diff --git a/src/components/BpmnDesign/assets/style/index.scss b/src/components/BpmnDesign/assets/style/index.scss new file mode 100644 index 0000000..d3ce379 --- /dev/null +++ b/src/components/BpmnDesign/assets/style/index.scss @@ -0,0 +1,230 @@ +.djs-palette { + width: 300px; + + .bpmn-icon-hand-tool:hover { + &:after { + content: '鍚姩鎵嬪姩宸ュ叿'; + position: absolute; + left: 45px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-lasso-tool:hover { + &:after { + content: '鍚姩濂楃储宸ュ叿'; + position: absolute; + left: 100px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-space-tool:hover { + &:after { + content: '鍚姩鍒涘缓/鍒犻櫎绌洪棿宸ュ叿'; + position: absolute; + left: 45px; + width: 170px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-connection-multi:hover { + &:after { + content: '鍚姩鍏ㄥ眬杩炴帴宸ュ叿'; + position: absolute; + left: 100px; + width: 140px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-start-event-none:hover { + &:after { + content: '鍒涘缓寮�濮嬩簨浠�'; + position: absolute; + left: 45px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-intermediate-event-none:hover { + &:after { + content: '鍒涘缓涓棿/杈圭晫浜嬩欢'; + position: absolute; + left: 100px; + width: 140px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-end-event-none:hover { + &:after { + content: '鍒涘缓缁撴潫浜嬩欢'; + position: absolute; + left: 45px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-gateway-none:hover { + &:after { + content: '鍒涘缓缃戝叧'; + position: absolute; + left: 100px; + width: 90px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-gateway-parallel:hover { + &:after { + content: '鍒涘缓骞惰缃戝叧'; + position: absolute; + left: 45px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-gateway-eventbased:hover { + &:after { + content: '鍒涘缓浜嬩欢缃戝叧'; + position: absolute; + left: 100px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-task:hover { + &:after { + content: '鍒涘缓浠诲姟'; + position: absolute; + left: 45px; + width: 80px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-subprocess-expanded:hover { + &:after { + content: '鍒涘缓鍙姌鍙犲瓙娴佺▼'; + position: absolute; + left: 100px; + width: 140px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-user-task:hover { + &:after { + content: '鍒涘缓鐢ㄦ埛浠诲姟'; + position: absolute; + left: 45px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + + .task-multi-instance:hover { + &:after { + content: '鍒涘缓澶氬疄渚嬬敤鎴蜂换鍔�'; + position: absolute; + left: 100px; + width: 160px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } + .bpmn-icon-participant:hover { + &:after { + content: '鍒涘缓娉虫睜/娉抽亾'; + position: absolute; + left: 45px; + width: 120px; + font-size: 15px; + font-weight: bold; + color: #3a84de; + border-radius: 2px; + border: 1px solid #cccccc; + background-color: #fafafa; + opacity: 0.8; + } + } +} diff --git a/src/components/BpmnDesign/hooks/usePanel.ts b/src/components/BpmnDesign/hooks/usePanel.ts new file mode 100644 index 0000000..0dbc3f3 --- /dev/null +++ b/src/components/BpmnDesign/hooks/usePanel.ts @@ -0,0 +1,132 @@ +import showConfig from '@/components/BpmnDesign/assets/showConfig'; +import { ModdleElement } from 'bpmn'; +import useModelerStore from '@/store/modules/modeler'; +interface Options { + element: ModdleElement; +} + +export default (ops: Options) => { + const { element } = ops; + const { getModeling, getModdle } = useModelerStore(); + const modeling = getModeling(); + const moddle = getModdle(); + + /** + * 褰撳墠鑺傜偣绫诲瀷 + */ + const elementType = computed(() => { + const bizObj = element.businessObject; + return bizObj.eventDefinitions ? bizObj.eventDefinitions[0].$type : bizObj.$type; + }); + + /** + * 鐢ㄤ簬鎺у埗闈㈡澘瀛楁鏄剧ず涓庨殣钘忕殑閰嶇疆 + */ + const config = computed(() => showConfig[elementType.value] || {}); + + /** + * 鍒涘缓涓�涓妭鐐� + * @param elementType 鑺傜偣绫诲瀷 + * @param properties 灞炴�� + * @param parent 鐖惰妭鐐� + */ + const createModdleElement = (elementType: string, properties: any, parent: ModdleElement) => { + const element = moddle.create(elementType, properties); + parent && (element.$parent = parent); + return element; + }; + + /** + * 鑾峰彇鎵╁睍灞炴�э紝濡傛灉涓嶅瓨鍦ㄤ細鑷姩鍒涘缓 + */ + const getExtensionElements = (create = true) => { + let extensionElements = element.businessObject.get<ModdleElement>('extensionElements'); + if (!extensionElements && create) { + extensionElements = createModdleElement('bpmn:ExtensionElements', { values: [] }, element.businessObject); + modeling.updateModdleProperties(element, element.businessObject, { extensionElements }); + } + return extensionElements; + }; + + /** + * 鑾峰彇extensionElements涓嬬殑properties + * @param extensionElements 鍙�夊弬鏁帮紝榛樿鑾峰彇褰撳墠Element涓嬬殑extensionElements涓嬬殑Properties + */ + const getPropertiesElements = (extensionElements?: ModdleElement) => { + if (!extensionElements) { + extensionElements = getExtensionElements(); + } + let propertiesElements = extensionElements.values.find((item) => item.$type === 'flowable:properties'); + if (!propertiesElements) { + propertiesElements = createModdleElement('flowable:properties', { values: [] }, extensionElements); + modeling.updateModdleProperties(element, extensionElements, { + values: [...extensionElements.get<[]>('values'), propertiesElements] + }); + } + return propertiesElements; + }; + + /** + * 鏇存柊鑺傜偣灞炴�� + * @param properties 灞炴�у�� + */ + const updateProperties = (properties: any) => { + modeling.updateProperties(element, properties); + }; + + /** + * 鏇存柊鑺傜偣淇℃伅 + * @param updateElement 闇�瑕佹洿鏂扮殑鑺傜偣 + * @param properties 灞炴�� + */ + const updateModdleProperties = (updateElement, properties: any) => { + modeling.updateModdleProperties(element, updateElement, properties); + }; + + /** + * 鏇存柊Property灞炴�� + * @param name key鍊� + * @param value 鍊� + */ + const updateProperty = (name: string, value: string) => { + const propertiesElements = getPropertiesElements(); + + let propertyElements = propertiesElements.values.find((item) => item.name === name); + if (!propertyElements) { + propertyElements = createModdleElement('flowable:property', { name: name, value: value }, propertiesElements); + modeling.updateModdleProperties(element, propertiesElements, { + values: [...propertiesElements.get('values'), propertyElements] + }); + } else { + propertyElements.name = name; + propertyElements.value = value; + } + return propertyElements; + }; + + const idChange = (newVal: string) => { + if (newVal) { + updateProperties({ id: newVal }); + } + }; + const nameChange = (newVal: string) => { + if (newVal) { + updateProperties({ name: newVal }); + } + }; + return { + elementType, + showConfig: config, + + updateProperties, + updateProperty, + updateModdleProperties, + + createModdleElement, + idChange, + nameChange, + + getExtensionElements, + getPropertiesElements + }; +}; diff --git a/src/components/BpmnDesign/hooks/useParseElement.ts b/src/components/BpmnDesign/hooks/useParseElement.ts new file mode 100644 index 0000000..a5a255d --- /dev/null +++ b/src/components/BpmnDesign/hooks/useParseElement.ts @@ -0,0 +1,34 @@ +import { ModdleElement } from 'bpmn'; + +interface Options { + element: ModdleElement; +} + +interface Data { + id: string; +} + +export default (ops: Options) => { + const { element } = ops; + + const parseData = <T>(): T => { + const result = { + ...element.businessObject, + ...element.businessObject.$attrs + }; + + // 绉婚櫎flowable鍓嶇紑锛屾牸寮忓寲鏁扮粍 + for (const key in result) { + if (key.indexOf('flowable:') === 0) { + const newKey = key.replace('flowable:', ''); + result[newKey] = result[key]; + delete result[key]; + } + } + return { ...result } as T; + }; + + return { + parseData + }; +}; diff --git a/src/components/BpmnDesign/index.vue b/src/components/BpmnDesign/index.vue new file mode 100644 index 0000000..1568a3e --- /dev/null +++ b/src/components/BpmnDesign/index.vue @@ -0,0 +1,409 @@ +<template> + <div class="containers"> + <div class="app-containers"> + <el-container class="h-full"> + <el-container style="align-items: stretch"> + <el-header> + <div class="process-toolbar"> + <el-space wrap :size="10"> + <el-button size="small" type="primary" @click="saveXml">淇� 瀛�</el-button> + <el-dropdown size="small"> + <el-button size="small" type="primary"> 棰� 瑙� </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item icon="Document" @click="previewXML">XML棰勮</el-dropdown-item> + <el-dropdown-item icon="View" @click="previewSVG"> SVG棰勮</el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + + <el-dropdown size="small"> + <el-button size="small" type="primary"> 涓� 杞� </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item icon="Download" @click="downloadXML">涓嬭浇XML</el-dropdown-item> + <el-dropdown-item icon="Download" @click="downloadSVG"> 涓嬭浇SVG</el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + <el-tooltip effect="dark" content="鏂板缓" placement="bottom"> + <el-button size="small" icon="CirclePlus" @click="newDiagram" /> + </el-tooltip> + <el-tooltip effect="dark" content="鑷�傚簲灞忓箷" placement="bottom"> + <el-button size="small" icon="Rank" @click="fitViewport" /> + </el-tooltip> + <el-tooltip effect="dark" content="鏀惧ぇ" placement="bottom"> + <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" /> + </el-tooltip> + <el-tooltip effect="dark" content="缂╁皬" placement="bottom"> + <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" /> + </el-tooltip> + <el-tooltip effect="dark" content="鍚庨��" placement="bottom"> + <el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" /> + </el-tooltip> + <el-tooltip effect="dark" content="鍓嶈繘" placement="bottom"> + <el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" /> + </el-tooltip> + </el-space> + </div> + </el-header> + <div ref="canvas" class="canvas" /> + </el-container> + <div :class="{ 'process-panel': true, 'hide': panelFlag }"> + <div class="process-panel-bar" @click="panelBarClick"> + <div class="open-bar"> + <el-link type="default" :underline="false"> + <svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon> + </el-link> + </div> + </div> + <transition enter-active-class="animate__animated animate__fadeIn"> + <div v-show="showPanel" v-if="bpmnModeler" class="panel-content"> + <PropertyPanel :modeler="bpmnModeler" /> + </div> + </transition> + </div> + </el-container> + </div> + </div> + <div> + <el-dialog v-model="perviewXMLShow" title="XML棰勮" width="80%" append-to-body> + <highlightjs :code="xmlStr" language="XML" /> + </el-dialog> + </div> + <div> + <el-dialog v-model="perviewSVGShow" title="SVG棰勮" width="80%" append-to-body> + <div style="text-align: center" v-html="svgData" /> + </el-dialog> + </div> +</template> + +<script lang="ts" setup name="BpmnDesign"> +import 'bpmn-js/dist/assets/diagram-js.css'; +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'; +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'; +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'; +import './assets/style/index.scss'; +import { Canvas, Modeler } from 'bpmn'; +import PropertyPanel from './panel/index.vue'; +import BpmnModeler from 'bpmn-js/lib/Modeler.js'; +import defaultXML from '@/components/BpmnDesign/assets/defaultXML'; +import flowableModdle from '@/components/BpmnDesign/assets/moddle/flowable'; +import Modules from './assets/module/index'; +import useModelerStore from '@/store/modules/modeler'; +import useDialog from '@/hooks/useDialog'; + +const emit = defineEmits(['closeCallBack', 'saveCallBack']); + +const { visible, title, openDialog, closeDialog } = useDialog({ + title: '缂栬緫娴佺▼' +}); +const modelerStore = useModelerStore(); + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const panelFlag = ref(false); +const showPanel = ref(true); +const canvas = ref<HTMLDivElement>(); +const panel = ref<HTMLDivElement>(); +const bpmnModeler = ref<Modeler>(); +const zoom = ref(1); +const perviewXMLShow = ref(false); +const perviewSVGShow = ref(false); +const xmlStr = ref(''); +const svgData = ref(''); + +const panelBarClick = () => { + // 寤惰繜鎵ц锛屽惁鍒欎細瀵艰嚧闈㈡澘鏀惰捣鏃讹紝灞炴�ч潰鏉夸笉鏄剧ず + panelFlag.value = !panelFlag.value; + setTimeout(() => { + showPanel.value = !panelFlag.value; + }, 100); +}; + +/** + * 鍒濆鍖朇anvas + */ +const initCanvas = () => { + bpmnModeler.value = new BpmnModeler({ + container: canvas.value, + // 閿洏 + keyboard: { + bindTo: window // 鎴栬�厀indow锛屾敞鎰忎笌澶栭儴琛ㄥ崟鐨勯敭鐩樼洃鍚簨浠舵槸鍚﹀啿绐� + }, + propertiesPanel: { + parent: panel.value + }, + additionalModules: Modules, + moddleExtensions: { + flowable: flowableModdle + } + }); +}; + +/** + * 鍒濆鍖朚odel + */ +const initModel = () => { + if (modelerStore.getModeler()) { + modelerStore.getModeler().destroy(); + modelerStore.setModeler(undefined); + } + modelerStore.setModeler(bpmnModeler.value); +}; + +/** + * 鏂板缓 + */ +const newDiagram = async () => { + await proxy?.$modal.confirm('鏄惁纭鏂板缓'); + initDiagram(); +}; + +/** + * 鍒濆鍖� + */ +const initDiagram = (xml?: string) => { + if (!xml) xml = defaultXML; + bpmnModeler.value.importXML(xml); +}; + +/** + * 鑷�傚簲灞忓箷 + */ +const fitViewport = () => { + zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom('fit-viewport'); + const bbox = document.querySelector<SVGGElement>('.app-containers .viewport').getBBox(); + const currentViewBox = bpmnModeler.value.get<Canvas>('canvas').viewbox(); + const elementMid = { + x: bbox.x + bbox.width / 2 - 65, + y: bbox.y + bbox.height / 2 + }; + bpmnModeler.value.get<Canvas>('canvas').viewbox({ + x: elementMid.x - currentViewBox.width / 2, + y: elementMid.y - currentViewBox.height / 2, + width: currentViewBox.width, + height: currentViewBox.height + }); + zoom.value = (bbox.width / currentViewBox.width) * 1.8; +}; +/** + * 鏀惧ぇ鎴栬�呯缉灏� + * @param zoomIn true 鏀惧ぇ | false 缂╁皬 + */ +const zoomViewport = (zoomIn = true) => { + zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom(); + zoom.value += zoomIn ? 0.1 : -0.1; + bpmnModeler.value.get<Canvas>('canvas').zoom(zoom.value); +}; + +/** + * 涓嬭浇XML + */ +const downloadXML = async () => { + try { + const { xml } = await bpmnModeler.value.saveXML({ format: true }); + downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml'); + } catch (e) { + proxy?.$modal.msgError(e); + } +}; + +/** + * 涓嬭浇SVG + */ +const downloadSVG = async () => { + try { + const { svg } = await bpmnModeler.value.saveSVG(); + downloadFile(getProcessElement().name, svg, 'image/svg+xml'); + } catch (e) { + proxy?.$modal.msgError(e); + } +}; + +/** + * XML棰勮 + */ +const previewXML = async () => { + try { + const { xml } = await bpmnModeler.value.saveXML({ format: true }); + xmlStr.value = xml; + perviewXMLShow.value = true; + } catch (e) { + proxy?.$modal.msgError(e); + } +}; + +/** + * SVG棰勮 + */ +const previewSVG = async () => { + try { + const { svg } = await bpmnModeler.value.saveSVG(); + svgData.value = svg; + perviewSVGShow.value = true; + } catch (e) { + proxy?.$modal.msgError(e); + } +}; + +const curNodeInfo = reactive({ + curType: '', // 浠诲姟绫诲瀷 鐢ㄦ埛浠诲姟 + curNode: '', + expValue: '' //澶氱敤鎴峰拰閮ㄩ棬瑙掕壊瀹炵幇 +}); + +const downloadFile = (fileName: string, data: any, type: string) => { + const a = document.createElement('a'); + const url = window.URL.createObjectURL(new Blob([data], { type: type })); + a.href = url; + a.download = fileName; + a.click(); + window.URL.revokeObjectURL(url); +}; + +const getProcessElement = () => { + const rootElements = bpmnModeler.value?.getDefinitions().rootElements; + for (let i = 0; i < rootElements.length; i++) { + if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]; + } +}; + +const getProcess = () => { + const element = getProcessElement(); + return { + id: element.id, + name: element.name + }; +}; + +const saveXml = async () => { + const { xml } = await bpmnModeler.value.saveXML({ format: true }); + const { svg } = await bpmnModeler.value.saveSVG(); + const process = getProcess(); + let data = { + xml: xml, + svg: svg, + key: process.id, + name: process.name + }; + emit('saveCallBack', data); +}; + +const open = (xml?: string) => { + openDialog(); + nextTick(() => { + initDiagram(xml); + }); +}; +const close = () => { + closeDialog(); +}; + +onMounted(() => { + nextTick(() => { + initCanvas(); + initModel(); + }); +}); + +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + initDiagram, + saveXml, + open, + close +}); +</script> + +<style lang="scss" scoped> +.containers { + height: 100%; + .app-containers { + width: 100%; + height: 100%; + .canvas { + width: 100%; + height: 100%; + background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+'); + } + .el-header { + height: 35px; + padding: 0; + } + + .process-panel { + transition: width 0.25s ease-in; + .process-panel-bar { + width: 34px; + height: 40px; + .open-bar { + width: 34px; + line-height: 40px; + } + } + // 鏀惰捣闈㈡澘鏍峰紡 + &.hide { + width: 34px; + overflow: hidden; + padding: 0; + .process-panel-bar { + width: 34px; + height: 100%; + box-sizing: border-box; + display: block; + text-align: left; + line-height: 34px; + } + .process-panel-bar:hover { + background-color: #f5f7fa; + } + } + } + } +} +pre { + margin: 0; + height: 100%; + max-height: calc(80vh - 32px); + overflow-x: hidden; + overflow-y: auto; + :deep(.hljs) { + word-break: break-word; + white-space: pre-wrap; + padding: 0.5em; + } +} + +.open-bar { + font-size: 20px; + cursor: pointer; + text-align: center; +} +.process-panel { + box-sizing: border-box; + padding: 0 8px 0 8px; + border-left: 1px solid #eeeeee; + box-shadow: #cccccc 0 0 8px; + max-height: 100%; + width: 480px; + height: calc(100vh - 80px); + :deep(.el-collapse) { + height: calc(100vh - 162px); + overflow: auto; + } +} + +// 浠诲姟鏍� 閫忔槑搴� +//:deep(.djs-palette) { +// opacity: 0.3; +// transition: all 1s; +//} +// +//:deep(.djs-palette:hover) { +// opacity: 1; +// transition: all 1s; +//} +</style> diff --git a/src/components/BpmnDesign/panel/GatewayPanel.vue b/src/components/BpmnDesign/panel/GatewayPanel.vue new file mode 100644 index 0000000..46c67b5 --- /dev/null +++ b/src/components/BpmnDesign/panel/GatewayPanel.vue @@ -0,0 +1,68 @@ +<template> + <div> + <el-collapse v-model="currentCollapseItem"> + <el-collapse-item name="1"> + <template #title> + <div class="collapse__title"> + <el-icon> + <InfoFilled /> + </el-icon> + 甯歌 + </div> + </template> + <div> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> + <el-form-item prop="id" label="鑺傜偣 ID"> + <el-input v-model="formData.id" @change="idChange"> </el-input> + </el-form-item> + <el-form-item prop="name" label="鑺傜偣鍚嶇О"> + <el-input v-model="formData.name" @change="nameChange"> </el-input> + </el-form-item> + </el-form> + </div> + </el-collapse-item> + + <el-collapse-item name="2"> + <template #title> + <div class="collapse__title"> + <el-icon> + <BellFilled /> + </el-icon> + 鎵ц鐩戝惉鍣� + </div> + </template> + <div> + <ExecutionListener :element="element"></ExecutionListener> + </div> + </el-collapse-item> + </el-collapse> + </div> +</template> +<script setup lang="ts"> +import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import { Modeler, ModdleElement } from 'bpmn'; +import { GatewayPanel } from 'bpmnDesign'; +import ExecutionListener from '@/components/BpmnDesign/panel/property/ExecutionListener.vue'; + +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); +const { nameChange, idChange } = usePanel({ + element: toRaw(props.element) +}); +const { parseData } = useParseElement({ + element: toRaw(props.element) +}); +const currentCollapseItem = ref(['1', '2']); +const formData = ref(parseData<GatewayPanel>()); + +const formRules = ref<ElFormRules>({ + processCategory: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); +</script> + +<style lang="scss" scoped></style> diff --git a/src/components/BpmnDesign/panel/ProcessPanel.vue b/src/components/BpmnDesign/panel/ProcessPanel.vue new file mode 100644 index 0000000..01e8f89 --- /dev/null +++ b/src/components/BpmnDesign/panel/ProcessPanel.vue @@ -0,0 +1,71 @@ +<template> + <div> + <el-collapse v-model="currentCollapseItem"> + <el-collapse-item name="1"> + <template #title> + <div class="collapse__title"> + <el-icon> + <InfoFilled /> + </el-icon> + 甯歌 + </div> + </template> + <div> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> + <el-form-item label="娴佺▼鏍囪瘑" prop="id"> + <el-input v-model="formData.id" @change="idChange"></el-input> + </el-form-item> + <el-form-item label="娴佺▼鍚嶇О" prop="name"> + <el-input v-model="formData.name" @change="nameChange"></el-input> + </el-form-item> + </el-form> + </div> + </el-collapse-item> + + <el-collapse-item name="2"> + <template #title> + <div class="collapse__title"> + <el-icon> + <BellFilled /> + </el-icon> + 鎵ц鐩戝惉鍣� + </div> + </template> + <div> + <ExecutionListener :element="element"></ExecutionListener> + </div> + </el-collapse-item> + </el-collapse> + </div> +</template> + +<script setup lang="ts"> +import ExecutionListener from './property/ExecutionListener.vue'; +import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import { Modeler, ModdleElement } from 'bpmn'; +import { ProcessPanel } from 'bpmnDesign'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); + +const { parseData } = useParseElement({ + element: toRaw(props.element) +}); +const { idChange, nameChange } = usePanel({ + element: toRaw(props.element) +}); +const currentCollapseItem = ref(['1', '2']); +const formData = ref<ProcessPanel>(parseData<ProcessPanel>()); + +const formRules = ref<ElFormRules>({ + id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); +</script> + +<style scoped lang="scss"></style> diff --git a/src/components/BpmnDesign/panel/SequenceFlowPanel.vue b/src/components/BpmnDesign/panel/SequenceFlowPanel.vue new file mode 100644 index 0000000..88ce350 --- /dev/null +++ b/src/components/BpmnDesign/panel/SequenceFlowPanel.vue @@ -0,0 +1,94 @@ +<template> + <div> + <el-collapse v-model="currentCollapseItem"> + <el-collapse-item name="1"> + <template #title> + <div class="collapse__title"> + <el-icon> + <InfoFilled /> + </el-icon> + 甯歌 + </div> + </template> + <div> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px"> + <el-form-item prop="id" label="鑺傜偣 ID"> + <el-input v-model="formData.id" @change="idChange"> </el-input> + </el-form-item> + <el-form-item prop="name" label="鑺傜偣鍚嶇О"> + <el-input v-model="formData.name" @change="nameChange"> </el-input> + </el-form-item> + <el-form-item prop="conditionExpression" label="璺宠浆鏉′欢"> + <el-input v-model="formData.conditionExpressionValue" @change="conditionExpressionChange"> </el-input> + </el-form-item> + <el-form-item prop="skipExpression" label="璺宠繃琛ㄨ揪寮�"> + <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input> + </el-form-item> + </el-form> + </div> + </el-collapse-item> + + <el-collapse-item name="2"> + <template #title> + <div class="collapse__title"> + <el-icon> + <BellFilled /> + </el-icon> + 鎵ц鐩戝惉鍣� + </div> + </template> + <div> + <ExecutionListener :element="element"></ExecutionListener> + </div> + </el-collapse-item> + </el-collapse> + </div> +</template> +<script setup lang="ts"> +import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import { Modeler, ModdleElement } from 'bpmn'; +import { SequenceFlowPanel } from 'bpmnDesign'; +import useModelerStore from '@/store/modules/modeler'; + +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); +const { nameChange, idChange, updateProperties } = usePanel({ + element: toRaw(props.element) +}); +const { parseData } = useParseElement({ + element: toRaw(props.element) +}); +const moddle = useModelerStore().getModdle(); +const currentCollapseItem = ref(['1', '2']); +const formData = ref(parseData<SequenceFlowPanel>()); + +const formRules = ref<ElFormRules>({ + processCategory: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); + +const conditionExpressionChange = (val: string) => { + if (val) { + const newCondition = moddle.create('bpmn:FormalExpression', { body: val }); + updateProperties({ conditionExpression: newCondition }); + } else { + updateProperties({ conditionExpression: null }); + } +}; + +const skipExpressionChange = (val: string) => { + updateProperties({ 'flowable:skipExpression': val }); +}; + +onBeforeMount(() => { + if (formData.value.conditionExpression) { + formData.value.conditionExpressionValue = formData.value.conditionExpression.body; + } +}); +</script> + +<style lang="scss" scoped></style> diff --git a/src/components/BpmnDesign/panel/StartEndPanel.vue b/src/components/BpmnDesign/panel/StartEndPanel.vue new file mode 100644 index 0000000..9cd0efc --- /dev/null +++ b/src/components/BpmnDesign/panel/StartEndPanel.vue @@ -0,0 +1,40 @@ +<template> + <div> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> + <el-form-item prop="id" label="鑺傜偣 ID"> + <el-input v-model="formData.id" @change="idChange"> </el-input> + </el-form-item> + <el-form-item prop="name" label="鑺傜偣鍚嶇О"> + <el-input v-model="formData.name" @change="nameChange"> </el-input> + </el-form-item> + <el-form-item label="鎵ц鐩戝惉鍣�" style="margin-bottom: 0"> </el-form-item> + <ExecutionListener :element="element"></ExecutionListener> + </el-form> + </div> +</template> +<script setup lang="ts"> +import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import { Modeler, ModdleElement } from 'bpmn'; +import { StartEndPanel } from 'bpmnDesign'; + +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); +const { nameChange, idChange } = usePanel({ + element: toRaw(props.element) +}); +const { parseData } = useParseElement({ + element: toRaw(props.element) +}); + +const formData = ref(parseData<StartEndPanel>()); + +const formRules = ref<ElFormRules>({ + id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); +</script> + +<style lang="scss" scoped></style> diff --git a/src/components/BpmnDesign/panel/TaskPanel.vue b/src/components/BpmnDesign/panel/TaskPanel.vue new file mode 100644 index 0000000..267ad30 --- /dev/null +++ b/src/components/BpmnDesign/panel/TaskPanel.vue @@ -0,0 +1,467 @@ +<template> + <div> + <el-form ref="formRef" size="default" :model="formData" :rules="formRules" label-width="100px"> + <el-collapse v-model="currentCollapseItem"> + <el-collapse-item name="1"> + <template #title> + <div class="collapse__title"> + <el-icon> + <InfoFilled /> + </el-icon> + 甯歌 + </div> + </template> + <div> + <el-form-item prop="id" label="鑺傜偣 ID"> + <el-input v-model="formData.id" @change="idChange"> </el-input> + </el-form-item> + <el-form-item prop="name" label="鑺傜偣鍚嶇О"> + <el-input v-model="formData.name" @change="nameChange"> </el-input> + </el-form-item> + <el-form-item v-if="showConfig.skipExpression" prop="skipExpression" label="璺宠繃琛ㄨ揪寮�"> + <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input> + </el-form-item> + </div> + </el-collapse-item> + <el-collapse-item name="2"> + <template #title> + <div class="collapse__title"> + <el-icon> + <Checked /> + </el-icon> + 浠诲姟 + </div> + </template> + <div> + <el-form-item v-if="showConfig.async" prop="sync" label="鏄惁寮傛"> + <el-switch v-model="formData.async" inline-prompt active-text="鏄�" inactive-text="鍚�" @change="syncChange" /> + </el-form-item> + + <el-tabs tab-position="left" class="demo-tabs" @tab-click="taskTabClick"> + <el-tab-pane label="韬唤瀛樺偍"> + <el-form-item label="鍒嗛厤浜哄憳"> + <el-input v-model="assignee.userName" disabled> + <template #append> + <el-button icon="Search" type="primary" @click="openSingleUserSelect" /> + </template> + </el-input> + </el-form-item> + <el-form-item label="鍊欓�変汉鍛�"> + <el-badge :value="selectUserLength" :max="99"> + <el-button size="small" type="primary" @click="openUserSelect">閫夋嫨浜哄憳</el-button> + </el-badge> + </el-form-item> + <el-form-item label="鍊欓�夌粍"> + <el-badge :value="selectRoleLength" :max="99"> + <el-button size="small" type="primary" @click="openRoleSelect">閫夋嫨缁�</el-button> + </el-badge> + </el-form-item> + </el-tab-pane> + + <el-tab-pane label="鍥哄畾鍊�"> + <el-form-item prop="auditUserType" label="鍒嗛厤绫诲瀷"> + <el-select v-model="formData.allocationType"> + <el-option v-for="item in AllocationTypeSelect" :key="item.id" :value="item.value" :label="item.label"> </el-option> + </el-select> + </el-form-item> + <el-form-item v-if="formData.allocationType === AllocationTypeEnum.USER" label="鍒嗛厤浜哄憳"> + <el-input v-model="formData.fixedAssignee" @change="fixedAssigneeChange"> + <template #append> + <el-button icon="Search" size="small" type="primary" @click="proxy.$modal.msgWarning('寮�鍙戜腑銆傘�傘�傘�傘�傘��')" /> + </template> + </el-input> + </el-form-item> + <div v-if="formData.allocationType === AllocationTypeEnum.CANDIDATE"> + <el-form-item label="鍊欓�変汉鍛�"> + <el-badge :value="selectUserLength" :max="99"> + <el-button size="small" type="primary" @click="openUserSelect">閫夋嫨浜哄憳</el-button> + </el-badge> + </el-form-item> + <el-form-item label="鍊欓�夌粍"> + <el-badge :value="selectRoleLength" :max="99"> + <el-button size="small" type="primary" @click="openRoleSelect">閫夋嫨缁�</el-button> + </el-badge> + </el-form-item> + </div> + <el-form-item v-if="formData.allocationType === AllocationTypeEnum.SPECIFY && showConfig.specifyDesc" style=""> + <el-radio-group v-model="formData.specifyDesc" class="ml-4"> + <el-radio v-for="item in SpecifyDesc" :key="item.id" :label="item.value" size="large">{{ item.label }}</el-radio> + </el-radio-group> + </el-form-item> + </el-tab-pane> + </el-tabs> + + <el-form-item v-if="showConfig.dueDate" prop="dueDate" label="鍒版湡鏃堕棿"> + <el-input v-model="formData.dueDate" clearable @change="dueDateChange" @click="openDueDate"> + <template #append> + <el-button icon="Search" type="primary" @click="openDueDate" /> + </template> + </el-input> + </el-form-item> + <el-form-item v-if="showConfig.priority" prop="priority" label="浼樺厛绾�"> + <el-input-number v-model="formData.priority" :min="0" @change="priorityChange"> </el-input-number> + </el-form-item> + </div> + </el-collapse-item> + <el-collapse-item name="3"> + <template #title> + <div class="collapse__title"> + <el-icon> + <HelpFilled /> + </el-icon> + 澶氬疄渚� + </div> + </template> + <div> + <el-form-item label="澶氬疄渚嬬被鍨�"> + <el-select v-model="formData.multiInstanceType" @change="multiInstanceTypeChange"> + <el-option v-for="item in MultiInstanceType" :key="item.id" :value="item.value" :label="item.label"> </el-option> + </el-select> + </el-form-item> + + <div v-if="formData.multiInstanceType !== MultiInstanceTypeEnum.NONE"> + <el-form-item label="闆嗗悎"> + <template #label> + <span> + 闆嗗悎 + <el-tooltip placement="top"> + <el-icon><QuestionFilled /></el-icon> + <template #content> + 灞炴�т細浣滀负琛ㄨ揪寮忚繘琛岃В鏋愩�傚鏋滆〃杈惧紡瑙f瀽涓哄瓧绗︿覆鑰屼笉鏄竴涓泦鍚堬紝<br /> + 涓嶈鏄洜涓烘湰韬厤缃殑灏辨槸闈欐�佸瓧绗︿覆鍊硷紝杩樻槸琛ㄨ揪寮忚绠楃粨鏋滀负瀛楃涓诧紝<br /> + 杩欎釜瀛楃涓查兘浼氳褰撳仛鍙橀噺鍚嶏紝骞朵粠娴佺▼鍙橀噺涓敤浜庤幏鍙栧疄闄呯殑闆嗗悎銆� + </template> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.collection" @change="collectionChange"></el-input> + </el-form-item> + <el-form-item label="鍏冪礌鍙橀噺"> + <template #label> + <span> + 鍏冪礌鍙橀噺 + <el-tooltip placement="top"> + <el-icon><QuestionFilled /></el-icon> + <template #content> + 姣忓垱寤轰竴涓敤鎴蜂换鍔″墠锛屽厛浠ヨ鍏冪礌鍙橀噺涓簂abel锛岄泦鍚堜腑鐨勪竴椤逛负value锛�<br /> + 鍒涘缓锛堝眬閮級娴佺▼鍙橀噺锛岃灞�閮ㄦ祦绋嬪彉閲忚鐢ㄤ簬鎸囨淳鐢ㄦ埛浠诲姟銆�<br /> + 涓�鑸潵璇达紝璇ュ瓧绗︿覆搴斾笌鎸囧畾浜哄憳鍙橀噺鐩稿悓銆� + </template> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.elementVariable" @change="elementVariableChange"> </el-input> + </el-form-item> + <el-form-item label="瀹屾垚鏉′欢"> + <template #label> + <span> + 瀹屾垚鏉′欢 + <el-tooltip placement="top"> + <el-icon><QuestionFilled /></el-icon> + <template #content> + 澶氬疄渚嬫椿鍔ㄥ湪鎵�鏈夊疄渚嬮兘瀹屾垚鏃剁粨鏉燂紝鐒惰�屼篃鍙互鎸囧畾涓�涓〃杈惧紡锛屽湪姣忎釜瀹炰緥<br /> + 缁撴潫鏃惰繘琛岃绠椼�傚綋琛ㄨ揪寮忚绠椾负true鏃讹紝灏嗛攢姣佹墍鏈夊墿浣欑殑瀹炰緥锛屽苟缁撴潫澶氬疄渚�<br /> + 娲诲姩锛岀户缁墽琛屾祦绋嬨�備緥濡� ${nrOfCompletedInstances/nrOfInstances >= 0.6 }锛�<br /> + 琛ㄧず褰撲换鍔″畬鎴�60%鏃讹紝璇ヨ妭鐐瑰氨绠楀畬鎴� + </template> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.completionCondition" @change="completionConditionChange"> </el-input> + </el-form-item> + </div> + </div> + </el-collapse-item> + <el-collapse-item v-if="showConfig.taskListener" name="4"> + <template #title> + <div class="collapse__title"> + <el-icon> + <BellFilled /> + </el-icon> + 浠诲姟鐩戝惉鍣� + </div> + </template> + <div> + <TaskListener v-if="showConfig.taskListener" :element="element"></TaskListener> + </div> + </el-collapse-item> + <el-collapse-item v-if="showConfig.executionListener" name="5"> + <template #title> + <div class="collapse__title"> + <el-icon> + <BellFilled /> + </el-icon> + 鎵ц鐩戝惉鍣� + </div> + </template> + <div> + <ExecutionListener v-if="showConfig.executionListener" :element="element"></ExecutionListener> + </div> + </el-collapse-item> + + <el-form-item v-if="showConfig.isForCompensation" prop="isForCompensation" label="鏄惁涓鸿ˉ鍋�"> + <el-switch v-model="formData.isForCompensation" inline-prompt active-text="鏄�" inactive-text="鍚�" /> + </el-form-item> + <el-form-item v-if="showConfig.triggerServiceTask" prop="triggerServiceTask" label="鏈嶅姟浠诲姟鍙Е鍙�"> + <el-switch v-model="formData.triggerServiceTask" inline-prompt active-text="鏄�" inactive-text="鍚�" /> + </el-form-item> + <el-form-item v-if="showConfig.autoStoreVariables" prop="autoStoreVariables" label="鑷姩瀛樺偍鍙橀噺"> + <el-switch v-model="formData.autoStoreVariables" inline-prompt active-text="鏄�" inactive-text="鍚�" /> + </el-form-item> + <el-form-item v-if="showConfig.ruleVariablesInput" prop="skipExpression" label="杈撳叆鍙橀噺"> + <el-input v-model="formData.ruleVariablesInput"> </el-input> + </el-form-item> + <el-form-item v-if="showConfig.exclude" prop="exclude" label="鎺掗櫎"> + <el-switch v-model="formData.exclude" inline-prompt active-text="鏄�" inactive-text="鍚�" /> + </el-form-item> + <el-form-item v-if="showConfig.class" prop="class" label="绫�"> + <el-input v-model="formData.class"> </el-input> + </el-form-item> + </el-collapse> + </el-form> + <UserSelect ref="userSelectRef" :data="formData.candidateUsers" @confirm-call-back="userSelectCallBack"></UserSelect> + <UserSelect ref="singleUserSelectRef" :data="formData.assignee" :multiple="false" @confirm-call-back="singleUserSelectCallBack"></UserSelect> + <RoleSelect ref="roleSelectRef" :data="formData.candidateGroups" @confirm-call-back="roleSelectCallBack"></RoleSelect> + <DueDate ref="dueDateRef" v-model="formData.dueDate" :data="formData.dueDate" @confirm-call-back="dueDateCallBack"></DueDate> + </div> +</template> +<script setup lang="ts"> +import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import UserSelect from '@/components/UserSelect'; +import RoleSelect from '@/components/RoleSelect'; +import DueDate from '@/components/BpmnDesign/panel/property/DueDate.vue'; +import { ModdleElement } from 'bpmn'; +import { TaskPanel } from 'bpmnDesign'; +import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums'; +import { UserVO } from '@/api/system/user/types'; +import { RoleVO } from '@/api/system/role/types'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); +const { showConfig, nameChange, idChange, updateProperties, getExtensionElements, createModdleElement } = usePanel({ + element: toRaw(props.element) +}); +const { parseData } = useParseElement({ + element: toRaw(props.element) +}); + +const initFormData = { + id: '', + name: '', + dueDate: '', + multiInstanceType: MultiInstanceTypeEnum.NONE, + allocationType: AllocationTypeEnum.USER, + specifyDesc: SpecifyDescEnum.SPECIFY_SINGLE +}; +const formData = ref({ ...initFormData, ...parseData<TaskPanel>() }); +const assignee = ref<Partial<UserVO>>({ + userName: '' +}); +const currentCollapseItem = ref(['1', '2']); +const userSelectRef = ref<InstanceType<typeof UserSelect>>(); +const singleUserSelectRef = ref<InstanceType<typeof UserSelect>>(); +const roleSelectRef = ref<InstanceType<typeof RoleSelect>>(); +const dueDateRef = ref<InstanceType<typeof DueDate>>(); + +const isMultiple = ref(true); +const openUserSelect = () => { + userSelectRef.value.open(); +}; +const openSingleUserSelect = () => { + singleUserSelectRef.value.open(); +}; +const openRoleSelect = () => { + roleSelectRef.value.open(); +}; +const openDueDate = (e) => { + dueDateRef.value.openDialog(); +}; + +const singleUserSelectCallBack = (data: UserVO[]) => { + const user: UserVO = data.length !== 0 ? data[0] : undefined; + updateProperties({ 'flowable:assignee': user?.userId }); + assignee.value = user ? user : { userName: '' }; + formData.value.assignee = String(user?.userId); + let extensionElements = getExtensionElements(); + extensionElements.values = extensionElements.get('values').filter((item) => item.$type !== 'flowable:extAssignee'); + if (user) { + const extAssigneeElement = createModdleElement('flowable:extAssignee', { body: '' }, extensionElements); + extensionElements.get('values').push(extAssigneeElement); + extAssigneeElement.body = JSON.stringify({ userName: user.userName, userId: user.userId }); + } + if (extensionElements.values.length === 0) { + extensionElements = undefined; + } + updateProperties({ extensionElements: extensionElements }); +}; +const userSelectCallBack = (data: UserVO[]) => { + let extensionElements = getExtensionElements(); + extensionElements.values = extensionElements.values.filter((item) => item.$type !== 'flowable:extCandidateUsers'); + if (data.length === 0) { + formData.value.candidateUsers = undefined; + updateProperties({ 'flowable:candidateUsers': undefined }); + } else { + const userIds = data.map((item) => item.userId).join(','); + formData.value.candidateUsers = userIds; + updateProperties({ 'flowable:candidateUsers': userIds }); + const extCandidateUsersElement = createModdleElement('flowable:extCandidateUsers', { body: '' }, extensionElements); + extensionElements.values.push(extCandidateUsersElement); + const users = data.map((item) => { + return { + userId: item.userId, + userName: item.userName + }; + }); + extCandidateUsersElement.body = JSON.stringify(users); + } + if (extensionElements.values.length === 0) { + extensionElements = undefined; + } + updateProperties({ extensionElements: extensionElements }); +}; +const roleSelectCallBack = (data: RoleVO[]) => { + if (data.length === 0) { + formData.value.candidateGroups = ''; + updateProperties({ 'flowable:candidateGroups': undefined }); + } else { + const roleIds = data.map((item) => item.roleId).join(','); + formData.value.candidateGroups = roleIds; + updateProperties({ 'flowable:candidateGroups': roleIds }); + } +}; +const dueDateCallBack = (data: string) => { + updateProperties({ 'flowable:dueDate': data }); +}; + +const taskTabClick = (e) => { + formData.value.candidateGroups = ''; + formData.value.candidateUsers = ''; + formData.value.assignee = ''; + assignee.value = {}; +}; + +const syncChange = (newVal) => { + updateProperties({ 'flowable:async': newVal }); +}; +const skipExpressionChange = (newVal) => { + updateProperties({ 'flowable:skipExpression': newVal && newVal.length > 0 ? newVal : undefined }); +}; +const priorityChange = (newVal) => { + updateProperties({ 'flowable:priority': newVal }); +}; +const fixedAssigneeChange = (newVal) => { + updateProperties({ 'flowable:assignee': newVal && newVal.length > 0 ? newVal : undefined }); +}; +const multiInstanceTypeChange = (newVal) => { + if (newVal !== MultiInstanceTypeEnum.NONE) { + let loopCharacteristics = props.element.businessObject.get('loopCharacteristics'); + if (!loopCharacteristics) { + loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject); + } + loopCharacteristics.isSequential = newVal === MultiInstanceTypeEnum.SERIAL; + updateProperties({ loopCharacteristics: loopCharacteristics }); + } else { + updateProperties({ loopCharacteristics: undefined }); + } +}; +const collectionChange = (newVal) => { + let loopCharacteristics = props.element.businessObject.get('loopCharacteristics'); + if (!loopCharacteristics) { + loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject); + } + loopCharacteristics.collection = newVal && newVal.length > 0 ? newVal : undefined; + updateProperties({ loopCharacteristics: loopCharacteristics }); +}; +const elementVariableChange = (newVal) => { + let loopCharacteristics = props.element.businessObject.get('loopCharacteristics'); + if (!loopCharacteristics) { + loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject); + } + loopCharacteristics.elementVariable = newVal && newVal.length > 0 ? newVal : undefined; + updateProperties({ loopCharacteristics: loopCharacteristics }); +}; +const completionConditionChange = (newVal) => { + let loopCharacteristics = props.element.businessObject.get<ModdleElement>('loopCharacteristics'); + if (!loopCharacteristics) { + loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject); + } + if (newVal && newVal.length > 0) { + if (!loopCharacteristics.completionCondition) { + loopCharacteristics.completionCondition = createModdleElement('bpmn:Expression', { body: newVal }, loopCharacteristics); + } else { + loopCharacteristics.completionCondition.body = newVal; + } + } else { + loopCharacteristics.completionCondition = undefined; + } + updateProperties({ loopCharacteristics: loopCharacteristics }); +}; +const dueDateChange = (newVal) => { + updateProperties({ 'flowable:dueDate': newVal && newVal.length > 0 ? newVal : undefined }); +}; +const selectUserLength = computed(() => { + if (formData.value.candidateUsers) { + return formData.value.candidateUsers.split(',').length; + } else { + return 0; + } +}); +const selectRoleLength = computed(() => { + if (formData.value.candidateGroups) { + return formData.value.candidateGroups.split(',').length; + } else { + return 0; + } +}); + +onBeforeMount(() => { + const extensionElements = getExtensionElements(false); + if (extensionElements && extensionElements.get('values')) { + let extAssigneeElement = extensionElements.get('values').find((item) => item.$type === 'flowable:extAssignee'); + if (extAssigneeElement) { + assignee.value = JSON.parse(extAssigneeElement.body); + } + } + + if (formData.value.loopCharacteristics) { + const loopCharacteristics = formData.value.loopCharacteristics; + formData.value.collection = loopCharacteristics.collection || ''; + formData.value.elementVariable = loopCharacteristics.elementVariable || ''; + formData.value.completionCondition = loopCharacteristics.completionCondition?.body || ''; + formData.value.multiInstanceType = loopCharacteristics.isSequential ? MultiInstanceTypeEnum.SERIAL : MultiInstanceTypeEnum.PARALLEL; + } + + if (formData.value.assignee) { + formData.value.fixedAssignee = formData.value.assignee; + } +}); + +const formRules = ref<ElFormRules>({ + id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); + +const AllocationTypeSelect = [ + { id: 'b9cdf970-dd91-47c0-819f-42a7010ca2a6', label: '鎸囧畾浜哄憳', value: AllocationTypeEnum.USER }, + { id: '3f7ccbcd-c464-4602-bb9d-e96649d10585', label: '鍊欓�変汉鍛�', value: AllocationTypeEnum.CANDIDATE }, + { id: 'c49065e0-7f2d-4c09-aedb-ab2d47d9a454', label: '鍙戣捣浜鸿嚜宸�', value: AllocationTypeEnum.YOURSELF }, + { id: '6ef40a03-7e9a-4898-89b2-c88fe9064542', label: '鍙戣捣浜烘寚瀹�', value: AllocationTypeEnum.SPECIFY } +]; +const SpecifyDesc = [ + { id: 'fa253b34-4335-458c-b1bc-b039e2a2b7a6', label: '鎸囧畾涓�涓汉', value: 'specifySingle' }, + { id: '7365ff54-2e05-4312-9bfb-0b8edd779c5b', label: '鎸囧畾澶氫釜浜�', value: 'specifyMultiple' } +]; + +const MultiInstanceType = [ + { id: '373d4b81-a0d1-4eb8-8685-0d2fb1b468e2', label: '鏃�', value: MultiInstanceTypeEnum.NONE }, + { id: 'b5acea7c-b7e5-46b0-8778-390db091bdab', label: '涓茶', value: MultiInstanceTypeEnum.SERIAL }, + { id: 'b4f0c683-1ccc-43c4-8380-e1b998986caf', label: '骞惰', value: MultiInstanceTypeEnum.PARALLEL } +]; +</script> + +<style lang="scss" scoped></style> diff --git a/src/components/BpmnDesign/panel/index.vue b/src/components/BpmnDesign/panel/index.vue new file mode 100644 index 0000000..aa39af1 --- /dev/null +++ b/src/components/BpmnDesign/panel/index.vue @@ -0,0 +1,104 @@ +<template> + <div ref="propertyPanel"> + <div v-if="nodeName" class="node-name">{{ nodeName }}</div> + <component :is="component" v-if="element" :element="element" /> + </div> +</template> +<script setup lang="ts" name="PropertyPanel"> +import { NodeName } from '../assets/lang/zh'; +import TaskPanel from './TaskPanel.vue'; +import ProcessPanel from './ProcessPanel.vue'; +import StartEndPanel from './StartEndPanel.vue'; +import GatewayPanel from './GatewayPanel.vue'; +import SequenceFlowPanel from './SequenceFlowPanel.vue'; +import { Modeler, ModdleElement } from 'bpmn'; +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +interface propsType { + modeler: Modeler; +} +const props = withDefaults(defineProps<propsType>(), {}); + +const element = ref<ModdleElement>(); +const processElement = ref<ModdleElement>(); + +const startEndType = ['bpmn:IntermediateThrowEvent', 'bpmn:StartEvent', 'bpmn:EndEvent']; +const taskType = [ + 'bpmn:UserTask', + 'bpmn:Task', + 'bpmn:SendTask', + 'bpmn:ReceiveTask', + 'bpmn:ManualTask', + 'bpmn:BusinessRuleTask', + 'bpmn:ServiceTask', + 'bpmn:ScriptTask' +]; +const sequenceType = ['bpmn:SequenceFlow']; +const gatewayType = ['bpmn:InclusiveGateway', 'bpmn:ExclusiveGateway', 'bpmn:ParallelGateway', 'bpmn:EventBasedGateway', 'bpmn:ComplexGateway']; +const processType = ['bpmn:Process']; + +// 缁勪欢璁$畻 +const component = computed(() => { + if (!element.value) return null; + const type = element.value.type; + if (startEndType.includes(type)) return StartEndPanel; + if (taskType.includes(type)) return TaskPanel; + if (sequenceType.includes(type)) return SequenceFlowPanel; + if (gatewayType.includes(type)) return GatewayPanel; + if (processType.includes(type)) return ProcessPanel; + return proxy?.$modal.msgWarning('闈㈡澘寮�鍙戜腑....'); +}); + +const nodeName = computed(() => { + if (element.value) { + const bizObj = element.value.businessObject; + const type = bizObj?.eventDefinitions && bizObj?.eventDefinitions.length > 0 ? bizObj.eventDefinitions[0].$type : bizObj.$type; + return NodeName[type] || type; + } +}); + +const handleModeler = () => { + props.modeler.on('root.added', (e: any) => { + element.value = null; + if (e.element.type === 'bpmn:Process') { + nextTick(() => { + element.value = e.element; + processElement.value = e.element; + }); + } + }); + props.modeler.on('element.click', (e: any) => { + if (e.element.type === 'bpmn:Process') { + nextTick(() => { + element.value = e.element; + processElement.value = e.element; + }); + } + }); + props.modeler.on('selection.changed', (e: any) => { + // 鍏堢粰null涓轰簡璁﹙ue鍒锋柊 + element.value = null; + const newElement = e.newSelection[0]; + if (newElement) { + nextTick(() => { + element.value = newElement; + }); + } else { + nextTick(() => { + element.value = processElement.value; + }); + } + }); +}; + +onMounted(() => { + handleModeler(); +}); +</script> + +<style scoped lang="scss"> +.node-name { + font-size: 16px; + font-weight: bold; + padding: 10px; +} +</style> diff --git a/src/components/BpmnDesign/panel/property/DueDate.vue b/src/components/BpmnDesign/panel/property/DueDate.vue new file mode 100644 index 0000000..4825a1c --- /dev/null +++ b/src/components/BpmnDesign/panel/property/DueDate.vue @@ -0,0 +1,252 @@ +<template> + <div> + <el-dialog v-model="visible" :title="title" width="600px" append-to-body> + <el-form label-width="100px"> + <el-form-item label="灏忔椂"> + <el-radio-group v-model="hourValue" @change="hourChange"> + <el-radio-button label="4" /> + <el-radio-button label="8" /> + <el-radio-button label="12" /> + <el-radio-button label="24" /> + <el-radio-button label="鑷畾涔�" /> + <el-input-number v-show="hourValue === '鑷畾涔�'" v-model="customHourValue" :min="1" @change="customHourValueChange"></el-input-number> + </el-radio-group> + </el-form-item> + <el-form-item label="澶�"> + <el-radio-group v-model="dayValue" @change="dayChange"> + <el-radio-button label="1" /> + <el-radio-button label="2" /> + <el-radio-button label="3" /> + <el-radio-button label="4" /> + <el-radio-button label="鑷畾涔�" /> + <el-input-number v-show="dayValue === '鑷畾涔�'" v-model="customDayValue" :min="1" @change="customDayValueChange"></el-input-number> + </el-radio-group> + </el-form-item> + <el-form-item label="鍛�"> + <el-radio-group v-model="weekValue" @change="weekChange"> + <el-radio-button label="1" /> + <el-radio-button label="2" /> + <el-radio-button label="3" /> + <el-radio-button label="4" /> + <el-radio-button label="鑷畾涔�" /> + <el-input-number v-show="weekValue === '鑷畾涔�'" v-model="customWeekValue" :min="1" @change="customWeekValueChange"></el-input-number> + </el-radio-group> + </el-form-item> + <el-form-item label="鏈�"> + <el-radio-group v-model="monthValue" @change="monthChange"> + <el-radio-button label="1" /> + <el-radio-button label="2" /> + <el-radio-button label="3" /> + <el-radio-button label="4" /> + <el-radio-button label="鑷畾涔�" /> + <el-input-number v-show="monthValue === '鑷畾涔�'" v-model="customMonthValue" :min="1" @change="customMonthValueChange"></el-input-number> + </el-radio-group> + </el-form-item> + </el-form> + + <template #footer> + <div> + <el-button @click="closeDialog">鍙栨秷</el-button> + <el-button type="primary" @click="confirm">纭畾</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup lang="ts"> +import useDialog from '@/hooks/useDialog'; + +interface PropType { + modelValue?: string; + data?: string; +} +const prop = withDefaults(defineProps<PropType>(), { + modelValue: '', + data: '' +}); +const emit = defineEmits(['update:modelValue', 'confirmCallBack']); + +const { title, visible, openDialog, closeDialog } = useDialog({ + title: '璁剧疆浠诲姟鍒版湡鏃堕棿' +}); +const formValue = ref(); +const valueType = ref(); + +const hourValue = ref(''); +const dayValue = ref(''); +const weekValue = ref(''); +const monthValue = ref(''); + +const customHourValue = ref(1); +const customDayValue = ref(1); +const customWeekValue = ref(1); +const customMonthValue = ref(1); + +const hourValueConst = ['4', '8', '12', '24']; +const dayAndWeekAndMonthValueConst = ['1', '2', '3', '4']; + +const initValue = () => { + formValue.value = prop.data; + if (prop.data) { + const lastStr = prop.data.substring(prop.data.length - 1); + if (lastStr === 'H') { + const hourValueValue = prop.data.substring(2, prop.data.length - 1); + if (hourValueConst.includes(hourValueValue)) { + hourValue.value = hourValueValue; + } else { + hourValue.value = '鑷畾涔�'; + customHourValue.value = Number(hourValueValue); + } + } + const dayAndWeekAndMonthValue = prop.data.substring(1, prop.data.length - 1); + if (lastStr === 'D') { + if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) { + dayValue.value = dayAndWeekAndMonthValue; + } else { + dayValue.value = '鑷畾涔�'; + customDayValue.value = Number(dayAndWeekAndMonthValue); + } + } + if (lastStr === 'W') { + if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) { + weekValue.value = dayAndWeekAndMonthValue; + } else { + weekValue.value = '鑷畾涔�'; + customWeekValue.value = Number(dayAndWeekAndMonthValue); + } + } + if (lastStr === 'M') { + if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) { + monthValue.value = dayAndWeekAndMonthValue; + } else { + monthValue.value = '鑷畾涔�'; + customMonthValue.value = Number(dayAndWeekAndMonthValue); + } + } + } +}; + +const confirm = () => { + emit('update:modelValue', formValue.value); + emit('confirmCallBack', formValue.value); + closeDialog(); +}; + +const customHourValueChange = (customHourValue) => { + formValue.value = `PT${customHourValue}H`; + + dayValue.value = ''; + weekValue.value = ''; + monthValue.value = ''; + customDayValue.value = 1; + customWeekValue.value = 1; + customMonthValue.value = 1; +}; +const customDayValueChange = (customDayValue) => { + formValue.value = `P${customDayValue}D`; + hourValue.value = ''; + weekValue.value = ''; + monthValue.value = ''; + + customHourValue.value = 1; + customWeekValue.value = 1; + customMonthValue.value = 1; +}; + +const customWeekValueChange = (customWeekValue) => { + formValue.value = `P${customWeekValue}W`; + hourValue.value = ''; + dayValue.value = ''; + monthValue.value = ''; + + customHourValue.value = 1; + customDayValue.value = 1; + customMonthValue.value = 1; +}; + +const customMonthValueChange = (customMonthValue) => { + formValue.value = `P${customMonthValue}M`; + hourValue.value = ''; + dayValue.value = ''; + weekValue.value = ''; + + customHourValue.value = 1; + customDayValue.value = 1; + customWeekValue.value = 1; +}; + +const hourChange = (hourValue) => { + if (hourValue === '鑷畾涔�') { + formValue.value = `PT${customHourValue.value}H`; + } else { + formValue.value = `PT${hourValue}H`; + } + + dayValue.value = ''; + weekValue.value = ''; + monthValue.value = ''; + customDayValue.value = 1; + customWeekValue.value = 1; + customMonthValue.value = 1; +}; +const dayChange = (dayValue) => { + if (dayValue === '鑷畾涔�') { + formValue.value = `P${customDayValue.value}D`; + } else { + formValue.value = `P${dayValue}D`; + } + + hourValue.value = ''; + weekValue.value = ''; + monthValue.value = ''; + + customHourValue.value = 1; + customWeekValue.value = 1; + customMonthValue.value = 1; +}; +const weekChange = (weekValue) => { + if (weekValue === '鑷畾涔�') { + formValue.value = `P${customWeekValue.value}W`; + } else { + formValue.value = `P${weekValue}W`; + } + + hourValue.value = ''; + dayValue.value = ''; + monthValue.value = ''; + + customHourValue.value = 1; + customDayValue.value = 1; + customMonthValue.value = 1; +}; +const monthChange = (monthValue) => { + if (monthValue === '鑷畾涔�') { + formValue.value = `P${customMonthValue.value}M`; + } else { + formValue.value = `P${monthValue}M`; + } + + hourValue.value = ''; + dayValue.value = ''; + weekValue.value = ''; + + customHourValue.value = 1; + customDayValue.value = 1; + customWeekValue.value = 1; +}; + +watch( + () => visible.value, + () => { + if (visible.value) { + initValue(); + } + } +); + +defineExpose({ + openDialog, + closeDialog +}); +</script> diff --git a/src/components/BpmnDesign/panel/property/ExecutionListener.vue b/src/components/BpmnDesign/panel/property/ExecutionListener.vue new file mode 100644 index 0000000..ecd5034 --- /dev/null +++ b/src/components/BpmnDesign/panel/property/ExecutionListener.vue @@ -0,0 +1,305 @@ +<template> + <div> + <vxe-toolbar> + <template #buttons> + <el-button type="primary" link size="small" @click="insertEvent">鏂板</el-button> + <el-button type="primary" link size="small" @click="removeSelectRowEvent">鍒犻櫎</el-button> + </template> + </vxe-toolbar> + <vxe-table + ref="tableRef" + size="mini" + height="100px" + border + show-overflow + keep-source + :data="tableData" + :menu-config="menuConfig" + @cell-dblclick="cellDBLClickEvent" + @menu-click="contextMenuClickEvent" + > + <vxe-column type="checkbox" width="40"></vxe-column> + <vxe-column type="seq" width="40"></vxe-column> + <vxe-column field="event" title="浜嬩欢" min-width="100px"> + <template #default="slotParams"> + <span>{{ eventSelect.find((e) => e.value === slotParams.row.event)?.label }}</span> + </template> + </vxe-column> + <vxe-column field="type" title="绫诲瀷" min-width="100px"> + <template #default="slotParams"> + <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span> + </template> + </vxe-column> + <vxe-column field="className" title="Java 绫诲悕" min-width="100px"> </vxe-column> + </vxe-table> + + <el-dialog + v-model="formDialog.visible.value" + :title="formDialog.title.value" + width="600px" + :close-on-click-modal="false" + :close-on-press-escape="false" + :show-close="false" + append-to-body + > + <el-form ref="formRef" :model="formData" :rules="tableRules" label-width="90px"> + <el-form-item label="浜嬩欢" prop="event"> + <el-select v-model="formData.event"> + <el-option v-for="item in eventSelect" :key="item.id" :value="item.value" :label="item.label"></el-option> + </el-select> + </el-form-item> + <el-form-item label="绫诲瀷" prop="type"> + <template #label> + <span> + 绫诲瀷 + <el-tooltip placement="top"> + <el-icon><QuestionFilled /></el-icon> + <template #content> + 绫伙細绀轰緥 com.company.MyCustomListener锛岃嚜瀹氫箟绫诲繀椤诲疄鐜� org.flowable.engine.delegate.TaskListener 鎺ュ彛<br /> + 琛ㄨ揪寮忥細绀轰緥 ${myObject.callMethod(task, task.eventName)}<br /> + 濮旀墭琛ㄨ揪寮忥細绀轰緥 ${myListenerSpringBean} 锛岃 springBean 闇�瑕佸疄鐜� org.flowable.engine.delegate.TaskListener 鎺ュ彛 + </template> + </el-tooltip> + </span> + </template> + <el-select v-model="formData.type"> + <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option> + </el-select> + </el-form-item> + <el-form-item label="Java 绫诲悕" prop="className"> + <el-input v-model="formData.className" type="text"></el-input> + </el-form-item> + </el-form> + <el-tabs type="border-card"> + <el-tab-pane label="鍙傛暟"> + <ListenerParam ref="listenerParamRef" :table-data="formData.params" /> + </el-tab-pane> + </el-tabs> + <template #footer> + <div class="dialog-footer"> + <el-button @click="formDialog.closeDialog">鍙� 娑�</el-button> + <el-button type="primary" @click="submitEvent">纭� 瀹�</el-button> + </div> + </template> + </el-dialog> + </div> +</template> +<script setup lang="ts"> +import ListenerParam from './ListenerParam.vue'; +import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table'; +import { ExecutionListenerVO } from 'bpmnDesign'; +import { Moddle, Modeler, ModdleElement } from 'bpmn'; + +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import useDialog from '@/hooks/useDialog'; +import useModelerStore from '@/store/modules/modeler'; + +const emit = defineEmits(['close']); +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const selectRow = ref<ExecutionListenerVO | null>(); +const formDialog = useDialog({ + title: selectRow.value ? '缂栬緫&淇濆瓨' : '鏂板&淇濆瓨' +}); + +const { showConfig, elementType, updateProperties } = usePanel({ + element: toRaw(props.element) +}); +const { getModdle } = useModelerStore(); +const moddle = getModdle(); + +const listenerParamRef = ref<InstanceType<typeof ListenerParam>>(); +const tableRef = ref<VxeTableInstance<ExecutionListenerVO>>(); +const formRef = ref<ElFormInstance>(); + +const initData: ExecutionListenerVO = { + event: '', + type: '', + className: '', + params: [] +}; +const formData = ref<ExecutionListenerVO>({ ...initData }); +const tableData = ref<ExecutionListenerVO[]>([]); +const tableRules = ref<ElFormRules>({ + event: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + type: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + className: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); + +const submitEvent = async () => { + const error = await listenerParamRef.value.validate(); + await formRef.value.validate((validate) => { + if (validate && !error) { + const $table = tableRef.value; + if ($table) { + formData.value.params = listenerParamRef.value.getTableData(); + if (selectRow.value) { + Object.assign(selectRow.value, formData.value); + } else { + $table.insertAt({ ...formData.value }, -1); + } + updateElement(); + formDialog.closeDialog(); + } + } + }); +}; + +const removeSelectRowEvent = async () => { + const $table = tableRef.value; + if ($table) { + const selectCount = $table.getCheckboxRecords().length; + if (selectCount === 0) { + proxy?.$modal.msgWarning('璇烽�夋嫨琛�'); + } else { + await $table.removeCheckboxRow(); + updateElement(); + } + } +}; +const insertEvent = async () => { + Object.assign(formData.value, initData); + selectRow.value = null; + formDialog.openDialog(); +}; + +const editEvent = (row: ExecutionListenerVO) => { + Object.assign(formData.value, row); + selectRow.value = row; + formDialog.openDialog(); +}; + +const removeEvent = async (row: ExecutionListenerVO) => { + await proxy?.$modal.confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ユ暟鎹�?'); + const $table = tableRef.value; + if ($table) { + await $table.remove(row); + updateElement(); + } +}; +const updateElement = () => { + const $table = tableRef.value; + const data = $table.getTableData().fullData; + if (data.length) { + let extensionElements = props.element.businessObject.get('extensionElements'); + if (!extensionElements) { + extensionElements = moddle.create('bpmn:ExtensionElements'); + } + // 娓呴櫎鏃у�� + extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:ExecutionListener') ?? []; + data.forEach((item) => { + const executionListener = moddle.create('flowable:ExecutionListener'); + executionListener['event'] = item.event; + executionListener[item.type] = item.className; + if (item.params && item.params.length) { + item.params.forEach((field) => { + const fieldElement = moddle.create('flowable:Field'); + fieldElement['name'] = field.name; + fieldElement[field.type] = field.value; + executionListener.get('fields').push(fieldElement); + }); + } + extensionElements.get('values').push(executionListener); + }); + updateProperties({ extensionElements: extensionElements }); + } else { + const extensionElements = props.element.businessObject[`extensionElements`]; + if (extensionElements) { + extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:ExecutionListener') ?? []; + } + } +}; + +const cellDBLClickEvent: VxeTableEvents.CellDblclick<ExecutionListenerVO> = ({ row }) => { + editEvent(row); +}; + +const menuConfig = reactive<VxeTablePropTypes.MenuConfig<ExecutionListenerVO>>({ + body: { + options: [ + [ + { code: 'edit', name: '缂栬緫', prefixIcon: 'vxe-icon-edit', disabled: false }, + { code: 'remove', name: '鍒犻櫎', prefixIcon: 'vxe-icon-delete', disabled: false } + ] + ] + }, + visibleMethod({ options, column }) { + const isDisabled = !column; + options.forEach((list) => { + list.forEach((item) => { + item.disabled = isDisabled; + }); + }); + return true; + } +}); +const contextMenuClickEvent: VxeTableEvents.MenuClick<ExecutionListenerVO> = ({ menu, row, column }) => { + const $table = tableRef.value; + if ($table) { + switch (menu.code) { + case 'edit': + editEvent(row); + break; + case 'remove': + removeEvent(row); + break; + } + } +}; + +const initTableData = () => { + tableData.value = + props.element.businessObject.extensionElements?.values + .filter((item) => item.$type === 'flowable:ExecutionListener') + .map((item) => { + let type; + if ('class' in item) type = 'class'; + if ('expression' in item) type = 'expression'; + if ('delegateExpression' in item) type = 'delegateExpression'; + return { + event: item.event, + type: type, + className: item[type], + params: + item.fields?.map((field) => { + let fieldType; + if ('stringValue' in field) fieldType = 'stringValue'; + if ('expression' in field) fieldType = 'expression'; + return { + name: field.name, + type: fieldType, + value: field[fieldType] + }; + }) ?? [] + }; + }) ?? []; +}; + +onMounted(() => { + initTableData(); +}); + +const typeSelect = [ + { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '绫�', value: 'class' }, + { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '琛ㄨ揪寮�', value: 'expression' }, + { id: '4b8135ab-6bc3-4a0f-80be-22f58bc6c5fd', label: '濮旀墭琛ㄨ揪寮�', value: 'delegateExpression' } +]; +const eventSelect = [ + { id: 'e6e0a51a-2d5d-4dc4-b847-b5c14f43a6ab', label: 'start', value: 'start' }, + { id: '6da97c1e-15fc-4445-8943-75d09f49778e', label: 'end', value: 'end' }, + { id: '6a2cbcec-e026-4f11-bef7-fff0b5c871e2', label: 'take', value: 'take' } +]; +</script> + +<style scoped lang="scss"> +.el-badge { + :deep(.el-badge__content) { + top: 10px; + } +} +</style> diff --git a/src/components/BpmnDesign/panel/property/ListenerParam.vue b/src/components/BpmnDesign/panel/property/ListenerParam.vue new file mode 100644 index 0000000..334249b --- /dev/null +++ b/src/components/BpmnDesign/panel/property/ListenerParam.vue @@ -0,0 +1,121 @@ +<template> + <vxe-toolbar> + <template #buttons> + <el-button icon="Plus" @click="insertRow">鏂板</el-button> + </template> + </vxe-toolbar> + <vxe-table + ref="tableRef" + :height="height" + border + show-overflow + keep-source + :data="tableData" + :edit-rules="tableRules" + :edit-config="{ trigger: 'click', mode: 'row', showStatus: true }" + > + <vxe-column type="seq" width="40"></vxe-column> + <vxe-column field="type" title="绫诲瀷" :edit-render="{}"> + <template #default="slotParams"> + <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span> + </template> + <template #edit="slotParams"> + <vxe-select v-model="slotParams.row.type"> + <vxe-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></vxe-option> + </vxe-select> + </template> + </vxe-column> + <vxe-column field="name" title="鍚嶇О" :edit-render="{}"> + <template #edit="slotParams"> + <vxe-input v-model="slotParams.row.name" type="text"></vxe-input> + </template> + </vxe-column> + <vxe-column field="value" title="鍊�" :edit-render="{}"> + <template #edit="slotParams"> + <vxe-input v-model="slotParams.row.value" type="text"></vxe-input> + </template> + </vxe-column> + <vxe-column title="鎿嶄綔" width="100" show-overflow align="center"> + <template #default="slotParams"> + <el-tooltip content="鍒犻櫎" placement="top"> + <el-button link type="danger" icon="Delete" @click="removeRow(slotParams.row)"></el-button> + </el-tooltip> + </template> + </vxe-column> + </vxe-table> +</template> + +<script setup lang="ts"> +import { VXETable, VxeTableInstance, VxeTablePropTypes } from 'vxe-table'; +import { ParamVO } from 'bpmnDesign'; +import useDialog from '@/hooks/useDialog'; + +interface PropType { + height?: string; + tableData?: ParamVO[]; +} + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const props = withDefaults(defineProps<PropType>(), { + height: '200px', + tableData: () => [] +}); + +const tableRules = ref<VxeTablePropTypes.EditRules>({ + type: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + value: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); + +const { title, visible, openDialog, closeDialog } = useDialog({ + title: '鐩戝惉鍣ㄥ弬鏁�' +}); +const typeSelect = [ + { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '瀛楃涓�', value: 'stringValue' }, + { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '琛ㄨ揪寮�', value: 'expression' } +]; + +const tableRef = ref<VxeTableInstance<ParamVO>>(); + +const getTableData = () => { + const $table = tableRef.value; + if ($table) { + return $table.getTableData().fullData; + } + return []; +}; + +const insertRow = async () => { + const $table = tableRef.value; + if ($table) { + const { row: newRow } = await $table.insertAt({}, -1); + // 鎻掑叆涓�鏉℃暟鎹苟瑙﹀彂鏍¢獙 + await $table.validate(newRow); + } +}; + +const removeRow = async (row: ParamVO) => { + await proxy?.$modal.confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ユ暟鎹�?'); + const $table = tableRef.value; + if ($table) { + await $table.remove(row); + } +}; + +const validate = async () => { + const $table = tableRef.value; + if ($table) { + return await $table.validate(true); + } +}; + +defineExpose({ + closeDialog, + openDialog, + validate, + getTableData +}); +</script> + +<style scoped lang="scss"></style> diff --git a/src/components/BpmnDesign/panel/property/TaskListener.vue b/src/components/BpmnDesign/panel/property/TaskListener.vue new file mode 100644 index 0000000..f90c9a8 --- /dev/null +++ b/src/components/BpmnDesign/panel/property/TaskListener.vue @@ -0,0 +1,307 @@ +<template> + <div> + <vxe-toolbar> + <template #buttons> + <el-button type="primary" link size="small" @click="insertEvent">鏂板</el-button> + <el-button type="primary" link size="small" @click="removeSelectRowEvent">鍒犻櫎</el-button> + </template> + </vxe-toolbar> + <vxe-table + ref="tableRef" + size="mini" + height="100px" + border + show-overflow + keep-source + :data="tableData" + :menu-config="menuConfig" + @cell-dblclick="cellDBLClickEvent" + @menu-click="contextMenuClickEvent" + > + <vxe-column type="checkbox" width="40"></vxe-column> + <vxe-column type="seq" width="40"></vxe-column> + <vxe-column field="event" title="浜嬩欢" min-width="100px"> + <template #default="slotParams"> + <span>{{ eventSelect.find((e) => e.value === slotParams.row.event)?.label }}</span> + </template> + </vxe-column> + <vxe-column field="type" title="绫诲瀷" min-width="100px"> + <template #default="slotParams"> + <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span> + </template> + </vxe-column> + <vxe-column field="className" title="Java 绫诲悕" min-width="100px"> </vxe-column> + </vxe-table> + + <el-dialog + v-model="formDialog.visible.value" + :title="formDialog.title.value" + width="600px" + :close-on-click-modal="false" + :close-on-press-escape="false" + :show-close="false" + append-to-body + > + <el-form ref="formRef" :model="formData" :rules="tableRules" label-width="90px"> + <el-form-item label="浜嬩欢" prop="event"> + <template #label> + <span> + 浜嬩欢 + <el-tooltip placement="top"> + <el-icon><QuestionFilled /></el-icon> + <template #content> + create锛堝垱寤猴級锛氬綋浠诲姟宸茬粡鍒涘缓锛屽苟涓旀墍鏈変换鍔″弬鏁伴兘宸茬粡璁剧疆鏃惰Е鍙戙��<br /> + assignment锛堟寚娲撅級锛氬綋浠诲姟宸茬粡鎸囨淳缁欐煇浜烘椂瑙﹀彂銆傝娉ㄦ剰锛氬綋娴佺▼鎵ц鍒拌揪鐢ㄦ埛浠诲姟鏃讹紝鍦ㄨЕ鍙慶reate浜嬩欢涔嬪墠锛屼細棣栧厛瑙﹀彂assignment浜嬩欢銆�<br /> + complete锛堝畬鎴愶級锛氬綋浠诲姟宸茬粡瀹屾垚锛屼粠杩愯鏃舵暟鎹腑鍒犻櫎鍓嶈Е鍙戙��<br /> + delete锛堝垹闄わ級锛氬湪浠诲姟鍗冲皢琚垹闄ゅ墠瑙﹀彂銆傝娉ㄦ剰浠诲姟鐢眂ompleteTask姝e父瀹屾垚鏃朵篃浼氳Е鍙戙�� + </template> + </el-tooltip> + </span> + </template> + <el-select v-model="formData.event"> + <el-option v-for="item in eventSelect" :key="item.id" :value="item.value" :label="item.label"></el-option> + </el-select> + </el-form-item> + <el-form-item label="绫诲瀷" prop="type"> + <el-select v-model="formData.type"> + <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option> + </el-select> + </el-form-item> + <el-form-item label="Java 绫诲悕" prop="className"> + <el-input v-model="formData.className" type="text"></el-input> + </el-form-item> + </el-form> + <el-tabs type="border-card"> + <el-tab-pane label="鍙傛暟"> + <ListenerParam ref="listenerParamRef" :table-data="formData.params" /> + </el-tab-pane> + </el-tabs> + <template #footer> + <div class="dialog-footer"> + <el-button @click="formDialog.closeDialog">鍙� 娑�</el-button> + <el-button type="primary" @click="submitEvent">纭� 瀹�</el-button> + </div> + </template> + </el-dialog> + </div> +</template> +<script setup lang="ts"> +import ListenerParam from './ListenerParam.vue'; +import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table'; +import { TaskListenerVO } from 'bpmnDesign'; +import { ModdleElement } from 'bpmn'; + +import usePanel from '@/components/BpmnDesign/hooks/usePanel'; +import useDialog from '@/hooks/useDialog'; +import useModelerStore from '@/store/modules/modeler'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +interface PropType { + element: ModdleElement; +} +const props = withDefaults(defineProps<PropType>(), {}); + +const selectRow = ref<TaskListenerVO | null>(); +const formDialog = useDialog({ + title: selectRow.value ? '缂栬緫&淇濆瓨' : '鏂板&淇濆瓨' +}); +const { showConfig, elementType, updateProperties } = usePanel({ + element: toRaw(props.element) +}); +const { getModdle } = useModelerStore(); +const moddle = getModdle(); + +const listenerParamRef = ref<InstanceType<typeof ListenerParam>>(); +const tableRef = ref<VxeTableInstance<TaskListenerVO>>(); +const formRef = ref<ElFormInstance>(); + +const initData: TaskListenerVO = { + event: '', + type: '', + className: '', + name: '', + params: [] +}; +const formData = ref<TaskListenerVO>({ ...initData }); +const currentIndex = ref(0); +const tableData = ref<TaskListenerVO[]>([]); +const tableRules = ref<VxeTablePropTypes.EditRules>({ + event: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + type: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }], + name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }], + className: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }] +}); + +const submitEvent = async () => { + const error = await listenerParamRef.value.validate(); + await formRef.value.validate((validate) => { + if (validate && !error) { + const $table = tableRef.value; + if ($table) { + formData.value.params = listenerParamRef.value.getTableData(); + if (selectRow.value) { + Object.assign(selectRow.value, formData.value); + } else { + $table.insertAt({ ...formData.value }, -1); + } + updateElement(); + formDialog.closeDialog(); + } + } + }); +}; + +const insertEvent = async () => { + Object.assign(formData.value, initData); + selectRow.value = null; + formDialog.openDialog(); +}; + +const editEvent = (row: TaskListenerVO) => { + Object.assign(formData.value, row); + selectRow.value = row; + formDialog.openDialog(); +}; +const removeEvent = async (row: TaskListenerVO) => { + await proxy?.$modal.confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ユ暟鎹�?'); + const $table = tableRef.value; + if ($table) { + await $table.remove(row); + updateElement(); + } +}; + +const removeSelectRowEvent = async () => { + const $table = tableRef.value; + if ($table) { + const selectCount = $table.getCheckboxRecords().length; + if (selectCount === 0) { + proxy?.$modal.msgWarning('璇烽�夋嫨琛�'); + } else { + await $table.removeCheckboxRow(); + updateElement(); + } + } +}; +const updateElement = () => { + const $table = tableRef.value; + const data = $table.getTableData().fullData; + if (data.length) { + let extensionElements = props.element.businessObject.get('extensionElements'); + if (!extensionElements) { + extensionElements = moddle.create('bpmn:ExtensionElements'); + } + // 娓呴櫎鏃у�� + extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:TaskListener') ?? []; + data.forEach((item) => { + const taskListener = moddle.create('flowable:TaskListener'); + taskListener['event'] = item.event; + taskListener[item.type] = item.className; + if (item.params && item.params.length) { + item.params.forEach((field) => { + const fieldElement = moddle.create('flowable:Field'); + fieldElement['name'] = field.name; + fieldElement[field.type] = field.value; + taskListener.get('fields').push(fieldElement); + }); + } + extensionElements.get('values').push(taskListener); + }); + updateProperties({ extensionElements: extensionElements }); + } else { + const extensionElements = props.element.businessObject[`extensionElements`]; + if (extensionElements) { + extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:TaskListener') ?? []; + } + } +}; + +const cellDBLClickEvent: VxeTableEvents.CellDblclick<TaskListenerVO> = ({ row }) => { + editEvent(row); +}; + +const menuConfig = reactive<VxeTablePropTypes.MenuConfig<TaskListenerVO>>({ + body: { + options: [ + [ + { code: 'edit', name: '缂栬緫', prefixIcon: 'vxe-icon-edit', disabled: false }, + { code: 'remove', name: '鍒犻櫎', prefixIcon: 'vxe-icon-delete', disabled: false } + ] + ] + }, + visibleMethod({ options, column }) { + const isDisabled = !column; + options.forEach((list) => { + list.forEach((item) => { + item.disabled = isDisabled; + }); + }); + return true; + } +}); +const contextMenuClickEvent: VxeTableEvents.MenuClick<TaskListenerVO> = ({ menu, row, column }) => { + const $table = tableRef.value; + if ($table) { + switch (menu.code) { + case 'edit': + editEvent(row); + break; + case 'remove': + removeEvent(row); + break; + } + } +}; +const initTableData = () => { + tableData.value = + props.element.businessObject.extensionElements?.values + .filter((item) => item.$type === 'flowable:TaskListener') + .map((item) => { + let type; + if ('class' in item) type = 'class'; + if ('expression' in item) type = 'expression'; + if ('delegateExpression' in item) type = 'delegateExpression'; + return { + event: item.event, + type: type, + className: item[type], + params: + item.fields?.map((field) => { + let fieldType; + if ('stringValue' in field) fieldType = 'stringValue'; + if ('expression' in field) fieldType = 'expression'; + return { + name: field.name, + type: fieldType, + value: field[fieldType] + }; + }) ?? [] + }; + }) ?? []; +}; + +onMounted(() => { + initTableData(); +}); + +const typeSelect = [ + { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '绫�', value: 'class' }, + { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '琛ㄨ揪寮�', value: 'expression' }, + { id: '4b8135ab-6bc3-4a0f-80be-22f58bc6c5fd', label: '濮旀墭琛ㄨ揪寮�', value: 'delegateExpression' } +]; +const eventSelect = [ + { id: 'e6e0a51a-2d5d-4dc4-b847-b5c14f43a6ab', label: '鍒涘缓', value: 'create' }, + { id: '6da97c1e-15fc-4445-8943-75d09f49778e', label: '鎸囨淳', value: 'assignment' }, + { id: '6a2cbcec-e026-4f11-bef7-fff0b5c871e2', label: '瀹屾垚', value: 'complete' }, + { id: '68801972-85f1-482f-bd86-1fad015c26ed', label: '鍒犻櫎', value: 'delete' } +]; +</script> + +<style scoped lang="scss"> +.el-badge { + :deep(.el-badge__content) { + top: 10px; + } +} +</style> diff --git a/src/components/Process/approvalRecord.vue b/src/components/Process/approvalRecord.vue new file mode 100644 index 0000000..d06e7ef --- /dev/null +++ b/src/components/Process/approvalRecord.vue @@ -0,0 +1,162 @@ +<template> + <el-dialog v-model="visible" draggable title="瀹℃壒璁板綍" :width="props.width" :height="props.height" append-to-body + :close-on-click-modal="false"> + <div v-loading="loading"> + <div style="width: 100%;height: 300px;overflow: auto;position: relative;"> + <div v-for="(graphic, index) in graphicInfoVos" :key="index" :style="{ + position: 'absolute', + left: `${graphic.x}px`, + top: `${graphic.y}px`, + width: `${graphic.width}px`, + height: `${graphic.height}px`, + cursor: 'pointer', + zIndex: 99 + }" @mouseover="handleMouseOver(graphic)" @mouseleave="handleMouseLeave()"></div> + <!-- 寮瑰嚭鐨� div 鍏冪礌 --> + <div v-show="popupVisible" class="triangle" :style="{ + position: 'absolute', + left: `${graphicX}px`, + top: `${graphicY}px`, + backgroundColor: '#fff', + padding: '10px', + zIndex: 100 + }"> + <p>瀹℃壒浜哄憳: {{ nodeInfo.nickName }}</p> + <p>鑺傜偣鐘舵�侊細{{ nodeInfo.status }}</p> + <p>寮�濮嬫椂闂达細{{ nodeInfo.startTime }}</p> + <p>缁撴潫鏃堕棿锛歿{ nodeInfo.endTime }}</p> + <p>瀹℃壒鑰楁椂锛歿{ nodeInfo.runDuration }}</p> + </div> + <el-image :src="src" /> + </div> + <div> + <el-table :data="historyList" style="width: 100%" border fit max-height="570"> + <el-table-column label="娴佺▼瀹℃壒鍘嗗彶璁板綍" align="center"> + <el-table-column type="index" label="搴忓彿" align="center" width="50"></el-table-column> + <el-table-column prop="name" label="浠诲姟鍚嶇О" sortable align="center"></el-table-column> + <el-table-column prop="nickName" label="鍔炵悊浜�" sortable align="center"></el-table-column> + <el-table-column label="鐘舵��" sortable align="center"> + <template #default="scope"> + <el-tag type="success">{{ scope.row.statusName }}</el-tag> + </template> + </el-table-column> + <el-table-column prop="comment" label="瀹℃壒鎰忚" sortable align="center"></el-table-column> + <el-table-column prop="attachmentList" label="闄勪欢" sortable align="center"> + <template #default="scope"> + <el-popover placement="right" v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" :width="310" trigger="click"> + <template #reference> + <el-button style="margin-right: 16px">闄勪欢</el-button> + </template> + <el-table border :data="scope.row.attachmentList"> + <el-table-column prop="name" width="202" :show-overflow-tooltip="true" label="闄勪欢鍚嶇О"></el-table-column> + <el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="鎿嶄綔"> + <template #default="tool"> + <el-button type="text" @click="handleDownload(tool.row.contentId)">涓嬭浇</el-button> + </template> + </el-table-column> + </el-table> + + </el-popover> + </template> + </el-table-column> + <el-table-column prop="startTime" label="寮�濮嬫椂闂�" sortable align="center"></el-table-column> + <el-table-column prop="endTime" label="缁撴潫鏃堕棿" sortable align="center"></el-table-column> + <el-table-column prop="runDuration" label="杩愯鏃堕暱" sortable align="center"></el-table-column> + </el-table-column> + </el-table> + </div> + </div> + </el-dialog> +</template> +<script lang="ts" setup> +import { getHistoryProcessImage, getHistoryRecord } from '@/api/workflow/processInstance'; +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +import { ref } from 'vue'; +const props = defineProps({ + width: { + type: String, + default: '70%' + }, + height: { + type: String, + default: '100%' + } +}); +const loading = ref(false); +const src = ref(''); +const visible = ref(false); +const historyList = ref<Array<any>>([]); +const deleteReason = ref<string>(''); +const graphicInfoVos = ref<Array<any>>([]); +const nodeListInfo = ref<Array<any>>([]); +const popupVisible = ref(false); +const nodeInfo = ref<any>({}); +const graphicX = ref<number | string>(0); +const graphicY = ref<number | string>(0); +//鍒濆鍖栨煡璇㈠鎵硅褰� +const init = async (processInstanceId: string) => { + visible.value = true; + loading.value = true; + historyList.value = []; + graphicInfoVos.value = []; + getHistoryProcessImage(processInstanceId).then((res) => { + src.value = 'data:image/png;base64,' + res.data + }); + getHistoryRecord(processInstanceId).then((response) => { + historyList.value = response.data.historyRecordList; + graphicInfoVos.value = response.data.graphicInfoVos; + nodeListInfo.value = response.data.nodeListInfo; + deleteReason.value = response.data.deleteReason; + loading.value = false; + }); +}; +//鎮诞浜嬩欢 +const handleMouseOver = async (graphic: any) => { + graphicX.value = graphic.x + graphic.width + 10; + graphicY.value = graphic.y - graphic.height + -10; + nodeInfo.value = {}; + if (nodeListInfo.value && nodeListInfo.value.length > 0) { + let info = nodeListInfo.value.find((e: any) => e.taskDefinitionKey == graphic.nodeId); + if (info) { + nodeInfo.value = { + nickName: info.nickName, + status: info.status, + startTime: info.startTime, + endTime: info.endTime, + runDuration: info.runDuration + }; + popupVisible.value = true; + } + } +}; +//鍏抽棴 +const handleMouseLeave = async () => { + popupVisible.value = false; +}; + +/** 涓嬭浇鎸夐挳鎿嶄綔 */ +const handleDownload = (ossId: string) => { + proxy?.$download.oss(ossId); +}; +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + init +}); +</script> +<style scoped> +.triangle { + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + border-radius: 6px; +} + +.triangle::after { + content: ' '; + position: absolute; + top: 8em; + right: 215px; + border: 15px solid; + border-color: transparent #fff transparent transparent; +} +</style> diff --git a/src/components/Process/multiInstance-user.vue b/src/components/Process/multiInstance-user.vue new file mode 100644 index 0000000..96fa21f --- /dev/null +++ b/src/components/Process/multiInstance-user.vue @@ -0,0 +1,362 @@ +<template> + <el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body + :close-on-click-modal="false"> + <div class="p-2" v-if="multiInstance === 'add'"> + <el-row :gutter="20"> + <!-- 閮ㄩ棬鏍� --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="deptName" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" prefix-icon="Search" clearable /> + <el-tree class="mt-2" ref="deptTreeRef" node-key="id" :data="deptOptions" + :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" + :filter-node-method="filterNode" highlight-current default-expand-all + @node-click="handleNodeClick"></el-tree> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" + :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div class="search" v-show="showSearch"> + <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName"> + <el-input v-model="queryParams.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" clearable style="width: 240px" + @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber"> + <el-input v-model="queryParams.phonenumber" placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�" clearable style="width: 240px" + @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleQuery" icon="Search">鎼滅储</el-button> + <el-button @click="resetQuery" icon="Refresh">閲嶇疆</el-button> + </el-form-item> + </el-form> + </div> + </transition> + + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10"> + <right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery" :search="true"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="userList" ref="multipleTableRef" row-key="userId" + @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="50" align="center" /> + <el-table-column label="鐢ㄦ埛缂栧彿" align="center" key="userId" prop="userId" /> + <el-table-column label="鐢ㄦ埛鍚嶇О" align="center" key="userName" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column label="鐢ㄦ埛鏄电О" align="center" key="nickName" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column label="鎵嬫満鍙风爜" align="center" key="phonenumber" prop="phonenumber" width="120" /> + <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="160"> + <template #default="scope"> + <span>{{ scope.row.createTime }}</span> + </template> + </el-table-column> + </el-table> + + <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" @pagination="handleQuery" /> + </el-card> + <el-card shadow="hover"> + <el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin:2px" closable + @close="handleCloseTag(user, index)">{{ user.userName }} + </el-tag> + </el-card> + </el-col> + </el-row> + </div> + <div class="p-2" v-if="multiInstance === 'delete'"> + <el-table v-loading="loading" :data="taskList" @selection-change="handleTaskSelection"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" label="浠诲姟鍚嶇О" /> + <el-table-column prop="assigneeName" label="鍔炵悊浜�" /> + </el-table> + </div> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button> + <el-button @click="visible = false">鍙� 娑�</el-button> + </div> + </template> + </el-dialog> +</template> + +<script setup name="User" lang="ts"> +import { deptTreeSelect } from '@/api/system/user'; +import { getWorkflowAddMultiListByPage, getWorkflowDeleteMultiInstanceList, getUserListByIds } from '@/api/workflow/workflowUser'; +import { addMultiInstanceExecution, deleteMultiInstanceExecution } from '@/api/workflow/task'; +import { UserVO } from '@/api/system/user/types'; +import { DeptVO } from '@/api/system/dept/types'; +import { ComponentInternalInstance } from 'vue'; +import { ElTree, ElTable } from 'element-plus'; +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const props = defineProps({ + // 瀹� + width: { + type: String, + default: '70%' + }, + // 楂� + height: { + type: String, + default: '100%' + }, + // 鏍囬 + title: { + type: String, + default: '鍔犵浜哄憳' + }, + //鏄惁澶氶�� + multiple: { + type: Boolean, + default: true + }, + //鍥炴樉鐢ㄦ埛id + userIdList: { + type: Array, + default: [] + } +}); +const deptTreeRef = ref(ElTree); +const multipleTableRef = ref(ElTable); + +const userList = ref<UserVO[]>(); +const taskList = ref<Array<any>[]>(); +const loading = ref(true); +const showSearch = ref(true); +const selectionTask = ref<Array<any>[]>(); +const visible = ref(false); +const total = ref(0); +const deptName = ref(''); +const deptOptions = ref<DeptVO[]>([]); +const chooseUserList = ref(ref<UserVO[]>()); +const userIds = ref<Array<number | string>>([]); +//鍔犵鎴栬�呭噺绛� +const multiInstance = ref(''); +const queryParams = ref<Record<string, any>>({ + pageNum: 1, + pageSize: 10, + userName: '', + nickName: '', + taskId: '' +}); +/** 鏌ヨ鐢ㄦ埛鍒楄〃 */ +const getAddMultiInstanceList = async (taskId: string, userIdList: Array<number | string>) => { + deptOptions.value = []; + getTreeSelect(); + multiInstance.value = 'add'; + userIds.value = userIdList; + visible.value = true; + queryParams.value.taskId = taskId; + loading.value = true; + const res = await getWorkflowAddMultiListByPage(queryParams.value); + loading.value = false; + userList.value = res.rows; + total.value = res.total; + if (userList.value && userIds.value.length > 0) { + const data = await getUserListByIds(userIds.value); + if (data.data && data.data.length > 0) { + chooseUserList.value = data.data; + data.data.forEach((user: UserVO) => { + multipleTableRef.value!.toggleRowSelection( + userList.value.find((item) => { + return item.userId == user.userId; + }), + true + ); + }); + } + } +}; + +const getList = async () => { + loading.value = true; + const res = await getWorkflowAddMultiListByPage(queryParams.value); + loading.value = false; + userList.value = res.rows; + total.value = res.total; + if (userList.value && userIds.value.length > 0) { + const data = await getUserListByIds(userIds.value); + if (data.data && data.data.length > 0) { + chooseUserList.value = data.data; + data.data.forEach((user: UserVO) => { + multipleTableRef.value!.toggleRowSelection( + userList.value.find((item) => { + return item.userId == user.userId; + }), + true + ); + }); + } + } +}; + +const getDeleteMultiInstanceList = async (taskId: string) => { + deptOptions.value = []; + loading.value = true; + queryParams.value.taskId = taskId; + multiInstance.value = 'delete'; + visible.value = true; + const res = await getWorkflowDeleteMultiInstanceList(taskId); + taskList.value = res.data; + loading.value = false; +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getAddMultiInstanceList(queryParams.value.taskId, userIds.value); +}; + +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryParams.value.pageNum = 1; + queryParams.value.deptId = undefined; + queryParams.value.userName = undefined; + queryParams.value.nickName = undefined; + deptTreeRef.value.setCurrentKey(null); + handleQuery(); +}; + +/** 閫夋嫨鏉℃暟 */ +const handleSelectionChange = (selection: UserVO[]) => { + if (props.multiple) { + chooseUserList.value = selection.filter((element, index, self) => { + return self.findIndex((x) => x.userId === element.userId) === index; + }); + selection.forEach((u) => { + if (chooseUserList.value && !chooseUserList.value.includes(u)) { + multipleTableRef.value!.toggleRowSelection(u, undefined); + } + }); + userIds.value = chooseUserList.value.map((item) => { + return item.userId; + }); + } else { + chooseUserList.value = selection; + if (selection.length > 1) { + let delRow = selection.shift(); + multipleTableRef.value!.toggleRowSelection(delRow, undefined); + } + if (selection.length === 0) { + chooseUserList.value = []; + } + } +}; +/** 閫夋嫨鏉℃暟 */ +const handleTaskSelection = (selection: any) => { + selectionTask.value = selection; +}; + +/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */ +const getTreeSelect = async () => { + const res = await deptTreeSelect(); + deptOptions.value = res.data; +}; + +/** 閫氳繃鏉′欢杩囨护鑺傜偣 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.label.indexOf(value) !== -1; +}; +/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */ +watchEffect( + () => { + if (visible.value && deptOptions.value && deptOptions.value.length > 0) { + deptTreeRef.value.filter(deptName.value); + } + }, + { + flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛� + } +); +/** 鑺傜偣鍗曞嚮浜嬩欢 */ +const handleNodeClick = (data: DeptVO) => { + queryParams.value.deptId = data.id; + getList(); +}; +//鍒犻櫎tag +const handleCloseTag = (user: UserVO, index: any) => { + if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) { + multipleTableRef.value.selection.forEach((u: UserVO, i: Number) => { + if (user.userId === u.userId) { + multipleTableRef.value.selection.splice(i, 1); + } + }); + } + if (chooseUserList.value && chooseUserList.value.length > 0) { + chooseUserList.value.splice(index, 1); + } + multipleTableRef.value.toggleRowSelection(user, undefined); + + if (userIds.value && userIds.value.length > 0) { + userIds.value.forEach((userId, i) => { + if (userId === user.userId) { + userIds.value.splice(i, 1); + } + }); + } +}; +const submitFileForm = async () => { + if (multiInstance.value === 'add') { + if (chooseUserList.value && chooseUserList.value.length > 0) { + loading.value = true; + let userIds = chooseUserList.value.map((item) => { + return item.userId; + }); + let nickNames = chooseUserList.value.map((item) => { + return item.nickName; + }); + let params = { + taskId: queryParams.value.taskId, + assignees: userIds, + assigneeNames: nickNames + }; + await addMultiInstanceExecution(params); + emits('submitCallback'); + loading.value = false; + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); + visible.value = false; + } + } else { + if (selectionTask.value && selectionTask.value.length > 0) { + loading.value = true; + let taskIds = selectionTask.value.map((item: any) => { + return item.id; + }); + let executionIds = selectionTask.value.map((item: any) => { + return item.executionId; + }); + let assigneeIds = selectionTask.value.map((item: any) => { + return item.assignee; + }); + let assigneeNames = selectionTask.value.map((item: any) => { + return item.assigneeName; + }); + let params = { + taskId: queryParams.value.taskId, + taskIds: taskIds, + executionIds: executionIds, + assigneeIds: assigneeIds, + assigneeNames: assigneeNames + }; + await deleteMultiInstanceExecution(params); + emits('submitCallback'); + loading.value = false; + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); + visible.value = false; + } + } +}; +//浜嬩欢 +const emits = defineEmits(['submitCallback']); + +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + getAddMultiInstanceList, + getDeleteMultiInstanceList +}); +</script> \ No newline at end of file diff --git a/src/components/Process/submitVerify.vue b/src/components/Process/submitVerify.vue new file mode 100644 index 0000000..b52854f --- /dev/null +++ b/src/components/Process/submitVerify.vue @@ -0,0 +1,165 @@ +<template> + <el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" :close-on-click-modal="false"> + <el-form v-loading="loading" :model="form" label-width="120px"> + <el-form-item label="娑堟伅鎻愰啋"> + <el-checkbox-group v-model="form.messageType"> + <el-checkbox label="1" name="type" disabled>绔欏唴淇�</el-checkbox> + <el-checkbox label="2" name="type">閭欢</el-checkbox> + <el-checkbox label="3" name="type">鐭俊</el-checkbox> + </el-checkbox-group> + </el-form-item> + <el-form-item label="闄勪欢"> + <fileUpload v-model="form.fileId" :fileType="['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'zip']" :fileSize="'20'"/> + </el-form-item> + <el-form-item label="鎶勯��"> + <el-button type="primary" @click="openUserSelectCopy" icon="Plus" circle /> + <el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)"> + {{ user.userName }} + </el-tag> + </el-form-item> + <el-form-item label="瀹℃壒鎰忚" v-if="businessStatus === 'waiting'"> + <el-input v-model="form.message" type="textarea" resize="none" /> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button v-loading="buttonLoading" @click="cancel">鍙栨秷</el-button> + <el-button v-loading="buttonLoading" type="primary" @click="handleCompleteTask"> 鎻愪氦 </el-button> + <el-button v-if="businessStatus === 'waiting'" v-loading="buttonLoading" type="danger" @click="handleBackProcess"> 閫�鍥� </el-button> + </span> + </template> + <UserSelect ref="userSelectCopyRef" :data="selectCopyUserIds" @confirm-call-back="userSelectCopyCallBack"></UserSelect> + </el-dialog> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import { ComponentInternalInstance } from 'vue'; +import { ElForm } from 'element-plus'; +import { completeTask, backProcess, getBusinessStatus } from '@/api/workflow/task'; +import UserSelect from '@/components/UserSelect'; +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +import { UserVO } from '@/api/system/user/types'; +const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>(); + +const props = defineProps({ + taskVariables: { + type: Object as () => Record<string, any>, + default: {} + } +}); +//閬僵灞� +const loading = ref(true); +//鎸夐挳 +const buttonLoading = ref(true); +//娴佺▼鐘舵�� +const businessStatus = ref<string>(''); +//浠诲姟id +const taskId = ref<string>(''); +//鎶勯�佷汉 +const selectCopyUserList = ref<UserVO[]>([]); +//鎶勯�佷汉id +const selectCopyUserIds = ref<string>(''); + + +const dialog = reactive<DialogOption>({ + visible: false, + title: '鎻愮ず' +}); + +const form = ref<Record<string, any>>({ + taskId: undefined, + message: undefined, + variables: {}, + messageType: ['1'], + wfCopyList: [] +}); +//鎵撳紑寮圭獥 +const openDialog = (id?: string) => { + selectCopyUserIds.value = '' + selectCopyUserList.value = [] + form.value.fileId = undefined + taskId.value = id; + form.value.message = undefined; + dialog.visible = true; + loading.value = true; + buttonLoading.value = true; + nextTick(() => { + getBusinessStatus(taskId.value).then((response) => { + businessStatus.value = response.data; + loading.value = false; + buttonLoading.value = false; + }); + }); +}; + +onMounted(() => {}); +const emits = defineEmits(['submitCallback', 'cancelCallback']); + +/** 鍔炵悊娴佺▼ */ +const handleCompleteTask = async () => { + form.value.taskId = taskId.value; + form.value.taskVariables = props.taskVariables; + if(selectCopyUserList && selectCopyUserList.value.length > 0){ + let wfCopyList = [] + selectCopyUserList.value.forEach( e=> { + let copyUser = { + userId: e.userId, + userName: e.nickName + } + wfCopyList.push(copyUser) + }) + form.value.wfCopyList = wfCopyList + } + await proxy?.$modal.confirm('鏄惁纭鎻愪氦锛�'); + loading.value = true; + buttonLoading.value = true; + await completeTask(form.value).finally(() => (loading.value = false)); + dialog.visible = false; + emits('submitCallback'); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); +}; + +/** 椹冲洖娴佺▼ */ +const handleBackProcess = async () => { + form.value.taskId = taskId.value; + await proxy?.$modal.confirm('鏄惁纭椹冲洖鍒扮敵璇蜂汉锛�'); + loading.value = true; + buttonLoading.value = true; + await backProcess(form.value).finally(() => (loading.value = false)); + dialog.visible = false; + emits('submitCallback'); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); +}; +//鍙栨秷 +const cancel = async () => { + dialog.visible = false; + buttonLoading.value = false; + emits('cancelCallback'); +}; +//鎵撳紑鎶勯�佷汉鍛� +const openUserSelectCopy = () => { + userSelectCopyRef.value.open(); +}; +//纭鎶勯�佷汉鍛� +const userSelectCopyCallBack = (data: UserVO[]) => { + if(data && data.length > 0){ + selectCopyUserList.value = data + selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(','); + } +} +//鍒犻櫎鎶勯�佷汉鍛� +const handleCopyCloseTag = (user: UserVO) => { + const userId = user.userId; + // 浣跨敤split鍒犻櫎鐢ㄦ埛 + const index = selectCopyUserList.value.findIndex((item) => item.userId === userId); + selectCopyUserList.value.splice(index, 1); + selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(','); +}; +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + openDialog +}); +</script> diff --git a/src/components/Process/sys-user.vue b/src/components/Process/sys-user.vue new file mode 100644 index 0000000..dc100f3 --- /dev/null +++ b/src/components/Process/sys-user.vue @@ -0,0 +1,301 @@ +<template> + <el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body :close-on-click-modal="false"> + <div class="p-2"> + <el-row :gutter="20"> + <!-- 閮ㄩ棬鏍� --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="deptName" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" prefix-icon="Search" clearable /> + <el-tree + ref="deptTreeRef" + class="mt-2" + node-key="id" + :data="deptOptions" + :props="{ label: 'label', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + ></el-tree> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="search"> + <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName"> + <el-input v-model="queryParams.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" clearable style="width: 240px" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName"> + <el-input v-model="queryParams.nickName" placeholder="璇疯緭鍏ョ敤鎴锋樀绉�" clearable style="width: 240px" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </div> + </transition> + + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10"> + <right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="getUserList"></right-toolbar> + </el-row> + </template> + + <el-table ref="multipleTableRef" v-loading="loading" :data="userList" row-key="userId" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="50" align="center" :reserve-selection="true" /> + <el-table-column key="userId" label="鐢ㄦ埛缂栧彿" align="center" prop="userId" /> + <el-table-column key="userName" label="鐢ㄦ埛鍚嶇О" align="center" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column key="nickName" label="鐢ㄦ埛鏄电О" align="center" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column key="phonenumber" label="鎵嬫満鍙风爜" align="center" prop="phonenumber" width="120" /> + <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="160"> + <template #default="scope"> + <span>{{ scope.row.createTime }}</span> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="getUserList" + /> + </el-card> + <el-card shadow="hover"> + <el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin: 2px" closable @close="handleCloseTag(user, index)" + >{{ user.userName }} + </el-tag> + </el-card> + </el-col> + </el-row> + </div> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button> + <el-button @click="visible = false">鍙� 娑�</el-button> + </div> + </template> + </el-dialog> +</template> + +<script setup name="User" lang="ts"> +import { deptTreeSelect } from '@/api/system/user'; +import { getUserListByPage, getUserListByIds } from '@/api/workflow/workflowUser'; +import { UserVO } from '@/api/system/user/types'; +import { DeptVO } from '@/api/system/dept/types'; +import { ComponentInternalInstance } from 'vue'; +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const props = defineProps({ + // 瀹� + width: { + type: String, + default: '70%' + }, + // 楂� + height: { + type: String, + default: '100%' + }, + // 鏍囬 + title: { + type: String, + default: '鐢ㄦ埛' + }, + //鏄惁澶氶�� + multiple: { + type: Boolean, + default: true + }, + //鍥炴樉鐢ㄦ埛id + userIdList: { + type: Array, + default: () => [] + } +}); +const deptTreeRef = ref<ElTreeInstance>(); +const multipleTableRef = ref<ElTableInstance>(); + +const userList = ref<UserVO[]>(); +const loading = ref(true); +const showSearch = ref(true); +const visible = ref(false); +const total = ref(0); +const deptName = ref(''); +const deptOptions = ref<DeptVO[]>([]); +const chooseUserList = ref(ref<UserVO[]>()); +const userIds = ref<Array<number | string>>([]); +//鏌ヨ鍙傛暟 +const queryParams = ref<Record<string, any>>({ + pageNum: 1, + pageSize: 10, + userName: undefined, + nickName: undefined, + deptId: undefined +}); +/** 鏌ヨ鐢ㄦ埛鍒楄〃 */ +const getUserList = async (userIdList: Array<number | string>) => { + deptOptions.value = []; + getTreeSelect(); + userIds.value = userIdList; + visible.value = true; + loading.value = true; + const res = await getUserListByPage(queryParams.value); + loading.value = false; + userList.value = res.rows; + total.value = res.total; + if (userList.value && userIds.value.length > 0) { + const data = await getUserListByIds(userIds.value); + if (data.data && data.data.length > 0) { + chooseUserList.value = data.data; + data.data.forEach((user: UserVO) => { + multipleTableRef.value!.toggleRowSelection( + userList.value.find((item) => { + return item.userId == user.userId; + }), + true + ); + }); + } + } +}; + +const getList = async () => { + loading.value = true; + const res = await getUserListByPage(queryParams.value); + loading.value = false; + userList.value = res.rows; + total.value = res.total; + + if (userList.value && userIds.value.length > 0) { + const data = await getUserListByIds(userIds.value); + if (data.data && data.data.length > 0) { + chooseUserList.value = data.data; + data.data.forEach((user: UserVO) => { + multipleTableRef.value!.toggleRowSelection( + userList.value.find((item) => { + return item.userId == user.userId; + }), + true + ); + }); + } + } +}; + +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getUserList(userIds.value); +}; + +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryParams.value.pageNum = 1; + queryParams.value.deptId = undefined; + queryParams.value.userName = undefined; + queryParams.value.nickName = undefined; + deptTreeRef.value.setCurrentKey(null); + handleQuery(); +}; + +/** 閫夋嫨鏉℃暟 */ +const handleSelectionChange = (selection: UserVO[]) => { + console.log(selection); + if (props.multiple) { + chooseUserList.value = selection.filter((element, index, self) => { + return self.findIndex((x) => x.userId === element.userId) === index; + }); + selection.forEach((u) => { + if (chooseUserList.value && !chooseUserList.value.includes(u)) { + multipleTableRef.value!.toggleRowSelection(u, undefined); + } + }); + userIds.value = chooseUserList.value.map((item) => { + return item.userId; + }); + } else { + chooseUserList.value = selection; + if (selection.length > 1) { + let delRow = selection.shift(); + multipleTableRef.value!.toggleRowSelection(delRow, undefined); + } + if (selection.length === 0) { + chooseUserList.value = []; + } + } +}; +/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */ +const getTreeSelect = async () => { + const res = await deptTreeSelect(); + deptOptions.value = res.data; +}; + +/** 閫氳繃鏉′欢杩囨护鑺傜偣 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.label.indexOf(value) !== -1; +}; +/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */ +watchEffect( + () => { + if (visible.value && deptOptions.value && deptOptions.value.length > 0) { + deptTreeRef.value.filter(deptName.value); + } + }, + { + flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛� + } +); +/** 鑺傜偣鍗曞嚮浜嬩欢 */ +const handleNodeClick = (data: DeptVO) => { + queryParams.value.deptId = data.id; + getList(); +}; +//鍒犻櫎tag +const handleCloseTag = (user: UserVO, index: any) => { + if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) { + multipleTableRef.value.selection.forEach((u: UserVO, i: number) => { + if (user.userId === u.userId) { + multipleTableRef.value.selection.splice(i, 1); + } + }); + } + if (chooseUserList.value && chooseUserList.value.length > 0) { + chooseUserList.value.splice(index, 1); + } + multipleTableRef.value.toggleRowSelection(user, undefined); + + if (userIds.value && userIds.value.length > 0) { + userIds.value.forEach((userId, i) => { + if (userId === user.userId) { + userIds.value.splice(i, 1); + } + }); + } +}; +const submitFileForm = async () => { + loading.value = true; + emits('submitCallback', chooseUserList); +}; +const close = async () => { + visible.value = false; + loading.value = false; + emits('close'); +}; +//浜嬩欢 +const emits = defineEmits(['submitCallback', 'close']); + +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + getUserList, + close +}); +</script> diff --git a/src/components/RoleSelect/index.vue b/src/components/RoleSelect/index.vue index a9a07d3..ec5bcc7 100644 --- a/src/components/RoleSelect/index.vue +++ b/src/components/RoleSelect/index.vue @@ -196,4 +196,4 @@ onMounted(() => { getList(); }); -</script> +</script> \ No newline at end of file diff --git a/src/components/UserSelect/index.vue b/src/components/UserSelect/index.vue index 6a68f92..f2ef014 100644 --- a/src/components/UserSelect/index.vue +++ b/src/components/UserSelect/index.vue @@ -258,4 +258,4 @@ }); </script> -<style lang="scss" scoped></style> +<style lang="scss" scoped></style> \ No newline at end of file diff --git a/src/directive/common/copyText.ts b/src/directive/common/copyText.ts index b6805f8..0e605d3 100644 --- a/src/directive/common/copyText.ts +++ b/src/directive/common/copyText.ts @@ -2,9 +2,10 @@ * v-copyText 澶嶅埗鏂囨湰鍐呭 * Copyright (c) 2022 ruoyi */ +import { DirectiveBinding } from 'vue'; export default { - beforeMount(el: any, { value, arg }: any) { + beforeMount(el: any, { value, arg }: DirectiveBinding) { if (arg === 'callback') { el.$copyCallback = value; } else { diff --git a/src/enums/bpmn/IndexEnums.ts b/src/enums/bpmn/IndexEnums.ts new file mode 100644 index 0000000..8c39823 --- /dev/null +++ b/src/enums/bpmn/IndexEnums.ts @@ -0,0 +1,17 @@ +export enum AllocationTypeEnum { + USER = 'user', + CANDIDATE = 'candidate', + YOURSELF = 'yourself', + SPECIFY = 'specify' +} + +export enum SpecifyDescEnum { + SPECIFY_MULTIPLE = 'specifyMultiple', + SPECIFY_SINGLE = 'specifySingle' +} + +export enum MultiInstanceTypeEnum { + SERIAL = 'serial', + PARALLEL = 'parallel', + NONE = 'none' +} diff --git a/src/hooks/useDialog.ts b/src/hooks/useDialog.ts index 547f199..68440bf 100644 --- a/src/hooks/useDialog.ts +++ b/src/hooks/useDialog.ts @@ -28,4 +28,4 @@ openDialog, closeDialog }; -}; +}; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 29a5ee5..592de58 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,6 +15,12 @@ // 娉ㄥ唽鎻掍欢 import plugins from './plugins/index'; // plugins +// 楂樹寒缁勪欢 +// import 'highlight.js/styles/a11y-light.css'; +import 'highlight.js/styles/atom-one-dark.css'; +import 'highlight.js/lib/common'; +import HighLight from '@highlightjs/vue-plugin'; + // svg鍥炬爣 import 'virtual:svg-icons-register'; import ElementIcons from '@/plugins/svgicon'; @@ -38,6 +44,7 @@ const app = createApp(App); +app.use(HighLight); app.use(ElementIcons); app.use(router); app.use(store); diff --git a/src/store/modules/modeler.ts b/src/store/modules/modeler.ts new file mode 100644 index 0000000..7b19c1a --- /dev/null +++ b/src/store/modules/modeler.ts @@ -0,0 +1,76 @@ +import { Modeler, Modeling, Canvas, ElementRegistry, Moddle, BpmnFactory } from 'bpmn'; + +type ModelerStore = { + modeler: Modeler | undefined; + moddle: Moddle | undefined; + modeling: Modeling | undefined; + canvas: Canvas | undefined; + elementRegistry: ElementRegistry | undefined; + bpmnFactory: BpmnFactory | undefined; + // 娴佺▼瀹氫箟鏍硅妭鐐逛俊鎭� + procDefId: string | undefined; + procDefName: string | undefined; +}; + +const defaultState: ModelerStore = { + modeler: undefined, + moddle: undefined, + modeling: undefined, + canvas: undefined, + elementRegistry: undefined, + bpmnFactory: undefined, + procDefId: undefined, + procDefName: undefined +}; +export const useModelerStore = defineStore('modeler', () => { + let modeler = defaultState.modeler; + let moddle = defaultState.moddle; + let modeling = defaultState.modeling; + let canvas = defaultState.canvas; + let elementRegistry = defaultState.elementRegistry; + let bpmnFactory = defaultState.bpmnFactory; + const procDefId = ref(defaultState.procDefId); + const procDefName = ref(defaultState.procDefName); + + const getModeler = () => modeler; + const getModdle = () => moddle; + const getModeling = (): Modeling | undefined => modeling; + const getCanvas = (): Canvas | undefined => canvas; + const getElRegistry = (): ElementRegistry | undefined => elementRegistry; + const getBpmnFactory = (): BpmnFactory | undefined => bpmnFactory; + const getProcDefId = (): string | undefined => procDefId.value; + const getProcDefName = (): string | undefined => procDefName.value; + + // 璁剧疆鏍硅妭鐐� + const setModeler = (modelers: Modeler | undefined) => { + if (modelers) { + modeler = modelers; + modeling = modelers.get<Modeling>('modeling'); + moddle = modelers.get<Moddle>('moddle'); + canvas = modelers.get<Canvas>('canvas'); + bpmnFactory = modelers.get<BpmnFactory>('bpmnFactory'); + elementRegistry = modelers.get<ElementRegistry>('elementRegistry'); + } else { + modeling = moddle = canvas = elementRegistry = bpmnFactory = undefined; + } + }; + // 璁剧疆娴佺▼瀹氫箟鏍硅妭鐐逛俊鎭� + const setProcDef = (modeler: Modeler | undefined) => { + procDefId.value = modeler.get<Canvas>('canvas').getRootElement().businessObject.get('id'); + procDefName.value = modeler.get<Canvas>('canvas').getRootElement().businessObject.get('name'); + }; + + return { + getModeler, + getModdle, + getModeling, + getCanvas, + getElRegistry, + getBpmnFactory, + getProcDefId, + getProcDefName, + setModeler, + setProcDef + }; +}); +export default useModelerStore; diff --git a/src/types/bpmn/editor/global.d.ts b/src/types/bpmn/editor/global.d.ts new file mode 100644 index 0000000..1b9a634 --- /dev/null +++ b/src/types/bpmn/editor/global.d.ts @@ -0,0 +1,13 @@ +import { MessageApiInjection } from 'naive-ui/lib/message/src/MessageProvider'; + +declare global { + interface Window { + bpmnInstances: any; + __messageBox: MessageApiInjection; + URL: any; + } +} + +declare interface Window { + bpmnInstances: any; +} diff --git a/src/types/bpmn/index.d.ts b/src/types/bpmn/index.d.ts new file mode 100644 index 0000000..f8e8d15 --- /dev/null +++ b/src/types/bpmn/index.d.ts @@ -0,0 +1,15 @@ +declare module 'bpmn' { + import type modeler from 'bpmn-js/lib/Modeler'; + import type modeling from 'bpmn-js/lib/features/modeling/Modeling'; + import type canvas from 'diagram-js/lib/core/Canvas'; + import type elementRegistry from 'diagram-js/lib/core/ElementRegistry'; + import type bpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory'; + + export type Modeler = modeler; + export type Modeling = modeling; + export type Canvas = canvas; + export type ElementRegistry = elementRegistry; + export type Moddle = import('moddle').Moddle; + export type ModdleElement = import('moddle').ModdleElement; + export type BpmnFactory = bpmnFactory; +} diff --git a/src/types/bpmn/moddle.d.ts b/src/types/bpmn/moddle.d.ts new file mode 100644 index 0000000..1ed7933 --- /dev/null +++ b/src/types/bpmn/moddle.d.ts @@ -0,0 +1,37 @@ +declare module 'moddle' { + import type { Element as element } from 'bpmn-js/lib/model/Types'; + + export type Element = { + get<T>(name: string): T; + + set(name: string, value: any): void; + } & element; + + export interface ModdleElement extends Element { + $model: Moddle; + readonly $type: string; + $attrs: object | {}; + $parent: any; + businessObject: ModdleElement; + type: string; + + [field: string]: any; + + hasType(element: ModdleElement, type?: string): boolean; + } + + export interface Package { + name: string; + prefix: string; + } + + export interface Moddle { + typeCache: Record<string, ModdleElement>; + + getPackage: typeof Registry.prototype.getPackage; + + getPackages: typeof Registry.prototype.getPackages; + + create(type: string, attrs?: any): ModdleElement; + } +} diff --git a/src/types/bpmn/panel.d.ts b/src/types/bpmn/panel.d.ts new file mode 100644 index 0000000..0d0cac4 --- /dev/null +++ b/src/types/bpmn/panel.d.ts @@ -0,0 +1,75 @@ +declare module 'bpmnDesign' { + import { AllocationTypeEnum, SpecifyDescEnum, MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums'; + + export interface ParamVO { + type: string; + name: string; + value: string; + } + + export interface TaskListenerVO { + event: string; + type: string; + name: string; + className: string; + params: ParamVO[]; + } + + export interface ExecutionListenerVO { + event: string; + type: string; + className: string; + params: ParamVO[]; + } + + interface BasePanel { + id: string; + name: string; + } + export interface ProcessPanel extends BasePanel {} + + export interface TaskPanel extends BasePanel { + allocationType: AllocationTypeEnum; + specifyDesc: SpecifyDescEnum; + multiInstanceType: MultiInstanceTypeEnum; + async?: boolean; + priority?: number; + skipExpression?: string; + isForCompensation?: boolean; + triggerServiceTask?: boolean; + autoStoreVariables?: boolean; + ruleVariablesInput?: string; + excludeTaskListener?: boolean; + exclude?: boolean; + class?: string; + dueDate?: string; + fixedAssignee?: string; + + candidateUsers?: string; + assignee?: string; + candidateGroups?: string; + collection?: string; + elementVariable?: string; + completionCondition?: string; + isSequential?: boolean; + + loopCharacteristics?: { + collection: string; + elementVariable: string; + isSequential: boolean; + completionCondition: { + body: string; + }; + }; + } + + export interface StartEndPanel extends BasePanel {} + export interface GatewayPanel extends BasePanel {} + export interface SequenceFlowPanel extends BasePanel { + conditionExpression: { + body: string; + }; + conditionExpressionValue: string; + skipExpression: string; + } +} diff --git a/src/views/workflow/category/index.vue b/src/views/workflow/category/index.vue new file mode 100644 index 0000000..3fa4307 --- /dev/null +++ b/src/views/workflow/category/index.vue @@ -0,0 +1,263 @@ +<template> + <div class="p-2"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="search"> + <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="鍒嗙被鍚嶇О" prop="categoryName"> + <el-input v-model="queryParams.categoryName" placeholder="璇疯緭鍏ュ垎绫诲悕绉�" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="鍒嗙被缂栫爜" prop="categoryCode"> + <el-input v-model="queryParams.categoryCode" placeholder="璇疯緭鍏ュ垎绫荤紪鐮�" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </div> + </transition> + + <el-card shadow="never"> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button v-hasPermi="['workflow:category:add']" type="primary" plain icon="Plus" @click="handleAdd()">鏂板</el-button> + </el-col> + <el-col :span="1.5"> + <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">灞曞紑/鎶樺彔</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> + </el-row> + </template> + <el-table + ref="categoryTableRef" + v-loading="loading" + :data="categoryList" + row-key="id" + :default-expand-all="isExpandAll" + :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" + > + <el-table-column label="鍒嗙被鍚嶇О" prop="categoryName" /> + <el-table-column label="鍒嗙被缂栫爜" align="center" prop="categoryCode" /> + <el-table-column label="鎺掑簭" align="center" prop="sortNum" /> + <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-tooltip content="淇敼" placement="top"> + <el-button v-hasPermi="['workflow:category:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" /> + </el-tooltip> + <el-tooltip content="鏂板" placement="top"> + <el-button v-hasPermi="['workflow:category:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" /> + </el-tooltip> + <el-tooltip content="鍒犻櫎" placement="top"> + <el-button v-hasPermi="['workflow:category:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" /> + </el-tooltip> + </template> + </el-table-column> + </el-table> + </el-card> + <!-- 娣诲姞鎴栦慨鏀规祦绋嬪垎绫诲璇濇 --> + <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body> + <el-form ref="categoryFormRef" v-loading="loading" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="鐖剁骇鍒嗙被" prop="parentId"> + <el-tree-select + v-model="form.parentId" + :data="categoryOptions" + :props="{ value: 'id', label: 'categoryName', children: 'children' }" + value-key="id" + placeholder="璇烽�夋嫨鐖剁骇id" + check-strictly + /> + </el-form-item> + <el-form-item label="鍒嗙被鍚嶇О" prop="categoryName"> + <el-input v-model="form.categoryName" placeholder="璇疯緭鍏ュ垎绫诲悕绉�" /> + </el-form-item> + <el-form-item label="鍒嗙被缂栫爜" prop="categoryCode"> + <el-input v-model="form.categoryCode" placeholder="璇疯緭鍏ュ垎绫荤紪鐮�" /> + </el-form-item> + <el-form-item label="鎺掑簭" prop="sortNum"> + <el-input-number v-model="form.sortNum" placeholder="璇疯緭鍏ユ帓搴�" controls-position="right" :min="0" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button :loading="buttonLoading" type="primary" @click="submitForm">纭� 瀹�</el-button> + <el-button @click="cancel">鍙� 娑�</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup name="Category" lang="ts"> +import { listCategory, getCategory, delCategory, addCategory, updateCategory } from '@/api/workflow/category'; +import { CategoryVO, CategoryQuery, CategoryForm } from '@/api/workflow/category/types'; + +type CategoryOption = { + id: number; + categoryName: string; + children?: CategoryOption[]; +}; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const categoryList = ref<CategoryVO[]>([]); +const categoryOptions = ref<CategoryOption[]>([]); +const buttonLoading = ref(false); +const showSearch = ref(true); +const isExpandAll = ref(true); +const loading = ref(false); + +const queryFormRef = ref<ElFormInstance>(); +const categoryFormRef = ref<ElFormInstance>(); +const categoryTableRef = ref<ElTableInstance>(); + +const dialog = reactive<DialogOption>({ + visible: false, + title: '' +}); + +const initFormData: CategoryForm = { + id: undefined, + categoryName: undefined, + categoryCode: undefined, + parentId: undefined, + sortNum: 0 +}; + +const data = reactive<PageData<CategoryForm, CategoryQuery>>({ + form: { ...initFormData }, + queryParams: { + pageNum: 1, + pageSize: 10, + categoryName: undefined, + categoryCode: undefined + }, + rules: { + id: [{ required: true, message: '涓婚敭涓嶈兘涓虹┖', trigger: 'blur' }], + categoryName: [{ required: true, message: '鍒嗙被鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' }], + categoryCode: [{ required: true, message: '鍒嗙被缂栫爜涓嶈兘涓虹┖', trigger: 'blur' }], + parentId: [{ required: true, message: '鐖剁骇id涓嶈兘涓虹┖', trigger: 'blur' }] + } +}); + +const { queryParams, form, rules } = toRefs(data); + +/** 鏌ヨ娴佺▼鍒嗙被鍒楄〃 */ +const getList = async () => { + loading.value = true; + const res = await listCategory(queryParams.value); + const data = proxy?.handleTree<CategoryVO>(res.data, 'id', 'parentId'); + if (data) { + categoryList.value = data; + loading.value = false; + } +}; + +/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */ +const getTreeselect = async () => { + const res = await listCategory(); + categoryOptions.value = []; + const data: CategoryOption = { id: 0, categoryName: '椤剁骇鑺傜偣', children: [] }; + data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId'); + categoryOptions.value.push(data); +}; + +// 鍙栨秷鎸夐挳 +const cancel = () => { + reset(); + dialog.visible = false; +}; + +// 琛ㄥ崟閲嶇疆 +const reset = () => { + form.value = { ...initFormData }; + categoryFormRef.value?.resetFields(); +}; + +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + getList(); +}; + +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + handleQuery(); +}; + +/** 鏂板鎸夐挳鎿嶄綔 */ +const handleAdd = (row?: CategoryVO) => { + dialog.visible = true; + dialog.title = '娣诲姞娴佺▼鍒嗙被'; + nextTick(() => { + reset(); + getTreeselect(); + if (row != null && row.id) { + form.value.parentId = row.id; + } else { + form.value.parentId = 0; + } + }); +}; + +/** 灞曞紑/鎶樺彔鎿嶄綔 */ +const handleToggleExpandAll = () => { + isExpandAll.value = !isExpandAll.value; + toggleExpandAll(categoryList.value, isExpandAll.value); +}; + +/** 灞曞紑/鎶樺彔鎿嶄綔 */ +const toggleExpandAll = (data: CategoryVO[], status: boolean) => { + data.forEach((item) => { + categoryTableRef.value?.toggleRowExpansion(item, status); + if (item.children && item.children.length > 0) toggleExpandAll(item.children, status); + }); +}; + +/** 淇敼鎸夐挳鎿嶄綔 */ +const handleUpdate = (row: CategoryVO) => { + loading.value = true; + dialog.visible = true; + dialog.title = '淇敼娴佺▼鍒嗙被'; + nextTick(async () => { + reset(); + await getTreeselect(); + if (row != null) { + form.value.parentId = row.id; + } + const res = await getCategory(row.id); + loading.value = false; + Object.assign(form.value, res.data); + }); +}; + +/** 鎻愪氦鎸夐挳 */ +const submitForm = () => { + categoryFormRef.value.validate(async (valid: boolean) => { + if (valid) { + buttonLoading.value = true; + if (form.value.id) { + await updateCategory(form.value).finally(() => (buttonLoading.value = false)); + } else { + await addCategory(form.value).finally(() => (buttonLoading.value = false)); + } + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); + dialog.visible = false; + await getList(); + } + }); +}; + +/** 鍒犻櫎鎸夐挳鎿嶄綔 */ +const handleDelete = async (row: CategoryVO) => { + await proxy?.$modal.confirm('鏄惁纭鍒犻櫎娴佺▼鍒嗙被缂栧彿涓�"' + row.id + '"鐨勬暟鎹」锛�'); + loading.value = true; + await delCategory(row.id).finally(() => (loading.value = false)); + await getList(); + proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛'); +}; + +onMounted(() => { + getList(); +}); +</script> diff --git a/src/views/workflow/leave/index.vue b/src/views/workflow/leave/index.vue new file mode 100644 index 0000000..c7550b2 --- /dev/null +++ b/src/views/workflow/leave/index.vue @@ -0,0 +1,381 @@ +<template> + <div class="p-2"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="search"> + <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="璇峰亣澶╂暟" prop="startLeaveDays"> + <el-input v-model="queryParams.startLeaveDays" placeholder="璇疯緭鍏ヨ鍋囧ぉ鏁�" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item prop="endLeaveDays"> 鑷� </el-form-item> + <el-form-item prop="endLeaveDays"> + <el-input v-model="queryParams.endLeaveDays" placeholder="璇疯緭鍏ヨ鍋囧ぉ鏁�" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </div> + </transition> + + <el-card shadow="never"> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button v-hasPermi="['demo:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">鏂板</el-button> + </el-col> + <el-col :span="1.5"> + <el-button v-hasPermi="['demo:leave:export']" type="warning" plain icon="Download" @click="handleExport">瀵煎嚭</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="leaveList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column v-if="false" label="涓婚敭" align="center" prop="id" /> + <el-table-column label="璇峰亣绫诲瀷" align="center"> + <template #default="scope"> + <el-tag>{{ options.find((e) => e.value === scope.row.leaveType)?.label }}</el-tag> + </template> + </el-table-column> + <el-table-column label="寮�濮嬫椂闂�" align="center" prop="startDate"> + <template #default="scope"> + <span>{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> + <el-table-column label="缁撴潫鏃堕棿" align="center" prop="endDate"> + <template #default="scope"> + <span>{{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> + <el-table-column label="璇峰亣澶╂暟" align="center" prop="leaveDays" /> + <el-table-column label="璇峰亣鍘熷洜" align="center" prop="remark" /> + <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag type="success">{{ scope.row.processInstanceVo.businessStatusName }}</el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-tooltip + v-if=" + scope.row.processInstanceVo.businessStatus === 'draft' || + scope.row.processInstanceVo.businessStatus === 'cancel' || + scope.row.processInstanceVo.businessStatus === 'back' + " + content="淇敼" + placement="top" + > + <el-button v-hasPermi="['demo:leave:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> + </el-tooltip> + <el-tooltip + v-if=" + scope.row.processInstanceVo.businessStatus === 'draft' || + scope.row.processInstanceVo.businessStatus === 'cancel' || + scope.row.processInstanceVo.businessStatus === 'back' + " + content="鍒犻櫎" + placement="top" + > + <el-button v-hasPermi="['demo:leave:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.processInstanceVo.businessStatus === 'waiting'" content="鎾ら攢" placement="top"> + <el-button link type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.processInstanceVo.id)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.processInstanceVo.businessStatus === 'waiting'" content="瀹℃壒璁板綍" placement="top"> + <el-button link type="primary" icon="Document" @click="handleApprovalRecord(scope.row.processInstanceVo.id)"></el-button> + </el-tooltip> + </template> + </el-table-column> + </el-table> + + <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> + </el-card> + <!-- 娣诲姞鎴栦慨鏀硅鍋囧璇濇 --> + <el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body> + <el-form ref="leaveFormRef" v-loading="loading" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="璇峰亣绫诲瀷" prop="leaveType"> + <el-select v-model="form.leaveType" placeholder="璇烽�夋嫨璇峰亣绫诲瀷" style="width: 100%"> + <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /> + </el-select> + </el-form-item> + <el-form-item label="璇峰亣鏃堕棿"> + <el-date-picker + v-model="leaveTime" + type="daterange" + range-separator="To" + start-placeholder="寮�濮嬫椂闂�" + end-placeholder="缁撴潫鏃堕棿" + @change="changeLeaveTime()" + /> + </el-form-item> + <el-form-item label="璇峰亣澶╂暟" prop="leaveDays"> + <el-input v-model="form.leaveDays" disabled type="number" placeholder="璇疯緭鍏ヨ鍋囧ぉ鏁�" /> + </el-form-item> + <el-form-item label="璇峰亣鍘熷洜" prop="remark"> + <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ鍋囧師鍥�" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button :loading="buttonLoading" type="info" @click="submitForm('draft')">鏆� 瀛�</el-button> + <el-button :loading="buttonLoading" type="primary" @click="submitForm('submit')">鎻� 浜�</el-button> + <el-button @click="cancel">鍙� 娑�</el-button> + </div> + </template> + </el-dialog> + <!-- 鎻愪氦缁勪欢 --> + <submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" /> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + </div> +</template> + +<script setup name="Leave" lang="ts"> +import { addLeave, delLeave, getLeave, listLeave, updateLeave } from '@/api/workflow/leave'; +import { cancelProcessApply } from '@/api/workflow/processInstance'; +import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types'; +import { startWorkFlow } from '@/api/workflow/task'; +import SubmitVerify from '@/components/Process/submitVerify.vue'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import { AxiosResponse } from 'axios'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const leaveList = ref<LeaveVO[]>([]); +const buttonLoading = ref(false); +const loading = ref(true); +const showSearch = ref(true); +const ids = ref<Array<string | number>>([]); +const single = ref(true); +const multiple = ref(true); +const total = ref(0); +const leaveTime = ref<Array<string>>([]); +const options = [ + { + value: '1', + label: '浜嬪亣' + }, + { + value: '2', + label: '璋冧紤' + }, + { + value: '3', + label: '鐥呭亣' + }, + { + value: '4', + label: '濠氬亣' + } +]; +//鎻愪氦缁勪欢 +const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>(); +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); + +const queryFormRef = ref<ElFormInstance>(); +const leaveFormRef = ref<ElFormInstance>(); + +const submitFormData = ref<Record<string, any>>({ + businessKey: '', + processKey: '', + variables: {} +}); +const taskVariables = ref<Record<string, any>>({}); + +const dialog = reactive<DialogOption>({ + visible: false, + title: '' +}); + +const initFormData: LeaveForm = { + id: undefined, + leaveType: undefined, + startDate: undefined, + endDate: undefined, + leaveDays: undefined, + remark: undefined +}; +const data = reactive<PageData<LeaveForm, LeaveQuery>>({ + form: { ...initFormData }, + queryParams: { + pageNum: 1, + pageSize: 10, + startLeaveDays: undefined, + endLeaveDays: undefined + }, + rules: { + id: [{ required: true, message: '涓婚敭涓嶈兘涓虹┖', trigger: 'blur' }], + leaveType: [{ required: true, message: '璇峰亣绫诲瀷涓嶈兘涓虹┖', trigger: 'blur' }], + leaveTime: [{ required: true, message: '璇峰亣鏃堕棿涓嶈兘涓虹┖', trigger: 'blur' }], + leaveDays: [{ required: true, message: '璇峰亣澶╂暟涓嶈兘涓虹┖', trigger: 'blur' }] + } +}); + +const { queryParams, form, rules } = toRefs(data); + +/** 鏌ヨ璇峰亣鍒楄〃 */ +const getList = async () => { + loading.value = true; + const res = await listLeave(queryParams.value); + leaveList.value = res.rows; + total.value = res.total; + loading.value = false; +}; + +/** 鍙栨秷鎸夐挳 */ +const cancel = () => { + reset(); + dialog.visible = false; +}; + +/** 琛ㄥ崟閲嶇疆 */ +const reset = () => { + form.value = { ...initFormData }; + leaveTime.value = []; + leaveFormRef.value?.resetFields(); +}; + +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getList(); +}; + +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + handleQuery(); +}; + +/** 澶氶�夋閫変腑鏁版嵁 */ +const handleSelectionChange = (selection: LeaveVO[]) => { + ids.value = selection.map((item) => item.id); + single.value = selection.length != 1; + multiple.value = !selection.length; +}; + +/** 鏂板鎸夐挳鎿嶄綔 */ +const handleAdd = () => { + dialog.visible = true; + dialog.title = '娣诲姞璇峰亣鐢宠'; + nextTick(() => { + reset(); + }); +}; + +const changeLeaveTime = () => { + const startDate = new Date(leaveTime.value[0]).getTime(); + const endDate = new Date(leaveTime.value[1]).getTime(); + const diffInMilliseconds = endDate - startDate; + form.value.leaveDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24)); +}; +/** 淇敼鎸夐挳鎿嶄綔 */ +const handleUpdate = (row?: LeaveVO) => { + buttonLoading.value = false; + dialog.visible = true; + dialog.title = '淇敼璇峰亣鐢宠'; + nextTick(async () => { + reset(); + const _id = row?.id || ids.value[0]; + const res = await getLeave(_id); + Object.assign(form.value, res.data); + leaveTime.value = []; + leaveTime.value.push(form.value.startDate); + leaveTime.value.push(form.value.endDate); + }); +}; + +/** 鎻愪氦鎸夐挳 */ +const submitForm = (status: string) => { + if (leaveTime.value.length === 0) { + proxy?.$modal.msgError('璇峰亣鏃堕棿涓嶈兘涓虹┖'); + return; + } + leaveFormRef.value?.validate(async (valid: boolean) => { + form.value.startDate = leaveTime.value[0]; + form.value.endDate = leaveTime.value[1]; + if (valid) { + buttonLoading.value = true; + let res: AxiosResponse<LeaveVO>; + if (form.value.id) { + res = await updateLeave(form.value); + } else { + res = await addLeave(form.value); + } + form.value = res.data; + if (status === 'draft') { + buttonLoading.value = false; + proxy?.$modal.msgSuccess('鏆傚瓨鎴愬姛'); + dialog.visible = false; + await getList(); + } else { + await handleStartWorkFlow(res.data); + } + } + }); +}; + +/** 鍒犻櫎鎸夐挳鎿嶄綔 */ +const handleDelete = async (row?: LeaveVO) => { + const _ids = row?.id || ids.value; + await proxy?.$modal.confirm('鏄惁纭鍒犻櫎璇峰亣缂栧彿涓�"' + _ids + '"鐨勬暟鎹」锛�').finally(() => (loading.value = false)); + await delLeave(_ids); + proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛'); + await getList(); +}; + +/** 瀵煎嚭鎸夐挳鎿嶄綔 */ +const handleExport = () => { + proxy?.download( + 'demo/leave/export', + { + ...queryParams.value + }, + `leave_${new Date().getTime()}.xlsx` + ); +}; + +//鎻愪氦鐢宠 +const handleStartWorkFlow = async (data: LeaveVO) => { + submitFormData.value.processKey = 'leave7'; + submitFormData.value.businessKey = data.id; + //娴佺▼鍙橀噺 + taskVariables.value = { + entity: data, + leaveDays: data.leaveDays, + userList: [1, 2], + userList2: [1, 2] + }; + submitFormData.value.variables = taskVariables.value; + const resp = await startWorkFlow(submitFormData.value); + if (submitVerifyRef.value) { + buttonLoading.value = false; + submitVerifyRef.value.openDialog(resp.data.taskId); + } +}; +//瀹℃壒璁板綍 +const handleApprovalRecord = (id: string) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(id); + } +}; +//鎻愪氦鍥炶皟 +const submitCallback = async () => { + dialog.visible = false; + handleQuery(); +}; +/** 鎾ら攢鎸夐挳鎿嶄綔 */ +const handleCancelProcessApply = async (id: string) => { + await proxy?.$modal.confirm('鏄惁纭鎾ら攢褰撳墠鍗曟嵁锛�'); + loading.value = true; + await cancelProcessApply(id).finally(() => (loading.value = false)); + await getList(); + proxy?.$modal.msgSuccess('鎾ら攢鎴愬姛'); +}; +onMounted(() => { + getList(); +}); +</script> diff --git a/src/views/workflow/model/design.vue b/src/views/workflow/model/design.vue new file mode 100644 index 0000000..0f6b118 --- /dev/null +++ b/src/views/workflow/model/design.vue @@ -0,0 +1,65 @@ +<template> + <div class="design"> + <el-dialog v-model="visible" width="100%" fullscreen :title="title"> + <div class="modeler"> + <bpmn-design ref="bpmnDesignRef" @save-call-back="saveCallBack"></bpmn-design> + </div> + </el-dialog> + </div> +</template> + +<script lang="ts" setup name="Design"> +import { getInfo, editModelXml } from '@/api/workflow/model'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +import { ModelForm } from '@/api/workflow/model/types'; +import BpmnDesign from '@/components/BpmnDesign'; +import useDialog from '@/hooks/useDialog'; +const bpmnDesignRef = ref<InstanceType<typeof BpmnDesign>>(); +const modelForm = ref<ModelForm>(); +const emit = defineEmits(['closeCallBack']); +const { visible, title } = useDialog({ + title: '缂栬緫娴佺▼' +}); +const modelId = ref(''); +const open = async (id) => { + visible.value = true; + modelId.value = id; + const { data } = await getInfo(id); + modelForm.value = data; + bpmnDesignRef.value.initDiagram(modelForm.value.xml); +}; +//淇濆瓨妯″瀷 +const saveCallBack = async (data) => { + await proxy?.$modal.confirm('鏄惁纭淇濆瓨锛�'); + modelForm.value.id = modelId.value; + modelForm.value.xml = data.xml; + modelForm.value.svg = data.svg; + modelForm.value.key = data.key; + modelForm.value.name = data.name; + editModelXml(modelForm.value).then((res) => { + if (res.code === 200) { + visible.value = false; + proxy?.$modal.msgSuccess('淇濆瓨鎴愬姛'); + emit('closeCallBack', data); + } + }); +}; + +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + open +}); +</script> + +<style lang="scss" scoped> +.design { + :deep(.el-dialog .el-dialog__body) { + max-height: 100% !important; + min-height: calc(100vh - 50px); + } +} +</style> diff --git a/src/views/workflow/model/index.vue b/src/views/workflow/model/index.vue new file mode 100644 index 0000000..93cd675 --- /dev/null +++ b/src/views/workflow/model/index.vue @@ -0,0 +1,360 @@ +<template> + <div class="p-2"> + <el-row :gutter="20"> + <!-- 娴佺▼鍒嗙被鏍� --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable /> + <el-tree + ref="categoryTreeRef" + class="mt-2" + node-key="id" + :data="categoryOptions" + :props="{ label: 'categoryName', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + ></el-tree> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="80px"> + <el-form-item label="妯″瀷鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユā鍨嬪悕绉�" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="妯″瀷KEY" prop="key"> + <el-input v-model="queryParams.key" placeholder="璇疯緭鍏ユā鍨婯EY" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="primary" plain icon="Plus" @click="handleAdd">鏂板</el-button> + </el-col> + <el-col :span="1.5"> + <el-button type="success" plain icon="Edit" :disabled="multiple" @click="handleUpdate()">淇敼</el-button> + </el-col> + <el-col :span="1.5"> + <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">鍒犻櫎</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="modelList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="name" label="妯″瀷鍚嶇О"></el-table-column> + <el-table-column align="center" prop="key" label="妯″瀷KEY"></el-table-column> + <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90"> + <template #default="scope"> v{{ scope.row.version }}.0</template> + </el-table-column> + <el-table-column align="center" prop="metaInfo" label="澶囨敞璇存槑" min-width="130"></el-table-column> + <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column> + <el-table-column align="center" prop="lastUpdateTime" label="鏇存柊鏃堕棿" width="160"></el-table-column> + <el-table-column fixed="right" label="鎿嶄綔" align="center" width="180" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Pointer" @click="clickDesign(scope.row.id)">璁捐娴佺▼</el-button> + </el-col> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Download" @click="clickExportZip(scope.row)">瀵煎嚭</el-button> + </el-col> + </el-row> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="ScaleToOriginal" @click="clickDeploy(scope.row.id, scope.row.key)"> + 娴佺▼閮ㄧ讲 + </el-button> + </el-col> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="getList" + /> + </el-card> + </el-col> + </el-row> + <!-- 璁捐娴佺▼寮�濮� --> + <Design ref="designRef" @close-call-back="handleQuery"></Design> + <!-- 璁捐娴佺▼缁撴潫 --> + <!-- 娣诲姞妯″瀷瀵硅瘽妗� --> + <el-dialog v-model="dialog.visible" :title="dialog.title" width="650px" append-to-body :close-on-click-modal="false"> + <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> + <el-form-item label="妯″瀷鍚嶇О锛�" prop="name"> + <el-input v-model="form.name" :disabled="ids && ids.length > 0" maxlength="20" show-word-limit /> + </el-form-item> + <el-form-item label="妯″瀷KEY锛�" prop="key"> + <el-input v-model="form.key" :disabled="ids && ids.length > 0" maxlength="20" show-word-limit /> + </el-form-item> + <el-form-item label="娴佺▼鍒嗙被" prop="categoryCode"> + <el-tree-select + v-model="form.categoryCode" + :data="categoryOptions" + :props="{ value: 'categoryCode', label: 'categoryName', children: 'children' }" + value-key="categoryCode" + placeholder="璇烽�夋嫨娴佺▼鍒嗙被" + check-strictly + /> + </el-form-item> + <el-form-item label="澶囨敞锛�" prop="description"> + <el-input v-model="form.description" type="textarea" maxlength="200" show-word-limit></el-input> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button type="primary" @click="submitForm">纭� 瀹�</el-button> + <el-button @click="cancel">鍙� 娑�</el-button> + </span> + </template> + </el-dialog> + </div> +</template> + +<script lang="ts" setup name="Model"> +import Design from './design.vue'; +import { listModel, addModel, delModel, modelDeploy, getInfo, update } from '@/api/workflow/model'; +import { ModelQuery, ModelForm, ModelVO } from '@/api/workflow/model/types'; +import { listCategory } from '@/api/workflow/category'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const formRef = ref<ElFormInstance>(); +const queryFormRef = ref<ElFormInstance>(); +const categoryTreeRef = ref<ElTreeInstance>(); +const designRef = ref<InstanceType<typeof Design>>(); + +type CategoryOption = { + categoryCode: string; + categoryName: string; + children?: CategoryOption[]; +}; + +const buttonLoading = ref(false); +const loading = ref(true); +const ids = ref<string[]>([]); +const single = ref(true); +const multiple = ref(true); +const showSearch = ref(true); +const total = ref(0); +const modelList = ref<ModelVO[]>([]); +const categoryOptions = ref<CategoryOption[]>([]); +const categoryName = ref(''); +const modelId = ref<string>(''); + +const dialog = reactive<DialogOption>({ + visible: false, + title: '' +}); + +const initFormData: ModelForm = { + id: '', + name: '', + key: '', + categoryCode: '', + xml: '', + svg: '', + description: '' +}; +const data = reactive<PageData<ModelForm, ModelQuery>>({ + form: { ...initFormData }, + queryParams: { + pageNum: 1, + pageSize: 10, + name: '', + key: '', + categoryCode: '' + }, + rules: { + name: [{ required: true, message: '妯″瀷涓嶈兘涓虹┖', trigger: 'blur' }], + key: [{ required: true, message: '妯″瀷KEY涓嶈兘涓虹┖', trigger: 'blur' }], + categoryCode: [{ required: true, message: '娴佺▼鍒嗙被涓嶈兘涓虹┖', trigger: 'blur' }] + } +}); +const { queryParams, form, rules } = toRefs(data); + +onMounted(() => { + getList(); + getTreeselect(); +}); + +/** 鑺傜偣鍗曞嚮浜嬩欢 */ +const handleNodeClick = (data: ModelForm) => { + queryParams.value.categoryCode = data.categoryCode; + if (data.categoryCode === 'ALL') { + queryParams.value.categoryCode = ''; + } + handleQuery(); +}; +/** 閫氳繃鏉′欢杩囨护鑺傜偣 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.categoryName.indexOf(value) !== -1; +}; +/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */ +watchEffect( + () => { + categoryTreeRef.value?.filter(categoryName.value); + }, + { + flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛� + } +); + +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getList(); +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.categoryCode = ''; + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: ModelVO[]) => { + ids.value = selection.map((item: ModelVO) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +//鍒嗛〉 +const getList = async () => { + loading.value = true; + const resp = await listModel(queryParams.value); + modelList.value = resp.rows; + total.value = resp.total; + loading.value = false; +}; +/** 鍒犻櫎鎸夐挳鎿嶄綔 */ +const handleDelete = async (row?: ModelVO) => { + const id = row?.id || ids.value; + await proxy?.$modal.confirm('鏄惁纭鍒犻櫎妯″瀷id涓恒��' + id + '銆戠殑鏁版嵁椤癸紵'); + loading.value = true; + await delModel(id).finally(() => (loading.value = false)); + await getList(); + proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛'); +}; +// 娴佺▼閮ㄧ讲 +const clickDeploy = async (id: string, key: string) => { + await proxy?.$modal.confirm('鏄惁閮ㄧ讲妯″瀷key涓恒��' + key + '銆戞祦绋嬶紵'); + loading.value = true; + await modelDeploy(id).finally(() => (loading.value = false)); + await getList(); + proxy?.$modal.msgSuccess('閮ㄧ讲鎴愬姛'); +}; +const handleAdd = () => { + ids.value = []; + getTreeselect(); + form.value = { ...initFormData }; + dialog.visible = true; + dialog.title = '鏂板妯″瀷'; +}; +const handleUpdate = () => { + dialog.title = '淇敼妯″瀷'; + nextTick(async () => { + await getTreeselect(); + const _id = ids.value[0]; + const res = await getInfo(_id); + Object.assign(form.value, res.data); + dialog.visible = true; + }); +}; + +/** 鎻愪氦鎸夐挳 */ +const submitForm = () => { + formRef.value.validate(async (valid: boolean) => { + if (valid) { + buttonLoading.value = true; + if (ids.value && ids.value.length > 0) { + form.value.id = ids.value[0]; + await update(form.value); + proxy?.$modal.msgSuccess('鏂板鎴愬姛'); + } else { + initXml(form.value.key, form.value.name); + form.value.xml = xml.value; + await addModel(form.value); + proxy?.$modal.msgSuccess('鏂板鎴愬姛'); + } + dialog.visible = false; + await getList(); + } + }); +}; + +/** 鍙栨秷鎸夐挳 */ +const cancel = () => { + reset(); + dialog.visible = false; +}; + +/** 琛ㄥ崟閲嶇疆 */ +const reset = () => { + form.value = { ...initFormData }; + formRef.value.resetFields(); +}; + +// 鎵撳紑璁捐娴佺▼ +const clickDesign = async (id: string) => { + await designRef.value.open(id); +}; +// 瀵煎嚭娴佺▼妯″瀷 +const clickExportZip = (data: any) => { + proxy?.$download.zip('/workflow/model/export/zip/' + data.id, data.name + '-' + data.key); +}; +/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */ +const getTreeselect = async () => { + const res = await listCategory(); + categoryOptions.value = []; + const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] }; + data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId'); + categoryOptions.value.push(data); +}; + +const xml = ref<string>(''); + +const initXml = async (key: string, name: string) => { + xml.value = `<?xml version="1.0" encoding="UTF-8"?> +<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef"> + <process id="${key}" name="${name}"> + <startEvent id="startNode1" name="寮�濮�" /> + </process> + <bpmndi:BPMNDiagram id="BPMNDiagram_flow"> + <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c"> + <bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke=""> + <omgdc:Bounds x="240" y="200" width="30" height="30" /> + <bpmndi:BPMNLabel> + <omgdc:Bounds x="242" y="237" width="23" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNShape> + </bpmndi:BPMNPlane> + </bpmndi:BPMNDiagram> +</definitions>`; + return xml; +}; +</script> diff --git a/src/views/workflow/processDefinition/components/processPreview.vue b/src/views/workflow/processDefinition/components/processPreview.vue new file mode 100644 index 0000000..817552a --- /dev/null +++ b/src/views/workflow/processDefinition/components/processPreview.vue @@ -0,0 +1,57 @@ +<template> + <el-dialog v-model="data.visible" title="棰勮" width="70%" append-to-body> + <div v-if="data.type === 'png'" style="align: center"> + <el-image v-if="data.type === 'png'" :src="data.url[0]"> + <div>娴佺▼鍥惧姞杞戒腑 <i class="el-icon-loading"></i></div> + </el-image> + </div> + <div v-if="data.type === 'xml'" class="xml-data"> + <div v-for="(xml, index) in data.url" :key="index"> + <pre class="font">{{ xml }}</pre> + </div> + </div> + <template #footer> + <span v-if="data.type === 'xml'" class="dialog-footer"> </span> + </template> + </el-dialog> +</template> + +<script setup lang="ts"> +const data = reactive({ + visible: false, + url: new Array<string>(), + type: '' +}); +//鎵撳紑 +const openDialog = (url: string[], type: string) => { + data.visible = true; + data.url = url; + data.type = type; +}; +/** + * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉� + */ +defineExpose({ + openDialog +}); +</script> +<style> +.xml-data { + background-color: #2b2b2b; + border-radius: 5px; + color: #c6c6c6; + word-break: break-all; + overflow-y: scroll; + overflow-x: hidden; + box-sizing: border-box; + padding: 8px 0px; + height: 500px; + width: inherit; + line-height: 1px; + overflow: auto; +} +.font { + font-family: '骞煎渾'; + font-weight: 500; +} +</style> diff --git a/src/views/workflow/processDefinition/index.vue b/src/views/workflow/processDefinition/index.vue new file mode 100644 index 0000000..3843573 --- /dev/null +++ b/src/views/workflow/processDefinition/index.vue @@ -0,0 +1,403 @@ +<template> + <div class="p-2"> + <el-row :gutter="20"> + <!-- 娴佺▼鍒嗙被鏍� --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable /> + <el-tree + ref="categoryTreeRef" + class="mt-2" + node-key="id" + :data="categoryOptions" + :props="{ label: 'categoryName', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + ></el-tree> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px"> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟KEY" prop="key"> + <el-input v-model="queryParams.key" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">閮ㄧ讲娴佺▼鏂囦欢</el-button> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> </el-col> + <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="processDefinitionList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="name" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column align="center" prop="key" label="鏍囪瘑Key"></el-table-column> + <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90"> + <template #default="scope"> v{{ scope.row.version }}.0</template> + </el-table-column> + <el-table-column align="center" prop="resourceName" label="娴佺▼XML" min-width="80" :show-overflow-tooltip="true"> + <template #default="scope"> + <el-link type="primary" @click="clickPreviewXML(scope.row.id)">{{ scope.row.resourceName }}</el-link> + </template> + </el-table-column> + <el-table-column align="center" prop="diagramResourceName" label="娴佺▼鍥剧墖" min-width="80" :show-overflow-tooltip="true"> + <template #default="scope"> + <el-link type="primary" @click="clickPreviewImg(scope.row.id)">{{ scope.row.diagramResourceName }}</el-link> + </template> + </el-table-column> + <el-table-column align="center" prop="suspensionState" label="鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag v-if="scope.row.suspensionState == 1" type="success">婵�娲�</el-tag> + <el-tag v-else type="danger">鎸傝捣</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="deploymentTime" label="閮ㄧ讲鏃堕棿" :show-overflow-tooltip="true"></el-table-column> + <el-table-column fixed="right" label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + link + type="primary" + size="small" + :icon="scope.row.suspensionState === 1 ? 'Lock' : 'Unlock'" + @click="handleProcessDefState(scope.row)" + > + {{ scope.row.suspensionState === 1 ? '鎸傝捣娴佺▼' : '婵�娲绘祦绋�' }} + </el-button> + </el-col> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button> + </el-col> + </el-row> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Sort" @click="handleConvertToModel(scope.row)"> 杞崲妯″瀷 </el-button> + </el-col> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="getProcessDefinitionHitoryList(scope.row.id, scope.row.key)"> + 鍘嗗彶鐗堟湰 + </el-button> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="getList" + /> + </el-card> + </el-col> + </el-row> + <!-- 棰勮鍥剧墖鎴杧ml --> + <process-preview ref="previewRef" /> + + <!-- 閮ㄧ讲鏂囦欢 --> + <el-dialog v-if="uploadDialog.visible" v-model="uploadDialog.visible" :title="uploadDialog.title" width="30%"> + <div v-loading="uploadDialogLoading"> + <el-upload class="upload-demo" drag accept="application/zip,application/xml,.bpmn" :http-request="handerDeployProcessFile"> + <el-icon class="UploadFilled"><upload-filled /></el-icon> + <div class="el-upload__text"><em>鐐瑰嚮涓婁紶锛岄�夋嫨BPMN娴佺▼鏂囦欢</em></div> + <div class="el-upload__text">浠呮敮鎸� .zip銆�.bpmn20.xml銆乥pmn 鏍煎紡鏂囦欢</div> + <div class="el-upload__text">PS:濡傝嫢閮ㄧ讲璇烽儴缃蹭粠鏈」鐩ā鍨嬬鐞嗗鍑虹殑鏁版嵁</div> + </el-upload> + </div> + </el-dialog> + + <!-- 鍘嗗彶鐗堟湰 --> + <el-dialog v-if="processDefinitionDialog.visible" v-model="processDefinitionDialog.visible" :title="processDefinitionDialog.title" width="70%"> + <el-table v-loading="loading" :data="processDefinitionHistoryList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="name" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column align="center" prop="key" label="鏍囪瘑Key"></el-table-column> + <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90"> + <template #default="scope"> v{{ scope.row.version }}.0</template> + </el-table-column> + <el-table-column align="center" prop="resourceName" label="娴佺▼XML" min-width="80" :show-overflow-tooltip="true"> + <template #default="scope"> + <el-link type="primary" @click="clickPreviewXML(scope.row.id)">{{ scope.row.resourceName }}</el-link> + </template> + </el-table-column> + <el-table-column align="center" prop="diagramResourceName" label="娴佺▼鍥剧墖" min-width="80" :show-overflow-tooltip="true"> + <template #default="scope"> + <el-link type="primary" @click="clickPreviewImg(scope.row.id)">{{ scope.row.diagramResourceName }}</el-link> + </template> + </el-table-column> + <el-table-column align="center" prop="suspensionState" label="鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag v-if="scope.row.suspensionState == 1" type="success">婵�娲�</el-tag> + <el-tag v-else type="danger">鎸傝捣</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="deploymentTime" label="閮ㄧ讲鏃堕棿" :show-overflow-tooltip="true"></el-table-column> + <el-table-column fixed="right" label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + link + type="primary" + size="small" + :icon="scope.row.suspensionState === 1 ? 'Lock' : 'Unlock'" + @click="handleProcessDefState(scope.row)" + > + {{ scope.row.suspensionState === 1 ? '鎸傝捣娴佺▼' : '婵�娲绘祦绋�' }} + </el-button> + </el-col> + <el-col :span="1.5"> + <el-button link type="primary" icon="Delete" size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button> + </el-col> + </el-row> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" icon="Sort" size="small" @click="handleConvertToModel(scope.row)"> 杞崲妯″瀷 </el-button> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + </el-dialog> + </div> +</template> + +<script lang="ts" setup name="processDefinition"> +import { + listProcessDefinition, + processDefinitionImage, + processDefinitionXml, + deleteProcessDefinition, + updateProcessDefState, + convertToModel, + deployProcessFile, + getProcessDefinitionListByKey +} from '@/api/workflow/processDefinition'; +import ProcessPreview from './components/processPreview.vue'; +import { listCategory } from '@/api/workflow/category'; +import { CategoryVO } from '@/api/workflow/category/types'; +import { ProcessDefinitionQuery, ProcessDefinitionVO } from '@/api/workflow/processDefinition/types'; +import { UploadRequestOptions } from 'element-plus'; + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +const previewRef = ref<InstanceType<typeof ProcessPreview>>(); +const queryFormRef = ref<ElFormInstance>(); +const categoryTreeRef = ref<ElTreeInstance>(); + +type CategoryOption = { + categoryCode: string; + categoryName: string; + children?: CategoryOption[]; +}; + +const loading = ref(true); +const ids = ref<Array<string | number>>([]); +const single = ref(true); +const multiple = ref(true); +const showSearch = ref(true); +const total = ref(0); +const uploadDialogLoading = ref(false); +const processDefinitionList = ref<ProcessDefinitionVO[]>([]); +const processDefinitionHistoryList = ref<ProcessDefinitionVO[]>([]); +const url = ref<string[]>([]); +const categoryOptions = ref<CategoryOption[]>([]); +const categoryName = ref(''); + +const uploadDialog = reactive<DialogOption>({ + visible: false, + title: '閮ㄧ讲娴佺▼鏂囦欢' +}); + +const processDefinitionDialog = reactive<DialogOption>({ + visible: false, + title: '鍘嗗彶鐗堟湰' +}); + +// 鏌ヨ鍙傛暟 +const queryParams = ref<ProcessDefinitionQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + key: undefined, + categoryCode: undefined +}); + +onMounted(() => { + getList(); + getTreeselect(); +}); + +/** 鑺傜偣鍗曞嚮浜嬩欢 */ +const handleNodeClick = (data: CategoryVO) => { + queryParams.value.categoryCode = data.categoryCode; + if (data.categoryCode === 'ALL') { + queryParams.value.categoryCode = ''; + } + handleQuery(); +}; +/** 閫氳繃鏉′欢杩囨护鑺傜偣 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.categoryName.indexOf(value) !== -1; +}; +/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */ +watchEffect( + () => { + categoryTreeRef.value.filter(categoryName.value); + }, + { + flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛� + } +); + +/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */ +const getTreeselect = async () => { + const res = await listCategory(); + categoryOptions.value = []; + const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] }; + data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId'); + categoryOptions.value.push(data); +}; + +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getList(); +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.categoryCode = ''; + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: any) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +//鍒嗛〉 +const getList = async () => { + loading.value = true; + const resp = await listProcessDefinition(queryParams.value); + processDefinitionList.value = resp.rows; + total.value = resp.total; + loading.value = false; +}; +//鑾峰彇鍘嗗彶娴佺▼瀹氫箟 +const getProcessDefinitionHitoryList = async (id: string, key: string) => { + processDefinitionDialog.visible = true; + loading.value = true; + const resp = await getProcessDefinitionListByKey(key); + if (resp.data && resp.data.length > 0) { + processDefinitionHistoryList.value = resp.data.filter((item: any) => item.id !== id); + } + loading.value = false; +}; + +//棰勮鍥剧墖 +const clickPreviewImg = async (id: string) => { + loading.value = true; + const resp = await processDefinitionImage(id); + if (previewRef.value) { + url.value = []; + url.value.push('data:image/png;base64,' + resp.data); + loading.value = false; + previewRef.value.openDialog(url.value, 'png'); + } +}; +//棰勮xml +const clickPreviewXML = async (id: string) => { + loading.value = true; + const resp = await processDefinitionXml(id); + if (previewRef.value) { + url.value = []; + url.value = resp.data.xml; + loading.value = false; + previewRef.value.openDialog(url.value, 'xml'); + } +}; +/** 鍒犻櫎鎸夐挳鎿嶄綔 */ +const handleDelete = async (row: ProcessDefinitionVO) => { + await proxy?.$modal.confirm('鏄惁纭鍒犻櫎娴佺▼瀹氫箟key涓恒��' + row.key + '銆戠殑鏁版嵁椤癸紵'); + loading.value = true; + await deleteProcessDefinition(row.deploymentId, row.id).finally(() => (loading.value = false)); + await getList(); + proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛'); +}; +/** 鎸傝捣/婵�娲� */ +const handleProcessDefState = async (row: ProcessDefinitionVO) => { + let msg: string; + if (row.suspensionState === 1) { + msg = `鏆傚仠鍚庯紝姝ゆ祦绋嬩笅鐨勬墍鏈変换鍔¢兘涓嶅厑璁稿線鍚庢祦杞紝鎮ㄧ‘瀹氭寕璧枫��${row.name || row.key}銆戝悧锛焋; + } else { + msg = `鍚姩鍚庯紝姝ゆ祦绋嬩笅鐨勬墍鏈変换鍔¢兘鍏佽寰�鍚庢祦杞紝鎮ㄧ‘瀹氭縺娲汇��${row.name || row.key}銆戝悧锛焋; + } + await proxy?.$modal.confirm(msg); + loading.value = true; + await updateProcessDefState(row.id).finally(() => (loading.value = false)); + await getList(); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); +}; +/** 娴佺▼瀹氫箟杞崲涓烘ā鍨� */ +const handleConvertToModel = async (row: ProcessDefinitionVO) => { + await proxy?.$modal.confirm('鏄惁纭杞崲娴佺▼瀹氫箟key涓恒��' + row.key + '銆戠殑鏁版嵁椤癸紵'); + await convertToModel(row.id).finally(() => (loading.value = false)); + getList(); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); +}; + +//閮ㄧ讲鏂囦欢 +const handerDeployProcessFile = (data: UploadRequestOptions): XMLHttpRequest => { + let formData = new FormData(); + if (queryParams.value.categoryCode === 'ALL') { + proxy?.$modal.msgError('椤剁骇鑺傜偣涓嶅彲浣滀负鍒嗙被锛�'); + return; + } + if (!queryParams.value.categoryCode) { + proxy?.$modal.msgError('璇烽�夋嫨宸︿晶瑕佷笂浼犵殑鍒嗙被锛�'); + return; + } + uploadDialogLoading.value = true + formData.append('file', data.file); + formData.append('categoryCode', queryParams.value.categoryCode); + deployProcessFile(formData).then(() => { + uploadDialog.visible = false; + proxy?.$modal.msgSuccess('閮ㄧ讲鎴愬姛'); + uploadDialogLoading.value = false + handleQuery(); + }); +}; +</script> diff --git a/src/views/workflow/processInstance/index.vue b/src/views/workflow/processInstance/index.vue new file mode 100644 index 0000000..9830366 --- /dev/null +++ b/src/views/workflow/processInstance/index.vue @@ -0,0 +1,363 @@ +<template> + <div class="p-2"> + <el-row :gutter="20"> + <!-- 娴佺▼鍒嗙被鏍� --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable /> + <el-tree + ref="categoryTreeRef" + class="mt-2" + node-key="id" + :data="categoryOptions" + :props="{ label: 'categoryName', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + ></el-tree> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <div class="mb-[10px]"> + <el-card shadow="hover" class="text-center"> + <el-radio-group v-model="tab" @change="changeTab(tab)"> + <el-radio-button label="running">杩愯涓�</el-radio-button> + <el-radio-button label="finish">宸插畬鎴�</el-radio-button> + </el-radio-group> + </el-card> + </div> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px"> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟KEY" prop="key"> + <el-input v-model="queryParams.key" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">鍒犻櫎</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="processInstanceList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column v-if="false" fixed align="center" prop="id" label="id"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column> + <el-table-column align="center" prop="processDefinitionVersion" label="鐗堟湰鍙�" width="90"> + <template #default="scope"> v{{ scope.row.processDefinitionVersion }}.0</template> + </el-table-column> + <el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag v-if="!scope.row.isSuspended" type="success">婵�娲�</el-tag> + <el-tag v-else type="danger">鎸傝捣</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="startTime" label="鍚姩鏃堕棿" width="160"></el-table-column> + <el-table-column v-if="tab === 'finish'" align="center" prop="endTime" label="缁撴潫鏃堕棿" width="160"></el-table-column> + <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button> + </el-col> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button> + </el-col> + </el-row> + <el-row v-if="tab === 'running'" :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + link + type="primary" + size="small" + icon="Sort" + @click="getProcessDefinitionHitoryList(scope.row.processDefinitionId, scope.row.processDefinitionKey)" + >鍒囨崲鐗堟湰</el-button + > + </el-col> + <el-col :span="1.5"> + <el-popover :ref="`popoverRef${scope.$index}`" trigger="click" placement="left" :width="300"> + <el-input v-model="deleteReason" resize="none" :rows="3" type="textarea" placeholder="璇疯緭鍏ヤ綔搴熷師鍥�" /> + <div style="text-align: right; margin: 5px 0px 0px 0px"> + <el-button size="small" text @click="cancelPopover(scope.$index)">鍙栨秷</el-button> + <el-button size="small" type="primary" @click="handleInvalid(scope.row)">纭</el-button> + </div> + <template #reference> + <el-button link type="primary" size="small" icon="CircleClose">浣滃簾</el-button> + </template> + </el-popover> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="handleQuery" + /> + </el-card> + </el-col> + </el-row> + <el-dialog v-if="processDefinitionDialog.visible" v-model="processDefinitionDialog.visible" :title="processDefinitionDialog.title" width="70%"> + <el-table v-loading="loading" :data="processDefinitionHistoryList"> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="name" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column align="center" prop="key" label="鏍囪瘑Key"></el-table-column> + <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90"> + <template #default="scope"> v{{ scope.row.version }}.0</template> + </el-table-column> + <el-table-column align="center" prop="suspensionState" label="鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag v-if="scope.row.suspensionState == 1" type="success">婵�娲�</el-tag> + <el-tag v-else type="danger">鎸傝捣</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="deploymentTime" label="閮ㄧ讲鏃堕棿" :show-overflow-tooltip="true"></el-table-column> + <el-table-column fixed="right" label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button link type="primary" size="small" icon="Sort" @click="handleChange(scope.row.id)">鍒囨崲</el-button> + </template> + </el-table-column> + </el-table> + </el-dialog> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + </div> +</template> + +<script lang="ts" setup> +import { + getProcessInstanceRunningByPage, + getProcessInstanceFinishByPage, + deleteRuntimeProcessAndHisInst, + deleteFinishProcessAndHisInst, + deleteRuntimeProcessInst +} from '@/api/workflow/processInstance'; +import { getProcessDefinitionListByKey, migrationProcessDefinition } from '@/api/workflow/processDefinition'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import { listCategory } from '@/api/workflow/category'; +import { CategoryVO } from '@/api/workflow/category/types'; +import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types'; +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const queryFormRef = ref<ElFormInstance>(); +const categoryTreeRef = ref<ElTreeInstance>(); + +// 閬僵灞� +const loading = ref(true); +// 閫変腑鏁扮粍 +const ids = ref<Array<any>>([]); +// 闈炲崟涓鐢� +const single = ref(true); +// 闈炲涓鐢� +const multiple = ref(true); +// 鏄剧ず鎼滅储鏉′欢 +const showSearch = ref(true); +// 鎬绘潯鏁� +const total = ref(0); +// 娴佺▼瀹氫箟id +const processDefinitionId = ref<string>(''); +// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁 +const processInstanceList = ref<ProcessInstanceVO[]>([]); +const processDefinitionHistoryList = ref<Array<any>>([]); +const categoryOptions = ref<CategoryOption[]>([]); +const categoryName = ref(''); + +const processDefinitionDialog = reactive<DialogOption>({ + visible: false, + title: '娴佺▼瀹氫箟' +}); + +type CategoryOption = { + categoryCode: string; + categoryName: string; + children?: CategoryOption[]; +}; + +const tab = ref('running'); +// 浣滃簾鍘熷洜 +const deleteReason = ref(''); +// 鏌ヨ鍙傛暟 +const queryParams = ref<ProcessInstanceQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + key: undefined, + categoryCode: undefined +}); + +onMounted(() => { + getProcessInstanceRunningList(); + getTreeselect(); +}); + +/** 鑺傜偣鍗曞嚮浜嬩欢 */ +const handleNodeClick = (data: CategoryVO) => { + queryParams.value.categoryCode = data.categoryCode; + if (data.categoryCode === 'ALL') { + queryParams.value.categoryCode = ''; + } + handleQuery(); +}; +/** 閫氳繃鏉′欢杩囨护鑺傜偣 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.categoryName.indexOf(value) !== -1; +}; +/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */ +watchEffect( + () => { + categoryTreeRef.value.filter(categoryName.value); + }, + { + flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛� + } +); + +/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */ +const getTreeselect = async () => { + const res = await listCategory(); + categoryOptions.value = []; + const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] }; + data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId'); + categoryOptions.value.push(data); +}; + +//瀹℃壒璁板綍 +const handleApprovalRecord = (row: any) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(row.id); + } +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + if ('running' === tab.value) { + getProcessInstanceRunningList(); + } else { + getProcessInstanceFinishList(); + } +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.categoryCode = ''; + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: ProcessInstanceVO[]) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +//鍒嗛〉 +const getProcessInstanceRunningList = () => { + loading.value = true; + getProcessInstanceRunningByPage(queryParams.value).then((resp) => { + processInstanceList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; +//鍒嗛〉 +const getProcessInstanceFinishList = () => { + loading.value = true; + getProcessInstanceFinishByPage(queryParams.value).then((resp) => { + processInstanceList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; + +/** 鍒犻櫎鎸夐挳鎿嶄綔 */ +const handleDelete = async (row: any) => { + const id = row.id || ids.value; + await proxy?.$modal.confirm('鏄惁纭鍒犻櫎id涓恒��' + id + '銆戠殑鏁版嵁椤癸紵'); + loading.value = true; + if ('running' === tab.value) { + await deleteRuntimeProcessAndHisInst(id).finally(() => (loading.value = false)); + getProcessInstanceRunningList(); + } else { + await deleteFinishProcessAndHisInst(id).finally(() => (loading.value = false)); + getProcessInstanceFinishList(); + } + proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛'); +}; +const changeTab = async (data: string) => { + queryParams.value.pageNum = 1; + if ('running' === data) { + getProcessInstanceRunningList(); + } else { + getProcessInstanceFinishList(); + } +}; +/** 浣滃簾鎸夐挳鎿嶄綔 */ +const handleInvalid = async (row: ProcessInstanceVO) => { + await proxy?.$modal.confirm('鏄惁纭浣滃簾涓氬姟id涓恒��' + row.businessKey + '銆戠殑鏁版嵁椤癸紵'); + loading.value = true; + if ('running' === tab.value) { + let param = { + processInstanceId: row.id, + deleteReason: deleteReason.value + }; + await deleteRuntimeProcessInst(param).finally(() => (loading.value = false)); + getProcessInstanceRunningList(); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); + } +}; +const cancelPopover = async (index: any) => { + (proxy?.$refs[`popoverRef${index}`] as any).hide(); //鍏抽棴寮圭獥 +}; +//鑾峰彇娴佺▼瀹氫箟 +const getProcessDefinitionHitoryList = (id: string, key: string) => { + processDefinitionDialog.visible = true; + processDefinitionId.value = id; + loading.value = true; + getProcessDefinitionListByKey(key).then((resp) => { + if (resp.data && resp.data.length > 0) { + processDefinitionHistoryList.value = resp.data.filter((item: any) => item.id !== id); + } + loading.value = false; + }); +}; +//鍒囨崲娴佺▼鐗堟湰 +const handleChange = async (id: string) => { + await proxy?.$modal.confirm('鏄惁纭鍒囨崲锛�'); + loading.value = true; + migrationProcessDefinition(processDefinitionId.value, id).then((resp) => { + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); + getProcessInstanceRunningList(); + processDefinitionDialog.visible = false; + loading.value = false; + }); +}; +</script> diff --git a/src/views/workflow/task/allTaskWaiting.vue b/src/views/workflow/task/allTaskWaiting.vue new file mode 100644 index 0000000..ef1c45e --- /dev/null +++ b/src/views/workflow/task/allTaskWaiting.vue @@ -0,0 +1,237 @@ +<template> + <div class="p-2"> + <div class="mb-[10px]"> + <el-card shadow="hover" class="text-center"> + <el-radio-group v-model="tab" @change="changeTab(tab)"> + <el-radio-button label="waiting">寰呭姙浠诲姟</el-radio-button> + <el-radio-button label="finish">宸插姙浠诲姟</el-radio-button> + </el-radio-group> + </el-card> + </div> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="浠诲姟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName"> + <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey"> + <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="primary" plain icon="Edit" @click="handleUpdate">淇敼鍔炵悊浜�</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column> + <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�"> + <template v-if="tab === 'waiting'" #default="scope"> + <template v-if="scope.row.participantVo && scope.row.assignee === null"> + <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success"> + {{ item }} + </el-tag> + </template> + <template v-else> + <el-tag type="success"> + {{ scope.row.assigneeName }} + </el-tag> + </template> + </template> + <template v-else-if="tab === 'finish'" #default="scope"> + <el-tag type="success"> + {{ scope.row.assigneeName }} + </el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag v-if="tab === 'waiting'" type="success">{{ scope.row.businessStatusName }}</el-tag> + <el-tag v-else type="success">宸插畬鎴�</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column> + <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button> + </el-col> + <el-col v-if="scope.row.multiInstance" :span="1.5"> + <el-button link type="primary" size="small" icon="CirclePlus" @click="addMultiInstanceUser(scope.row)">鍔犵</el-button> + </el-col> + <el-col v-if="scope.row.multiInstance" :span="1.5"> + <el-button link type="primary" size="small" icon="Remove" @click="deleteMultiInstanceUser(scope.row)">鍑忕</el-button> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="handleQuery" + /> + </el-card> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + <!-- 鎻愪氦缁勪欢 --> + <submitVerify ref="submitVerifyRef" :task-id="taskId" @submit-callback="handleQuery" /> + <!-- 鍔犵缁勪欢 --> + <multiInstanceUser ref="multiInstanceUserRef" :title="title" @submit-callback="handleQuery" /> + <!-- 鍔犵缁勪欢 --> + <SysUser ref="sysUserRef" :multiple="true" @submit-callback="submitCallback" /> + </div> +</template> + +<script lang="ts" setup> +import { getAllTaskWaitByPage, getAllTaskFinishByPage, updateAssignee } from '@/api/workflow/task'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import SubmitVerify from '@/components/Process/submitVerify.vue'; +import MultiInstanceUser from '@/components/Process/multiInstance-user.vue'; +import SysUser from '@/components/Process/sys-user.vue'; +import { TaskQuery, TaskVO } from '@/api/workflow/task/types'; +//鎻愪氦缁勪欢 +const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>(); +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); +//鍔犵缁勪欢 +const multiInstanceUserRef = ref<InstanceType<typeof MultiInstanceUser>>(); +//閫変汉缁勪欢 +const sysUserRef = ref<InstanceType<typeof SysUser>>(); + +const queryFormRef = ref<ElFormInstance>(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +// 閬僵灞� +const loading = ref(true); +// 閫変腑鏁扮粍 +const ids = ref<Array<any>>([]); +// 闈炲崟涓鐢� +const single = ref(true); +// 闈炲涓鐢� +const multiple = ref(true); +// 鏄剧ず鎼滅储鏉′欢 +const showSearch = ref(true); +// 鎬绘潯鏁� +const total = ref(0); +// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁 +const taskList = ref([]); +// 浠诲姟id +const taskId = ref(''); +const title = ref(''); +// 鏌ヨ鍙傛暟 +const queryParams = ref<TaskQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + processDefinitionName: undefined, + processDefinitionKey: undefined +}); +const tab = ref('waiting'); +onMounted(() => { + getWaitingList(); +}); +//瀹℃壒璁板綍 +const handleApprovalRecord = (row: TaskVO) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(row.processInstanceId); + } +}; +//鍔犵 +const addMultiInstanceUser = (row: TaskVO) => { + if (multiInstanceUserRef.value) { + title.value = '鍔犵浜哄憳'; + multiInstanceUserRef.value.getAddMultiInstanceList(row.id, []); + } +}; +//鍑忕 +const deleteMultiInstanceUser = (row: TaskVO) => { + if (multiInstanceUserRef.value) { + title.value = '鍑忕浜哄憳'; + multiInstanceUserRef.value.getDeleteMultiInstanceList(row.id); + } +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + if ('waiting' === tab.value) { + getWaitingList(); + } else { + getFinishList(); + } +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: any) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +const changeTab = async (data: string) => { + queryParams.value.pageNum = 1; + if ('waiting' === data) { + getWaitingList(); + } else { + getFinishList(); + } +}; +//鍒嗛〉 +const getWaitingList = () => { + loading.value = true; + getAllTaskWaitByPage(queryParams.value).then((resp) => { + taskList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; +const getFinishList = () => { + loading.value = true; + getAllTaskFinishByPage(queryParams.value).then((resp) => { + taskList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; +const handleUpdate = () => { + if (sysUserRef.value) { + sysUserRef.value.getUserList([]); + } +}; +//淇敼鍔炵悊浜� +const submitCallback = (data) => { + if (data && data.value.length > 0) { + updateAssignee(ids.value, data.value[0].userId).then((resp) => { + sysUserRef.value.close(); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); + handleQuery(); + }); + } +}; +</script> diff --git a/src/views/workflow/task/myDocument.vue b/src/views/workflow/task/myDocument.vue new file mode 100644 index 0000000..1e1d2a0 --- /dev/null +++ b/src/views/workflow/task/myDocument.vue @@ -0,0 +1,256 @@ +<template> + <div class="p-2"> + <el-row :gutter="20"> + <!-- 娴佺▼鍒嗙被鏍� --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable /> + <el-tree + ref="categoryTreeRef" + class="mt-2" + node-key="id" + :data="categoryOptions" + :props="{ label: 'categoryName', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + ></el-tree> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px"> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="processInstanceList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column v-if="false" fixed align="center" prop="id" label="id"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column> + <el-table-column align="center" prop="processDefinitionVersion" label="鐗堟湰鍙�" width="90"> + <template #default="scope"> v{{ scope.row.processDefinitionVersion }}.0</template> + </el-table-column> + <el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag v-if="!scope.row.isSuspended" type="success">婵�娲�</el-tag> + <el-tag v-else type="danger">鎸傝捣</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="startTime" label="鍚姩鏃堕棿" width="160"></el-table-column> + <el-table-column v-if="tab === 'finish'" align="center" prop="endTime" label="缁撴潫鏃堕棿" width="160"></el-table-column> + <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row.id)">瀹℃壒璁板綍</el-button> + </el-col> + <el-col + v-if="scope.row.businessStatus === 'draft' || scope.row.businessStatus === 'cancel' || scope.row.businessStatus === 'back'" + :span="1.5" + > + <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button> + </el-col> + <el-col v-if="scope.row.businessStatus === 'waiting'" :span="1.5"> + <el-button link type="primary" size="small" icon="Notification" @click="handleCancelProcessApply(scope.row.id)">鎾ら攢</el-button> + </el-col> + <el-col + v-if="scope.row.businessStatus === 'draft' || scope.row.businessStatus === 'cancel' || scope.row.businessStatus === 'back'" + :span="1.5" + > + <el-button link type="primary" size="small" icon="Edit" @click="submitVerifyOpen(scope.row.taskVoList[0].id)">鎻愪氦</el-button> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="getList" + /> + </el-card> + </el-col> + </el-row> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + <!-- 鎻愪氦缁勪欢 --> + <submitVerify ref="submitVerifyRef" @submit-callback="getList" /> + </div> +</template> + +<script lang="ts" setup> +import { getCurrentSubmitByPage, deleteRuntimeProcessAndHisInst, cancelProcessApply } from '@/api/workflow/processInstance'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import SubmitVerify from '@/components/Process/submitVerify.vue'; +import { listCategory } from '@/api/workflow/category'; +import { CategoryVO } from '@/api/workflow/category/types'; +import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types'; +//鎻愪氦缁勪欢 +const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>(); +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const queryFormRef = ref<ElFormInstance>(); +const categoryTreeRef = ref<ElTreeInstance>(); + +// 閬僵灞� +const loading = ref(true); +// 閫変腑鏁扮粍 +const ids = ref<Array<any>>([]); +// 闈炲崟涓鐢� +const single = ref(true); +// 闈炲涓鐢� +const multiple = ref(true); +// 鏄剧ず鎼滅储鏉′欢 +const showSearch = ref(true); +// 鎬绘潯鏁� +const total = ref(0); +// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁 +const processInstanceList = ref<ProcessInstanceVO[]>([]); + +const categoryOptions = ref<CategoryOption[]>([]); +const categoryName = ref(''); + +interface CategoryOption { + categoryCode: string; + categoryName: string; + children?: CategoryOption[]; +} + +const tab = ref('running'); +// 鏌ヨ鍙傛暟 +const queryParams = ref<ProcessInstanceQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + categoryCode: undefined +}); + +onMounted(() => { + getList(); + getTreeselect(); +}); + +/** 鑺傜偣鍗曞嚮浜嬩欢 */ +const handleNodeClick = (data: CategoryVO) => { + queryParams.value.categoryCode = data.categoryCode; + if (data.categoryCode === 'ALL') { + queryParams.value.categoryCode = ''; + } + handleQuery(); +}; +/** 閫氳繃鏉′欢杩囨护鑺傜偣 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.categoryName.indexOf(value) !== -1; +}; +/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */ +watchEffect( + () => { + categoryTreeRef.value.filter(categoryName.value); + }, + { + flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛� + } +); + +/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */ +const getTreeselect = async () => { + const res = await listCategory(); + categoryOptions.value = []; + const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] }; + data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId'); + categoryOptions.value.push(data); +}; + +//瀹℃壒璁板綍 +const handleApprovalRecord = (processInstanceId: string) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(processInstanceId); + } +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + getList(); +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.categoryCode = ''; + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: ProcessInstanceVO[]) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +//鍒嗛〉 +const getList = () => { + loading.value = true; + getCurrentSubmitByPage(queryParams.value).then((resp) => { + processInstanceList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; + +/** 鍒犻櫎鎸夐挳鎿嶄綔 */ +const handleDelete = async (row: ProcessInstanceVO) => { + const id = row.id || ids.value; + await proxy?.$modal.confirm('鏄惁纭鍒犻櫎id涓恒��' + id + '銆戠殑鏁版嵁椤癸紵'); + loading.value = true; + if ('running' === tab.value) { + await deleteRuntimeProcessAndHisInst(id).finally(() => (loading.value = false)); + getList(); + } + proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛'); +}; + +/** 鎾ら攢鎸夐挳鎿嶄綔 */ +const handleCancelProcessApply = async (processInstanceId: string) => { + await proxy?.$modal.confirm('鏄惁纭鎾ら攢褰撳墠鍗曟嵁锛�'); + loading.value = true; + if ('running' === tab.value) { + await cancelProcessApply(processInstanceId).finally(() => (loading.value = false)); + getList(); + } + proxy?.$modal.msgSuccess('鎾ら攢鎴愬姛'); +}; +//鎻愪氦 +const submitVerifyOpen = async (id: string) => { + if (submitVerifyRef.value) { + submitVerifyRef.value.openDialog(id); + } +}; +</script> diff --git a/src/views/workflow/task/taskCopyList.vue b/src/views/workflow/task/taskCopyList.vue new file mode 100644 index 0000000..6348024 --- /dev/null +++ b/src/views/workflow/task/taskCopyList.vue @@ -0,0 +1,141 @@ +<template> + <div class="p-2"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="浠诲姟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName"> + <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey"> + <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column> + <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�"> + <template #default="scope"> + <template v-if="scope.row.participantVo && scope.row.assignee === null"> + <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success"> + {{ item }} + </el-tag> + </template> + <template v-else> + <el-tag type="success"> + {{ scope.row.assigneeName }} + </el-tag> + </template> + </template> + </el-table-column> + <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="handleQuery" + /> + </el-card> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + </div> +</template> + +<script lang="ts" setup> +import { getTaskCopyByPage} from '@/api/workflow/task'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import { TaskQuery, TaskVO } from '@/api/workflow/task/types'; +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); +const queryFormRef = ref<ElFormInstance>(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +// 閬僵灞� +const loading = ref(true); +// 閫変腑鏁扮粍 +const ids = ref<Array<any>>([]); +// 闈炲崟涓鐢� +const single = ref(true); +// 闈炲涓鐢� +const multiple = ref(true); +// 鏄剧ず鎼滅储鏉′欢 +const showSearch = ref(true); +// 鎬绘潯鏁� +const total = ref(0); +// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁 +const taskList = ref([]); +// 鏌ヨ鍙傛暟 +const queryParams = ref<TaskQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + processDefinitionName: undefined, + processDefinitionKey: undefined +}); +onMounted(() => { + getTaskCopyList(); +}); +//瀹℃壒璁板綍 +const handleApprovalRecord = (row: TaskVO) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(row.processInstanceId); + } +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + getTaskCopyList(); +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: any) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +//鍒嗛〉 +const getTaskCopyList = () => { + loading.value = true; + getTaskCopyByPage(queryParams.value).then((resp) => { + taskList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; + +</script> diff --git a/src/views/workflow/task/taskFinish.vue b/src/views/workflow/task/taskFinish.vue new file mode 100644 index 0000000..3e8adba --- /dev/null +++ b/src/views/workflow/task/taskFinish.vue @@ -0,0 +1,128 @@ +<template> + <div class="p-2"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="浠诲姟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName"> + <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey"> + <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column> + <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�"> + <template #default="scope"> + <el-tag type="success"> + {{ scope.row.assigneeName }} + </el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column> + <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="handleQuery" + /> + </el-card> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + </div> +</template> + +<script lang="ts" setup> +import { getTaskFinishByPage } from '@/api/workflow/task'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import { TaskQuery, TaskVO } from '@/api/workflow/task/types'; +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); +const queryFormRef = ref<ElFormInstance>(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +// 閬僵灞� +const loading = ref(true); +// 閫変腑鏁扮粍 +const ids = ref<Array<any>>([]); +// 闈炲崟涓鐢� +const single = ref(true); +// 闈炲涓鐢� +const multiple = ref(true); +// 鏄剧ず鎼滅储鏉′欢 +const showSearch = ref(true); +// 鎬绘潯鏁� +const total = ref(0); +// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁 +const taskList = ref([]); +// 鏌ヨ鍙傛暟 +const queryParams = ref<TaskQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + processDefinitionName: undefined, + processDefinitionKey: undefined +}); +onMounted(() => { + getFinishList(); +}); +//瀹℃壒璁板綍 +const handleApprovalRecord = (row: TaskVO) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(row.processInstanceId); + } +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + getFinishList(); +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: any) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +const getFinishList = () => { + loading.value = true; + getTaskFinishByPage(queryParams.value).then((resp) => { + taskList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; +</script> diff --git a/src/views/workflow/task/taskWaiting.vue b/src/views/workflow/task/taskWaiting.vue new file mode 100644 index 0000000..c3f34aa --- /dev/null +++ b/src/views/workflow/task/taskWaiting.vue @@ -0,0 +1,181 @@ +<template> + <div class="p-2"> + <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> + <div v-show="showSearch" class="mb-[10px]"> + <el-card shadow="hover"> + <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px"> + <el-form-item label="浠诲姟鍚嶇О" prop="name"> + <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName"> + <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey"> + <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button> + <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10" class="mb8"> + <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column> + <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column> + <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�"> + <template #default="scope"> + <template v-if="scope.row.participantVo && scope.row.assignee === null"> + <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success"> + {{ item }} + </el-tag> + </template> + <template v-else> + <el-tag type="success"> + {{ scope.row.assigneeName }} + </el-tag> + </template> + </template> + </el-table-column> + <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70"> + <template #default="scope"> + <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag> + </template> + </el-table-column> + <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column> + <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button> + </el-col> + <el-col v-if="scope.row.participantVo && (scope.row.participantVo.claim === null || scope.row.participantVo.claim === true)" :span="1.5"> + <el-button link type="primary" size="small" icon="Edit" @click="submitVerifyOpen(scope.row.id)">鍔炵悊</el-button> + </el-col> + <el-col v-if="scope.row.participantVo && scope.row.participantVo.claim === true" :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="handleReturnTask(scope.row.id)">褰掕繕</el-button> + </el-col> + <el-col v-if="scope.row.participantVo && scope.row.participantVo.claim === false" :span="1.5"> + <el-button link type="primary" size="small" icon="Document" @click="handleClaimTask(scope.row.id)">璁ら</el-button> + </el-col> + </el-row> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="handleQuery" + /> + </el-card> + <!-- 瀹℃壒璁板綍 --> + <approvalRecord ref="approvalRecordRef" /> + <!-- 鎻愪氦缁勪欢 --> + <submitVerify ref="submitVerifyRef" @submit-callback="handleQuery" /> + </div> +</template> + +<script lang="ts" setup> +import { getTaskWaitByPage, claim, returnTask } from '@/api/workflow/task'; +import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import SubmitVerify from '@/components/Process/submitVerify.vue'; +import { TaskQuery, TaskVO } from '@/api/workflow/task/types'; +//鎻愪氦缁勪欢 +const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>(); +//瀹℃壒璁板綍缁勪欢 +const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); +const queryFormRef = ref<ElFormInstance>(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +// 閬僵灞� +const loading = ref(true); +// 閫変腑鏁扮粍 +const ids = ref<Array<any>>([]); +// 闈炲崟涓鐢� +const single = ref(true); +// 闈炲涓鐢� +const multiple = ref(true); +// 鏄剧ず鎼滅储鏉′欢 +const showSearch = ref(true); +// 鎬绘潯鏁� +const total = ref(0); +// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁 +const taskList = ref([]); +// 鏌ヨ鍙傛暟 +const queryParams = ref<TaskQuery>({ + pageNum: 1, + pageSize: 10, + name: undefined, + processDefinitionName: undefined, + processDefinitionKey: undefined +}); +onMounted(() => { + getWaitingList(); +}); +//瀹℃壒璁板綍 +const handleApprovalRecord = (row: TaskVO) => { + if (approvalRecordRef.value) { + approvalRecordRef.value.init(row.processInstanceId); + } +}; +/** 鎼滅储鎸夐挳鎿嶄綔 */ +const handleQuery = () => { + getWaitingList(); +}; +/** 閲嶇疆鎸夐挳鎿嶄綔 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + queryParams.value.pageNum = 1; + queryParams.value.pageSize = 10; + handleQuery(); +}; +// 澶氶�夋閫変腑鏁版嵁 +const handleSelectionChange = (selection: any) => { + ids.value = selection.map((item: any) => item.id); + single.value = selection.length !== 1; + multiple.value = !selection.length; +}; +//鍒嗛〉 +const getWaitingList = () => { + loading.value = true; + getTaskWaitByPage(queryParams.value).then((resp) => { + taskList.value = resp.rows; + total.value = resp.total; + loading.value = false; + }); +}; +//鎻愪氦 +const submitVerifyOpen = async (id: string) => { + if (submitVerifyRef.value) { + submitVerifyRef.value.openDialog(id); + } +}; + +/** 璁ら浠诲姟 */ +const handleClaimTask = async (taskId: string) => { + loading.value = true; + await claim(taskId).finally(() => (loading.value = false)); + getWaitingList(); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); +}; + +/** 褰掕繕浠诲姟 */ +const handleReturnTask = async (taskId: string) => { + loading.value = true; + await returnTask(taskId).finally(() => (loading.value = false)); + getWaitingList(); + proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛'); +}; +</script> diff --git a/vite.config.ts b/vite.config.ts index 01da668..99ea8e8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -65,7 +65,24 @@ 'echarts', 'vue-i18n', '@vueup/vue-quill', + 'bpmn-js/lib/Modeler.js', + 'bpmn-js-properties-panel', + 'min-dash', + 'bpmn-js/lib/features/palette/PaletteProvider', + 'bpmn-js/lib/features/context-pad/ContextPadProvider', + 'diagram-js/lib/draw/BaseRenderer', + 'tiny-svg', + 'element-plus/es/components/collapse-item/style/css', + 'element-plus/es/components/collapse/style/css', + 'element-plus/es/components/space/style/css', + 'element-plus/es/components/container/style/css', + 'element-plus/es/components/aside/style/css', + 'element-plus/es/components/main/style/css', + 'element-plus/es/components/header/style/css', + 'element-plus/es/components/button-group/style/css', + 'element-plus/es/components/radio-button/style/css', + 'element-plus/es/components/checkbox-group/style/css', 'element-plus/es/components/form/style/css', 'element-plus/es/components/form-item/style/css', 'element-plus/es/components/button/style/css', -- Gitblit v1.9.3