!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,优化画图接口 * 添加工作流模型新增,修改,查询,删除,画图工具
This commit is contained in:
19
package.json
19
package.json
@ -20,23 +20,33 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "2.3.1",
|
"@element-plus/icons-vue": "2.3.1",
|
||||||
|
"@highlightjs/vue-plugin": "2.1.0",
|
||||||
|
"@lezer/common": "1.2.1",
|
||||||
"@vueup/vue-quill": "1.2.0",
|
"@vueup/vue-quill": "1.2.0",
|
||||||
"@vueuse/core": "10.7.2",
|
"@vueuse/core": "10.7.2",
|
||||||
"animate.css": "4.1.1",
|
"animate.css": "4.1.1",
|
||||||
"await-to-js": "3.0.0",
|
"await-to-js": "3.0.0",
|
||||||
"axios": "1.6.5",
|
"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",
|
"crypto-js": "4.2.0",
|
||||||
|
"diagram-js": "12.3.0",
|
||||||
|
"didi": "9.0.2",
|
||||||
"echarts": "5.4.3",
|
"echarts": "5.4.3",
|
||||||
"element-plus": "2.4.4",
|
"element-plus": "2.4.4",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"fuse.js": "7.0.0",
|
"fuse.js": "7.0.0",
|
||||||
|
"highlight.js": "11.9.0",
|
||||||
"image-conversion": "^2.1.1",
|
"image-conversion": "^2.1.1",
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"jsencrypt": "3.3.2",
|
"jsencrypt": "3.3.2",
|
||||||
|
"moddle": "6.2.3",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"path-browserify": "1.0.1",
|
"path-browserify": "1.0.1",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"pinia": "2.1.7",
|
"pinia": "2.1.7",
|
||||||
|
"preact": "10.19.3",
|
||||||
"screenfull": "6.0.2",
|
"screenfull": "6.0.2",
|
||||||
"vform3-builds": "3.0.10",
|
"vform3-builds": "3.0.10",
|
||||||
"vue": "3.4.13",
|
"vue": "3.4.13",
|
||||||
@ -44,7 +54,8 @@
|
|||||||
"vue-i18n": "9.9.0",
|
"vue-i18n": "9.9.0",
|
||||||
"vue-router": "4.2.5",
|
"vue-router": "4.2.5",
|
||||||
"vue-types": "5.1.1",
|
"vue-types": "5.1.1",
|
||||||
"vxe-table": "4.5.18"
|
"vxe-table": "4.5.18",
|
||||||
|
"zeebe-bpmn-moddle": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/json": "2.2.168",
|
"@iconify/json": "2.2.168",
|
||||||
@ -60,8 +71,8 @@
|
|||||||
"@unocss/preset-attributify": "0.58.3",
|
"@unocss/preset-attributify": "0.58.3",
|
||||||
"@unocss/preset-icons": "0.58.3",
|
"@unocss/preset-icons": "0.58.3",
|
||||||
"@unocss/preset-uno": "0.58.3",
|
"@unocss/preset-uno": "0.58.3",
|
||||||
"@vue/compiler-sfc": "3.4.13",
|
|
||||||
"@vitejs/plugin-vue": "5.0.3",
|
"@vitejs/plugin-vue": "5.0.3",
|
||||||
|
"@vue/compiler-sfc": "3.4.13",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
@ -82,11 +93,11 @@
|
|||||||
"unplugin-icons": "0.18.2",
|
"unplugin-icons": "0.18.2",
|
||||||
"unplugin-vue-components": "0.26.0",
|
"unplugin-vue-components": "0.26.0",
|
||||||
"unplugin-vue-setup-extend-plus": "1.0.0",
|
"unplugin-vue-setup-extend-plus": "1.0.0",
|
||||||
|
"vite": "5.0.11",
|
||||||
"vite-plugin-compression": "0.5.1",
|
"vite-plugin-compression": "0.5.1",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vitest": "1.2.0",
|
"vitest": "1.2.0",
|
||||||
"vue-eslint-parser": "9.4.0",
|
"vue-eslint-parser": "9.4.0",
|
||||||
"vue-tsc": "1.8.27",
|
"vue-tsc": "1.8.27"
|
||||||
"vite": "5.0.11"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,17 @@ export const listRole = (query: RoleQuery): AxiosPromise<RoleVO[]> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过roleIds查询角色
|
||||||
|
* @param roleIds
|
||||||
|
*/
|
||||||
|
export const optionSelect = (roleIds: (number | string)[]): AxiosPromise<RoleVO[]> => {
|
||||||
|
return request({
|
||||||
|
url: '/system/role/optionselect?roleIds=' + roleIds,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询角色详细
|
* 查询角色详细
|
||||||
*/
|
*/
|
||||||
@ -142,3 +153,8 @@ export const deptTreeSelect = (roleId: string | number): AxiosPromise<RoleDeptTr
|
|||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
optionSelect,
|
||||||
|
listRole
|
||||||
|
};
|
||||||
|
@ -17,6 +17,17 @@ export const listUser = (query: UserQuery): AxiosPromise<UserVO[]> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过用户ids查询用户
|
||||||
|
* @param userIds
|
||||||
|
*/
|
||||||
|
export const optionSelect = (userIds: (number | string)[]): AxiosPromise<UserVO[]> => {
|
||||||
|
return request({
|
||||||
|
url: '/system/user/optionselect?userIds=' + userIds,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户详情
|
* 获取用户详情
|
||||||
* @param userId
|
* @param userId
|
||||||
@ -199,6 +210,7 @@ export const deptTreeSelect = (): AxiosPromise<DeptVO[]> => {
|
|||||||
export default {
|
export default {
|
||||||
listUser,
|
listUser,
|
||||||
getUser,
|
getUser,
|
||||||
|
optionSelect,
|
||||||
addUser,
|
addUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
delUser,
|
delUser,
|
||||||
|
63
src/api/workflow/category/index.ts
Normal file
63
src/api/workflow/category/index.ts
Normal file
@ -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'
|
||||||
|
});
|
||||||
|
};
|
67
src/api/workflow/category/types.ts
Normal file
67
src/api/workflow/category/types.ts
Normal file
@ -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;
|
||||||
|
}
|
63
src/api/workflow/leave/index.ts
Normal file
63
src/api/workflow/leave/index.ts
Normal file
@ -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'
|
||||||
|
});
|
||||||
|
};
|
22
src/api/workflow/leave/types.ts
Normal file
22
src/api/workflow/leave/types.ts
Normal file
@ -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;
|
||||||
|
}
|
91
src/api/workflow/model/index.ts
Normal file
91
src/api/workflow/model/index.ts
Normal file
@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按id删除模型
|
||||||
|
* @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'
|
||||||
|
});
|
||||||
|
};
|
66
src/api/workflow/model/types.ts
Normal file
66
src/api/workflow/model/types.ts
Normal file
@ -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;
|
||||||
|
}
|
123
src/api/workflow/processDefinition/index.ts
Normal file
123
src/api/workflow/processDefinition/index.ts
Normal file
@ -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或xml部署流程定义
|
||||||
|
* @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'
|
||||||
|
});
|
||||||
|
};
|
22
src/api/workflow/processDefinition/types.ts
Normal file
22
src/api/workflow/processDefinition/types.ts
Normal file
@ -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;
|
||||||
|
}
|
115
src/api/workflow/processInstance/index.ts
Normal file
115
src/api/workflow/processInstance/index.ts
Normal file
@ -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'
|
||||||
|
});
|
||||||
|
};
|
27
src/api/workflow/processInstance/types.ts
Normal file
27
src/api/workflow/processInstance/types.ts
Normal file
@ -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[];
|
||||||
|
}
|
181
src/api/workflow/task/index.ts
Normal file
181
src/api/workflow/task/index.ts
Normal file
@ -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'
|
||||||
|
});
|
||||||
|
};
|
39
src/api/workflow/task/types.ts
Normal file
39
src/api/workflow/task/types.ts
Normal file
@ -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;
|
||||||
|
}
|
53
src/api/workflow/workflowUser/index.ts
Normal file
53
src/api/workflow/workflowUser/index.ts
Normal file
@ -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
|
||||||
|
});
|
||||||
|
};
|
1
src/assets/icons/svg/caret-back.svg
Normal file
1
src/assets/icons/svg/caret-back.svg
Normal file
@ -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>
|
After Width: | Height: | Size: 223 B |
1
src/assets/icons/svg/caret-forward.svg
Normal file
1
src/assets/icons/svg/caret-forward.svg
Normal file
@ -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>
|
After Width: | Height: | Size: 223 B |
@ -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 {
|
.el-divider--horizontal {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@ -68,6 +79,12 @@
|
|||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 16px 16px 8px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
src/components/BpmnDesign/assets/defaultXML.ts
Normal file
23
src/components/BpmnDesign/assets/defaultXML.ts
Normal file
@ -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>`;
|
118
src/components/BpmnDesign/assets/lang/zh.ts
Normal file
118
src/components/BpmnDesign/assets/lang/zh.ts
Normal file
@ -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': '追加中间信号捕获事件'
|
||||||
|
};
|
1250
src/components/BpmnDesign/assets/moddle/flowable.ts
Normal file
1250
src/components/BpmnDesign/assets/moddle/flowable.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
@ -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;
|
@ -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 当前元素的svgNode
|
||||||
|
* @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'];
|
15
src/components/BpmnDesign/assets/module/Translate/index.ts
Normal file
15
src/components/BpmnDesign/assets/module/Translate/index.ts
Normal file
@ -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;
|
17
src/components/BpmnDesign/assets/module/index.ts
Normal file
17
src/components/BpmnDesign/assets/module/index.ts
Normal file
@ -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;
|
50
src/components/BpmnDesign/assets/showConfig.ts
Normal file
50
src/components/BpmnDesign/assets/showConfig.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
};
|
230
src/components/BpmnDesign/assets/style/index.scss
Normal file
230
src/components/BpmnDesign/assets/style/index.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
src/components/BpmnDesign/hooks/usePanel.ts
Normal file
132
src/components/BpmnDesign/hooks/usePanel.ts
Normal file
@ -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
|
||||||
|
};
|
||||||
|
};
|
34
src/components/BpmnDesign/hooks/useParseElement.ts
Normal file
34
src/components/BpmnDesign/hooks/useParseElement.ts
Normal file
@ -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
|
||||||
|
};
|
||||||
|
};
|
409
src/components/BpmnDesign/index.vue
Normal file
409
src/components/BpmnDesign/index.vue
Normal file
@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Canvas
|
||||||
|
*/
|
||||||
|
const initCanvas = () => {
|
||||||
|
bpmnModeler.value = new BpmnModeler({
|
||||||
|
container: canvas.value,
|
||||||
|
// 键盘
|
||||||
|
keyboard: {
|
||||||
|
bindTo: window // 或者window,注意与外部表单的键盘监听事件是否冲突
|
||||||
|
},
|
||||||
|
propertiesPanel: {
|
||||||
|
parent: panel.value
|
||||||
|
},
|
||||||
|
additionalModules: Modules,
|
||||||
|
moddleExtensions: {
|
||||||
|
flowable: flowableModdle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Model
|
||||||
|
*/
|
||||||
|
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>
|
68
src/components/BpmnDesign/panel/GatewayPanel.vue
Normal file
68
src/components/BpmnDesign/panel/GatewayPanel.vue
Normal file
@ -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>
|
71
src/components/BpmnDesign/panel/ProcessPanel.vue
Normal file
71
src/components/BpmnDesign/panel/ProcessPanel.vue
Normal file
@ -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>
|
94
src/components/BpmnDesign/panel/SequenceFlowPanel.vue
Normal file
94
src/components/BpmnDesign/panel/SequenceFlowPanel.vue
Normal file
@ -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>
|
40
src/components/BpmnDesign/panel/StartEndPanel.vue
Normal file
40
src/components/BpmnDesign/panel/StartEndPanel.vue
Normal file
@ -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>
|
467
src/components/BpmnDesign/panel/TaskPanel.vue
Normal file
467
src/components/BpmnDesign/panel/TaskPanel.vue
Normal file
@ -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>
|
||||||
|
属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,<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>
|
||||||
|
每创建一个用户任务前,先以该元素变量为label,集合中的一项为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>
|
104
src/components/BpmnDesign/panel/index.vue
Normal file
104
src/components/BpmnDesign/panel/index.vue
Normal file
@ -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为了让vue刷新
|
||||||
|
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>
|
252
src/components/BpmnDesign/panel/property/DueDate.vue
Normal file
252
src/components/BpmnDesign/panel/property/DueDate.vue
Normal file
@ -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>
|
305
src/components/BpmnDesign/panel/property/ExecutionListener.vue
Normal file
305
src/components/BpmnDesign/panel/property/ExecutionListener.vue
Normal file
@ -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>
|
121
src/components/BpmnDesign/panel/property/ListenerParam.vue
Normal file
121
src/components/BpmnDesign/panel/property/ListenerParam.vue
Normal file
@ -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>
|
307
src/components/BpmnDesign/panel/property/TaskListener.vue
Normal file
307
src/components/BpmnDesign/panel/property/TaskListener.vue
Normal file
@ -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(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发assignment事件。<br />
|
||||||
|
complete(完成):当任务已经完成,从运行时数据中删除前触发。<br />
|
||||||
|
delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
|
||||||
|
</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>
|
162
src/components/Process/approvalRecord.vue
Normal file
162
src/components/Process/approvalRecord.vue
Normal file
@ -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>
|
362
src/components/Process/multiInstance-user.vue
Normal file
362
src/components/Process/multiInstance-user.vue
Normal file
@ -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>
|
165
src/components/Process/submitVerify.vue
Normal file
165
src/components/Process/submitVerify.vue
Normal file
@ -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>
|
301
src/components/Process/sys-user.vue
Normal file
301
src/components/Process/sys-user.vue
Normal file
@ -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>
|
@ -196,4 +196,4 @@ defineExpose({
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
@ -258,4 +258,4 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
@ -2,9 +2,10 @@
|
|||||||
* v-copyText 复制文本内容
|
* v-copyText 复制文本内容
|
||||||
* Copyright (c) 2022 ruoyi
|
* Copyright (c) 2022 ruoyi
|
||||||
*/
|
*/
|
||||||
|
import { DirectiveBinding } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
beforeMount(el: any, { value, arg }: any) {
|
beforeMount(el: any, { value, arg }: DirectiveBinding) {
|
||||||
if (arg === 'callback') {
|
if (arg === 'callback') {
|
||||||
el.$copyCallback = value;
|
el.$copyCallback = value;
|
||||||
} else {
|
} else {
|
||||||
|
17
src/enums/bpmn/IndexEnums.ts
Normal file
17
src/enums/bpmn/IndexEnums.ts
Normal file
@ -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'
|
||||||
|
}
|
@ -28,4 +28,4 @@ export default (ops?: Options): Return => {
|
|||||||
openDialog,
|
openDialog,
|
||||||
closeDialog
|
closeDialog
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -15,6 +15,12 @@ import directive from './directive';
|
|||||||
// 注册插件
|
// 注册插件
|
||||||
import plugins from './plugins/index'; // plugins
|
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图标
|
// svg图标
|
||||||
import 'virtual:svg-icons-register';
|
import 'virtual:svg-icons-register';
|
||||||
import ElementIcons from '@/plugins/svgicon';
|
import ElementIcons from '@/plugins/svgicon';
|
||||||
@ -38,6 +44,7 @@ ElDialog.props.closeOnClickModal.default = false;
|
|||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(HighLight);
|
||||||
app.use(ElementIcons);
|
app.use(ElementIcons);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(store);
|
app.use(store);
|
||||||
|
76
src/store/modules/modeler.ts
Normal file
76
src/store/modules/modeler.ts
Normal file
@ -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;
|
13
src/types/bpmn/editor/global.d.ts
vendored
Normal file
13
src/types/bpmn/editor/global.d.ts
vendored
Normal file
@ -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;
|
||||||
|
}
|
15
src/types/bpmn/index.d.ts
vendored
Normal file
15
src/types/bpmn/index.d.ts
vendored
Normal file
@ -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;
|
||||||
|
}
|
37
src/types/bpmn/moddle.d.ts
vendored
Normal file
37
src/types/bpmn/moddle.d.ts
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
75
src/types/bpmn/panel.d.ts
vendored
Normal file
75
src/types/bpmn/panel.d.ts
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
263
src/views/workflow/category/index.vue
Normal file
263
src/views/workflow/category/index.vue
Normal file
@ -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>
|
381
src/views/workflow/leave/index.vue
Normal file
381
src/views/workflow/leave/index.vue
Normal file
@ -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>
|
65
src/views/workflow/model/design.vue
Normal file
65
src/views/workflow/model/design.vue
Normal file
@ -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>
|
360
src/views/workflow/model/index.vue
Normal file
360
src/views/workflow/model/index.vue
Normal file
@ -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="请输入模型KEY" 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>
|
@ -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>
|
403
src/views/workflow/processDefinition/index.vue
Normal file
403
src/views/workflow/processDefinition/index.vue
Normal file
@ -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="请输入流程定义KEY" 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>
|
||||||
|
<!-- 预览图片或xml -->
|
||||||
|
<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、bpmn 格式文件</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>
|
363
src/views/workflow/processInstance/index.vue
Normal file
363
src/views/workflow/processInstance/index.vue
Normal file
@ -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="请输入流程定义KEY" @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>
|
237
src/views/workflow/task/allTaskWaiting.vue
Normal file
237
src/views/workflow/task/allTaskWaiting.vue
Normal file
@ -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="请输入流程定义KEY" @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>
|
256
src/views/workflow/task/myDocument.vue
Normal file
256
src/views/workflow/task/myDocument.vue
Normal file
@ -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>
|
141
src/views/workflow/task/taskCopyList.vue
Normal file
141
src/views/workflow/task/taskCopyList.vue
Normal file
@ -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="请输入流程定义KEY" @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>
|
128
src/views/workflow/task/taskFinish.vue
Normal file
128
src/views/workflow/task/taskFinish.vue
Normal file
@ -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="请输入流程定义KEY" @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>
|
181
src/views/workflow/task/taskWaiting.vue
Normal file
181
src/views/workflow/task/taskWaiting.vue
Normal file
@ -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="请输入流程定义KEY" @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>
|
@ -65,7 +65,24 @@ export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => {
|
|||||||
'echarts',
|
'echarts',
|
||||||
'vue-i18n',
|
'vue-i18n',
|
||||||
'@vueup/vue-quill',
|
'@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/style/css',
|
||||||
'element-plus/es/components/form-item/style/css',
|
'element-plus/es/components/form-item/style/css',
|
||||||
'element-plus/es/components/button/style/css',
|
'element-plus/es/components/button/style/css',
|
||||||
|
Reference in New Issue
Block a user