From 0108df13340210bebd5ad6c693fc1a6bd1b061ff Mon Sep 17 00:00:00 2001
From: 疯狂的狮子Li <15040126243@163.com>
Date: 星期二, 05 三月 2024 22:57:51 +0800
Subject: [PATCH] !88 合并flowable工作流功能 * merge 合并dev * add 添加抄送查询 * add 添加我的已办 * add 添加抄送 * add 添加附件下载 * update 优化类型 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * fix 修复 流程设计器打包部署报错问题 * add 添加审批附件上传 * update 修复固定值未在xml显示问题 * update 修复跳转条件回显Object问题 * update 优化面板文件名称 * update 调整key * remove 移除旧设计器,添加xml保存 * update 增加任务面板提示 * update 优化xml预览和svg预览 * update 优化xml预览和svg预览 * update 优化模型设计 * update 优化设计器 * update 优化设计器 * update 优化设计器 * update 删除旧设计器 * update 删除console.log * add 添加模型接口 * update 优化预览xml和svg样式被修改问题 * update 优化属性面板,增加展开动画 * update 去除开发模式 * update 优化任务栏样式 * update 优化图标渲染样式 * update 增加BpmnFactory类型 * update 增加BpmnFactory * update 移除users和group * update 移除无用类型 * update 优化页面类型 * update 去除多余属性 * update 完善流程线 * update 增加复杂网关 * update 完善流程 * update 完善网关 * update 优化网关汉化 * update 优化过期时间选择 * update 支持多实例 * update 增加类容提示 * update 支持选择组 * update 新增角色api * update 优化roleSelect 选中未确定,再次打开还保留选中的问题 * update 优化userSelect 选中未确定,再次打开还保留选中的问题 * update 优化userSelect 选中未确定,再次打开还保留选中的问题 * update 去掉modeler store多余属性 bpmnModel * update 优化属性面板,当面板未选中时默认展示流程面板 * update 优化TaskPanel类型,去掉roles属性 * update 优化用户api * update 优化用户选择器 * update 优化执行监听器 * update 优化任务监听器 * update 优化usePanel方法 * update 选人优化 * update 增加扩展节点信息 * update 增加usePanel默认方法 * update 去除处理事件 * update 扩展flowable  userinfo属性 * update 全局modeler 改为非响应式 * update 增加hooks方法 * update 修改命名 * update 修改面板formData来源 * update 重写用户任务面板选择逻辑 * update 重写用户任务面板选择逻辑 * update 修改用户选择组件获取数据逻辑 * update 修改枚举类型 * update 修改默认配置列 * update 增加修改节点方法 * update 调整预览窗口大小 * update 优化用户选择组件 返回值 * update 优化用户选择组件 * update 新增通过ids 获取用户信息 * update 重写task面板选人 未完成 * update 升级用户选择 支持多选配置 * update 升级bpmnjs依赖版本 * update 增加useDialog类型 * update 调整全局样式 * update 代码高亮设置 * update 优化领用,归还加载 * update 增加选择角色 * update  新增角色选择组件 * update 新增过期时间选择组件 * update 调整任务面板样式 * update  调整全局dialog header 增加分割线 * update 代码高亮设置 * update  调整面板位置 * update 封装用户选择组件 * update 移除所有的节点描述 * update 删除分类 * update  调整面板位置 * update  修改命名,增加自定义渲染 * update  修改命名,增加自定义渲染 * update 增加 Element类型定义 * update 调整样式 * update 移除bpmn panel依赖,升级bpmn.js依赖到最新,修改汉化包 * update 调整类型声明文件 * update 调整类型声明文件 * update 优化面板工具 * update 优化面板工具 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * update 优化面板工具 * Merge branch 'future/flowable' of https://gitee.com/JavaLionLi/plus-ui… * add 添加修改办理人 * update 优化面板工具 * update 初始化流程数据 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * add 流程设计面板 * update 调整初始化xml * add 任务面板 * add 新增bpmn.js * update 优化request请求类判断请求头方式 * update 流程定义预览 优化 * update 流程定义预览 优化 * update 去掉console.log * update 优化工作流代码 * fix 修复待办任务 重置查询条件失效问题 * add 增加待办任务 接口类型,优化页面 * add 增加vite 启动预编译css * fix 修复i18n无感刷新问题 * Merge branch 'dev' into future/flowable * update 调整选择请假事件 * Merge branch 'dev' into future/flowable * 同步dev代码 * Merge branch 'dev' into future/flowable * 合并dev * remove 设计器无用代码 调整请假查询 * update 调整请假申请 * update 移动请假表单包结构,调整设计器选择引用表单请求错误 * remove 移除动态表单 * update 调整流程办理 * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * 合并 * update 调整请假申请流程提交 * update 修改业务单据流程提交 * update 调整业务单据流程提交 * 优化代码 * 优化工作流代码缩进 * 项目格式化配置修改 * 调整代码缩进 * Merge branch 'ts' into future/flowable * add 添加动态表单提交流程 * add 添加动态表单单据 * add 新增流程定义与表单关联 * update 调整点击左侧部门查询人员,部门刷新问题 * update 调整按钮图标 * 调整错别字 * update 调整流程定义图片预览 * add 添加流程实例迁移版本 * fix 修复我的单据无法提交问题 * Merge branch 'ts' into future/flowable * remove 还原代码后端解决 * update 流程设计器中分配发起人变量错误,先移除 * fix 修复设计器无法编辑问题 * update 调整设计器请求头 * Merge branch 'ts' into future/flowable * Merge branch 'ts' into future/flowable * add 添加流程定义历史列表 * update 审批记录v2改为v3 * update 调整请假必填项 * add 添加请假申请示例,添加流程定义文件部署 * update 移除流程表单 formConfig 属性,表单配置信息都放一起便于使用。 * add 添加任务加签,减签 * update 调整流程作废 * update 优化流程状态 * add 添加任务驳回 * add 添加查询当前租户所有待办,已办任务 * add 增加审批意见 * add 添加流程办理弹窗确认组件 * add 添加任务作废理由 * update 调整流程实例,流程定义检索 * add 添加我的单据页面 * add 添加任务归还认领 * add 添加流程实例,流程定义分类查询 * add 添加模型分类查询 * add 添加流程分类 * Merge remote-tracking branch 'origin/future/flowable' into future/flowable * add 添加流程表单管理页面 * add 集成vForm动态表单组件 * update xml调整超出滚动 * add 添加已办列表 * add 添加单据状态 * update 优化流程实例删除 * fix 修复流程实例查询挂起状态错误 * update 调整流程实例挂起激活状态 * add 添加流程实例列表 * update 调整流程定义弹窗提示 * add 添加流程定义列表,添加流程图,xml预览,添加简单流程启动,办理 * 调整审批记录悬浮逻辑 * 删除无用代码 * add 添加节点悬浮信息 * 调整流程预览 * 调整流程追踪 * add 添加审批记录 * add 模型设计的types * update 修改排版 * add lang=ts * update 修改ele的废弃api * fix调整审批记录图片不显示问题 * add 添加任务待办,流程图 * 调整设计器关闭 * add 添加待办 * remove 删除无用代码 * update types * 添加模型token验证 * 隐藏设计器验证按钮,隐藏表单,案例,应用程序等 * 添加模型部署 * 添加画图接口token,优化画图接口 * 添加工作流模型新增,修改,查询,删除,画图工具

---
 src/views/workflow/category/index.vue                                          |  263 +
 src/assets/icons/svg/caret-back.svg                                            |    1 
 src/components/BpmnDesign/panel/property/ExecutionListener.vue                 |  305 +
 src/types/bpmn/index.d.ts                                                      |   15 
 src/api/workflow/workflowUser/index.ts                                         |   53 
 src/directive/common/copyText.ts                                               |    3 
 src/views/workflow/model/design.vue                                            |   65 
 src/enums/bpmn/IndexEnums.ts                                                   |   17 
 src/components/BpmnDesign/assets/showConfig.ts                                 |   50 
 src/views/workflow/processInstance/index.vue                                   |  363 ++
 src/components/BpmnDesign/index.vue                                            |  409 ++
 src/components/BpmnDesign/panel/SequenceFlowPanel.vue                          |   94 
 src/types/bpmn/editor/global.d.ts                                              |   13 
 src/components/BpmnDesign/panel/ProcessPanel.vue                               |   71 
 src/components/BpmnDesign/panel/property/DueDate.vue                           |  252 +
 src/components/BpmnDesign/assets/moddle/flowable.ts                            | 1250 +++++++
 src/components/BpmnDesign/panel/TaskPanel.vue                                  |  467 ++
 src/components/Process/approvalRecord.vue                                      |  162 +
 src/views/workflow/processDefinition/index.vue                                 |  403 ++
 src/components/BpmnDesign/panel/property/ListenerParam.vue                     |  121 
 src/components/BpmnDesign/panel/property/TaskListener.vue                      |  307 +
 src/views/workflow/leave/index.vue                                             |  381 ++
 src/views/workflow/task/myDocument.vue                                         |  256 +
 src/api/workflow/model/types.ts                                                |   66 
 src/api/workflow/task/types.ts                                                 |   39 
 src/components/BpmnDesign/hooks/usePanel.ts                                    |  132 
 src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts       |  119 
 src/components/BpmnDesign/assets/defaultXML.ts                                 |   23 
 src/views/workflow/model/index.vue                                             |  360 ++
 src/components/BpmnDesign/assets/style/index.scss                              |  230 +
 src/components/BpmnDesign/panel/index.vue                                      |  104 
 src/store/modules/modeler.ts                                                   |   76 
 src/components/BpmnDesign/assets/module/index.ts                               |   17 
 src/types/bpmn/moddle.d.ts                                                     |   37 
 src/components/Process/submitVerify.vue                                        |  165 +
 src/api/workflow/leave/types.ts                                                |   22 
 src/components/BpmnDesign/assets/lang/zh.ts                                    |  118 
 src/api/workflow/category/types.ts                                             |   67 
 src/api/workflow/processDefinition/types.ts                                    |   22 
 src/api/workflow/processInstance/index.ts                                      |  115 
 src/api/workflow/model/index.ts                                                |   91 
 src/hooks/useDialog.ts                                                         |    2 
 src/api/workflow/task/index.ts                                                 |  181 +
 src/views/workflow/task/allTaskWaiting.vue                                     |  237 +
 src/views/workflow/task/taskFinish.vue                                         |  128 
 vite.config.ts                                                                 |   17 
 src/api/workflow/processInstance/types.ts                                      |   27 
 src/api/workflow/category/index.ts                                             |   63 
 src/api/workflow/processDefinition/index.ts                                    |  123 
 src/components/BpmnDesign/assets/module/Translate/index.ts                     |   15 
 src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts |  138 
 src/components/Process/multiInstance-user.vue                                  |  362 ++
 src/components/Process/sys-user.vue                                            |  301 +
 src/assets/styles/element-ui.scss                                              |   19 
 src/views/workflow/task/taskCopyList.vue                                       |  141 
 src/components/BpmnDesign/hooks/useParseElement.ts                             |   34 
 src/api/system/user/index.ts                                                   |   12 
 src/components/BpmnDesign/panel/StartEndPanel.vue                              |   40 
 src/components/UserSelect/index.vue                                            |    2 
 src/main.ts                                                                    |    7 
 src/api/workflow/leave/index.ts                                                |   63 
 src/components/BpmnDesign/panel/GatewayPanel.vue                               |   68 
 src/views/workflow/task/taskWaiting.vue                                        |  181 +
 src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts             |   56 
 src/types/bpmn/panel.d.ts                                                      |   75 
 src/components/RoleSelect/index.vue                                            |    2 
 package.json                                                                   |   19 
 src/api/system/role/index.ts                                                   |   16 
 src/views/workflow/processDefinition/components/processPreview.vue             |   57 
 src/assets/icons/svg/caret-forward.svg                                         |    1 
 70 files changed, 9,502 insertions(+), 9 deletions(-)

diff --git a/package.json b/package.json
index 7a47e4b..751bd6f 100644
--- a/package.json
+++ b/package.json
@@ -20,23 +20,33 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "2.3.1",
+    "@highlightjs/vue-plugin": "2.1.0",
+    "@lezer/common": "1.2.1",
     "@vueup/vue-quill": "1.2.0",
     "@vueuse/core": "10.7.2",
     "animate.css": "4.1.1",
     "await-to-js": "3.0.0",
     "axios": "1.6.5",
+    "bpmn-js": "16.4.0",
+    "camunda-bpmn-js-behaviors": "1.2.2",
+    "camunda-bpmn-moddle": "7.0.1",
     "crypto-js": "4.2.0",
+    "diagram-js": "12.3.0",
+    "didi": "9.0.2",
     "echarts": "5.4.3",
     "element-plus": "2.4.4",
     "file-saver": "2.0.5",
     "fuse.js": "7.0.0",
+    "highlight.js": "11.9.0",
     "image-conversion": "^2.1.1",
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",
+    "moddle": "6.2.3",
     "nprogress": "0.2.0",
     "path-browserify": "1.0.1",
     "path-to-regexp": "6.2.1",
     "pinia": "2.1.7",
+    "preact": "10.19.3",
     "screenfull": "6.0.2",
     "vform3-builds": "3.0.10",
     "vue": "3.4.13",
@@ -44,7 +54,8 @@
     "vue-i18n": "9.9.0",
     "vue-router": "4.2.5",
     "vue-types": "5.1.1",
-    "vxe-table": "4.5.18"
+    "vxe-table": "4.5.18",
+    "zeebe-bpmn-moddle": "1.0.0"
   },
   "devDependencies": {
     "@iconify/json": "2.2.168",
@@ -60,8 +71,8 @@
     "@unocss/preset-attributify": "0.58.3",
     "@unocss/preset-icons": "0.58.3",
     "@unocss/preset-uno": "0.58.3",
-    "@vue/compiler-sfc": "3.4.13",
     "@vitejs/plugin-vue": "5.0.3",
+    "@vue/compiler-sfc": "3.4.13",
     "autoprefixer": "10.4.16",
     "eslint": "8.56.0",
     "eslint-config-prettier": "9.1.0",
@@ -82,11 +93,11 @@
     "unplugin-icons": "0.18.2",
     "unplugin-vue-components": "0.26.0",
     "unplugin-vue-setup-extend-plus": "1.0.0",
+    "vite": "5.0.11",
     "vite-plugin-compression": "0.5.1",
     "vite-plugin-svg-icons": "2.0.1",
     "vitest": "1.2.0",
     "vue-eslint-parser": "9.4.0",
-    "vue-tsc": "1.8.27",
-    "vite": "5.0.11"
+    "vue-tsc": "1.8.27"
   }
 }
diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts
index 4e8b612..fb0fcab 100644
--- a/src/api/system/role/index.ts
+++ b/src/api/system/role/index.ts
@@ -13,6 +13,17 @@
 };
 
 /**
+ * 閫氳繃roleIds鏌ヨ瑙掕壊
+ * @param roleIds
+ */
+export const optionSelect = (roleIds: (number | string)[]): AxiosPromise<RoleVO[]> => {
+  return request({
+    url: '/system/role/optionselect?roleIds=' + roleIds,
+    method: 'get'
+  });
+};
+
+/**
  * 鏌ヨ瑙掕壊璇︾粏
  */
 export const getRole = (roleId: string | number): AxiosPromise<RoleVO> => {
@@ -142,3 +153,8 @@
     method: 'get'
   });
 };
+
+export default {
+  optionSelect,
+  listRole
+};
diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts
index fdcafc2..61a58f6 100644
--- a/src/api/system/user/index.ts
+++ b/src/api/system/user/index.ts
@@ -18,6 +18,17 @@
 };
 
 /**
+ * 閫氳繃鐢ㄦ埛ids鏌ヨ鐢ㄦ埛
+ * @param userIds
+ */
+export const optionSelect = (userIds: (number | string)[]): AxiosPromise<UserVO[]> => {
+  return request({
+    url: '/system/user/optionselect?userIds=' + userIds,
+    method: 'get'
+  });
+};
+
+/**
  * 鑾峰彇鐢ㄦ埛璇︽儏
  * @param userId
  */
@@ -199,6 +210,7 @@
 export default {
   listUser,
   getUser,
+  optionSelect,
   addUser,
   updateUser,
   delUser,
diff --git a/src/api/workflow/category/index.ts b/src/api/workflow/category/index.ts
new file mode 100644
index 0000000..e9723b0
--- /dev/null
+++ b/src/api/workflow/category/index.ts
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { CategoryVO, CategoryForm, CategoryQuery } from '@/api/workflow/category/types';
+
+/**
+ * 鏌ヨ娴佺▼鍒嗙被鍒楄〃
+ * @param query
+ * @returns {*}
+ */
+
+export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => {
+  return request({
+    url: '/workflow/category/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ娴佺▼鍒嗙被璇︾粏
+ * @param id
+ */
+export const getCategory = (id: string | number): AxiosPromise<CategoryVO> => {
+  return request({
+    url: '/workflow/category/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 鏂板娴佺▼鍒嗙被
+ * @param data
+ */
+export const addCategory = (data: CategoryForm) => {
+  return request({
+    url: '/workflow/category',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 淇敼娴佺▼鍒嗙被
+ * @param data
+ */
+export const updateCategory = (data: CategoryForm) => {
+  return request({
+    url: '/workflow/category',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 鍒犻櫎娴佺▼鍒嗙被
+ * @param id
+ */
+export const delCategory = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/workflow/category/' + id,
+    method: 'delete'
+  });
+};
diff --git a/src/api/workflow/category/types.ts b/src/api/workflow/category/types.ts
new file mode 100644
index 0000000..414fa55
--- /dev/null
+++ b/src/api/workflow/category/types.ts
@@ -0,0 +1,67 @@
+export interface CategoryVO {
+  /**
+   * 涓婚敭
+   */
+  id: string;
+
+  /**
+   * 鍒嗙被鍚嶇О
+   */
+  categoryName: string;
+
+  /**
+   * 鍒嗙被缂栫爜
+   */
+  categoryCode: string;
+
+  /**
+   * 鐖剁骇id
+   */
+  parentId: string | number;
+
+  /**
+   * 鎺掑簭
+   */
+  sortNum: number;
+
+  children?: CategoryVO[];
+}
+
+export interface CategoryForm extends BaseEntity {
+  /**
+   * 涓婚敭
+   */
+  id?: string | number;
+
+  /**
+   * 鍒嗙被鍚嶇О
+   */
+  categoryName?: string;
+
+  /**
+   * 鍒嗙被缂栫爜
+   */
+  categoryCode?: string;
+
+  /**
+   * 鐖剁骇id
+   */
+  parentId?: string | number;
+
+  /**
+   * 鎺掑簭
+   */
+  sortNum?: number;
+}
+
+export interface CategoryQuery extends PageQuery {
+  /**
+   * 鍒嗙被鍚嶇О
+   */
+  categoryName?: string;
+
+  /**
+   * 鍒嗙被缂栫爜
+   */
+  categoryCode?: string;
+}
diff --git a/src/api/workflow/leave/index.ts b/src/api/workflow/leave/index.ts
new file mode 100644
index 0000000..36c6fdf
--- /dev/null
+++ b/src/api/workflow/leave/index.ts
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { LeaveVO, LeaveQuery, LeaveForm } from '@/api/workflow/leave/types';
+
+/**
+ * 鏌ヨ璇峰亣鍒楄〃
+ * @param query
+ * @returns {*}
+ */
+
+export const listLeave = (query?: LeaveQuery): AxiosPromise<LeaveVO[]> => {
+  return request({
+    url: '/demo/leave/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ璇峰亣璇︾粏
+ * @param id
+ */
+export const getLeave = (id: string | number): AxiosPromise<LeaveVO> => {
+  return request({
+    url: '/demo/leave/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 鏂板璇峰亣
+ * @param data
+ */
+export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
+  return request({
+    url: '/demo/leave',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 淇敼璇峰亣
+ * @param data
+ */
+export const updateLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
+  return request({
+    url: '/demo/leave',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 鍒犻櫎璇峰亣
+ * @param id
+ */
+export const delLeave = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/demo/leave/' + id,
+    method: 'delete'
+  });
+};
diff --git a/src/api/workflow/leave/types.ts b/src/api/workflow/leave/types.ts
new file mode 100644
index 0000000..9405257
--- /dev/null
+++ b/src/api/workflow/leave/types.ts
@@ -0,0 +1,22 @@
+export interface LeaveVO {
+  id: string | number;
+  leaveType: string;
+  startDate: string;
+  endDate: string;
+  leaveDays: number;
+  remark: string;
+}
+
+export interface LeaveForm extends BaseEntity {
+  id?: string | number;
+  leaveType?: string;
+  startDate?: string;
+  endDate?: string;
+  leaveDays?: number;
+  remark?: string;
+}
+
+export interface LeaveQuery extends PageQuery {
+  startLeaveDays?: number;
+  endLeaveDays?: number;
+}
diff --git a/src/api/workflow/model/index.ts b/src/api/workflow/model/index.ts
new file mode 100644
index 0000000..f58cdd3
--- /dev/null
+++ b/src/api/workflow/model/index.ts
@@ -0,0 +1,91 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ModelForm, ModelQuery, ModelVO } from '@/api/workflow/model/types';
+
+/**
+ * 鏌ヨ妯″瀷鍒楄〃
+ * @param query
+ * @returns {*}
+ */
+export const listModel = (query: ModelQuery): AxiosPromise<ModelVO[]> => {
+  return request({
+    url: '/workflow/model/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ妯″瀷淇℃伅
+ * @param query
+ * @returns {*}
+ */
+export const getInfo = (id: string): AxiosPromise<ModelForm> => {
+  return request({
+    url: '/workflow/model/getInfo/'+id,
+    method: 'get'
+  });
+};
+
+/**
+ * 鏂板妯″瀷
+ * @param data
+ * @returns {*}
+ */
+export const addModel = (data: ModelForm): AxiosPromise<void> => {
+  return request({
+    url: '/workflow/model/save',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 淇敼妯″瀷淇℃伅
+ * @param data
+ * @returns {*}
+ */
+export function update(data: ModelForm): AxiosPromise<void> {
+  return request({
+    url: '/workflow/model/update',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 淇敼妯″瀷淇℃伅
+ * @param data
+ * @returns {*}
+ */
+export function editModelXml(data: ModelForm): AxiosPromise<void> {
+  return request({
+    url: '/workflow/model/editModelXml',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 鎸塱d鍒犻櫎妯″瀷
+ * @returns {*}
+ * @param id 妯″瀷id
+ */
+export function delModel(id: string | string[]): AxiosPromise<void> {
+  return request({
+    url: '/workflow/model/' + id,
+    method: 'delete'
+  });
+}
+
+/**
+ * 妯″瀷閮ㄧ讲
+ * @returns {*}
+ * @param id 妯″瀷id
+ */
+export const modelDeploy = (id: string): AxiosPromise<void> => {
+  return request({
+    url: `/workflow/model/modelDeploy/${id}`,
+    method: 'post'
+  });
+};
diff --git a/src/api/workflow/model/types.ts b/src/api/workflow/model/types.ts
new file mode 100644
index 0000000..40a0faa
--- /dev/null
+++ b/src/api/workflow/model/types.ts
@@ -0,0 +1,66 @@
+export interface ModelForm {
+  id: string,
+  name: string;
+  key: string;
+  categoryCode: string;
+  xml:string,
+  svg:string,
+  description: string;
+}
+
+export interface ModelQuery extends PageQuery {
+  name?: string;
+  key?: string;
+  categoryCode?: string;
+}
+
+export interface OriginalPersistentState {
+  metaInfo: string;
+  editorSourceValueId: string;
+  createTime: string;
+  deploymentId?: string;
+  name: string;
+  tenantId: string;
+  category?: string;
+  version: number;
+  editorSourceExtraValueId?: string;
+  key: string;
+  lastUpdateTime: string;
+}
+
+export interface PersistentState {
+  metaInfo: string;
+  editorSourceValueId: string;
+  createTime: string;
+  deploymentId?: string;
+  name: string;
+  tenantId: string;
+  category?: string;
+  version: number;
+  editorSourceExtraValueId?: string;
+  key: string;
+  lastUpdateTime: string;
+}
+
+export interface ModelVO {
+  id: string;
+  revision: number;
+  originalPersistentState: OriginalPersistentState;
+  name: string;
+  key: string;
+  category?: string;
+  createTime: string;
+  lastUpdateTime: string;
+  version: number;
+  metaInfo: string;
+  deploymentId?: string;
+  editorSourceValueId: string;
+  editorSourceExtraValueId?: string;
+  tenantId: string;
+  persistentState: PersistentState;
+  revisionNext: number;
+  idPrefix: string;
+  inserted: boolean;
+  updated: boolean;
+  deleted: boolean;
+}
diff --git a/src/api/workflow/processDefinition/index.ts b/src/api/workflow/processDefinition/index.ts
new file mode 100644
index 0000000..f64b531
--- /dev/null
+++ b/src/api/workflow/processDefinition/index.ts
@@ -0,0 +1,123 @@
+import request from '@/utils/request';
+import { ProcessDefinitionQuery, ProcessDefinitionVO, ProcessDefinitionXmlVO } from '@/api/workflow/processDefinition/types';
+import { AxiosPromise } from 'axios';
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+
+/**
+ * 鑾峰彇娴佺▼瀹氫箟鍒楄〃
+ * @param query 娴佺▼瀹炰緥id
+ * @returns
+ */
+export const listProcessDefinition = (query: ProcessDefinitionQuery): AxiosPromise<ProcessDefinitionVO[]> => {
+  return request({
+    url: `/workflow/processDefinition/list`,
+    method: 'get',
+    params: query
+  });
+};
+/**
+ * 鎸夌収娴佺▼瀹氫箟key鑾峰彇娴佺▼瀹氫箟
+ * @param processInstanceId 娴佺▼瀹炰緥id
+ * @returns
+ */
+export const getProcessDefinitionListByKey = (key: string) => {
+  return request({
+    url: `/workflow/processDefinition/getProcessDefinitionListByKey/${key}`,
+    method: 'get'
+  });
+};
+
+/**
+ * 閫氳繃娴佺▼瀹氫箟id鑾峰彇娴佺▼鍥�
+ */
+export const processDefinitionImage = (processDefinitionId: string): AxiosPromise<any> => {
+  return request({
+    url: `/workflow/processDefinition/processDefinitionImage/${processDefinitionId}` + '?t' + Math.random(),
+    method: 'get'
+  });
+};
+
+/**
+ * 閫氳繃娴佺▼瀹氫箟id鑾峰彇xml
+ * @param processDefinitionId 娴佺▼瀹氫箟id
+ * @returns
+ */
+export const processDefinitionXml = (processDefinitionId: string): AxiosPromise<ProcessDefinitionXmlVO> => {
+  return request({
+    url: `/workflow/processDefinition/processDefinitionXml/${processDefinitionId}`,
+    method: 'get'
+  });
+};
+
+/**
+ * 鍒犻櫎娴佺▼瀹氫箟
+ * @param processDefinitionId 娴佺▼瀹氫箟id
+ * @param deploymentId 閮ㄧ讲id
+ * @returns
+ */
+export const deleteProcessDefinition = (deploymentId: string, processDefinitionId: string) => {
+  return request({
+    url: `/workflow/processDefinition/${deploymentId}/${processDefinitionId}`,
+    method: 'delete'
+  });
+};
+
+/**
+ * 鎸傝捣/婵�娲�
+ * @param processDefinitionId 娴佺▼瀹氫箟id
+ * @returns
+ */
+export const updateProcessDefState = (processDefinitionId: string) => {
+  return request({
+    url: `/workflow/processDefinition/updateProcessDefState/${processDefinitionId}`,
+    method: 'put'
+  });
+};
+
+/**
+ * 娴佺▼瀹氫箟杞崲涓烘ā鍨�
+ * @param processDefinitionId 娴佺▼瀹氫箟id
+ * @returns
+ */
+export const convertToModel = (processDefinitionId: string) => {
+  return request({
+    url: `/workflow/processDefinition/convertToModel/${processDefinitionId}`,
+    method: 'put'
+  });
+};
+
+/**
+ * 閫氳繃zip鎴杧ml閮ㄧ讲娴佺▼瀹氫箟
+ * @returns
+ */
+export function deployProcessFile(data: any) {
+  return request({
+    url: '/workflow/processDefinition/deployByFile',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 杩佺Щ娴佺▼
+ * @param currentProcessDefinitionId
+ * @param fromProcessDefinitionId
+ * @returns
+ */
+export const migrationProcessDefinition = (currentProcessDefinitionId: string, fromProcessDefinitionId: string) => {
+  return request({
+    url: `/workflow/processDefinition/migrationProcessDefinition/${currentProcessDefinitionId}/${fromProcessDefinitionId}`,
+    method: 'put'
+  });
+};
+
+/**
+ * 鏌ヨ娴佺▼瀹氫箟鍒楄〃
+ * @returns
+ */
+export const getProcessDefinitionList = () => {
+  return request({
+    url: `/workflow/processDefinition/getProcessDefinitionList`,
+    method: 'get'
+  });
+};
diff --git a/src/api/workflow/processDefinition/types.ts b/src/api/workflow/processDefinition/types.ts
new file mode 100644
index 0000000..8987b33
--- /dev/null
+++ b/src/api/workflow/processDefinition/types.ts
@@ -0,0 +1,22 @@
+export interface ProcessDefinitionQuery extends PageQuery {
+  key?: string;
+  name?: string;
+  categoryCode?: string;
+}
+
+export interface ProcessDefinitionVO extends BaseEntity {
+  id: string;
+  name: string;
+  key: string;
+  version: number;
+  suspensionState: number;
+  resourceName: string;
+  diagramResourceName: string;
+  deploymentId: string;
+  deploymentTime: string;
+}
+
+export interface ProcessDefinitionXmlVO {
+  xml: string[];
+  xmlStr: string;
+}
diff --git a/src/api/workflow/processInstance/index.ts b/src/api/workflow/processInstance/index.ts
new file mode 100644
index 0000000..295fc0d
--- /dev/null
+++ b/src/api/workflow/processInstance/index.ts
@@ -0,0 +1,115 @@
+import request from '@/utils/request';
+import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types';
+import { AxiosPromise } from 'axios';
+import { string } from 'vue-types';
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+
+/**
+ * 鏌ヨ杩愯涓疄渚嬪垪琛�
+ * @param query
+ * @returns {*}
+ */
+export const getProcessInstanceRunningByPage = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => {
+  return request({
+    url: '/workflow/processInstance/getProcessInstanceRunningByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ宸插畬鎴愬疄渚嬪垪琛�
+ * @param query
+ * @returns {*}
+ */
+export const getProcessInstanceFinishByPage = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => {
+  return request({
+    url: '/workflow/processInstance/getProcessInstanceFinishByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥�
+ */
+export const getHistoryProcessImage = (processInstanceId: string) => {
+  return request({
+    url: `/workflow/processInstance/getHistoryProcessImage/${processInstanceId}` + '?t' + Math.random(),
+    method: 'get'
+  });
+};
+
+/**
+ * 鑾峰彇瀹℃壒璁板綍
+ * @param processInstanceId 娴佺▼瀹炰緥id
+ * @returns
+ */
+export const getHistoryRecord = (processInstanceId: string) => {
+  return request({
+    url: `/workflow/processInstance/getHistoryRecord/${processInstanceId}`,
+    method: 'get'
+  });
+};
+
+/**
+ * 浣滃簾
+ * @param data 鍙傛暟
+ * @returns
+ */
+export const deleteRuntimeProcessInst = (data: object) => {
+  return request({
+    url: `/workflow/processInstance/deleteRuntimeProcessInst`,
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+ * @param processInstanceId 娴佺▼瀹炰緥id
+ * @returns
+ */
+export const deleteRuntimeProcessAndHisInst = (processInstanceId: string | string[]) => {
+  return request({
+    url: `/workflow/processInstance/deleteRuntimeProcessAndHisInst/${processInstanceId}`,
+    method: 'delete'
+  });
+};
+
+/**
+ * 宸插畬鎴愮殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+ * @param processInstanceId 娴佺▼瀹炰緥id
+ * @returns
+ */
+export const deleteFinishProcessAndHisInst = (processInstanceId: string | string[]) => {
+  return request({
+    url: `/workflow/processInstance/deleteFinishProcessAndHisInst/${processInstanceId}`,
+    method: 'delete'
+  });
+};
+
+/**
+ * 鍒嗛〉鏌ヨ褰撳墠鐧诲綍浜哄崟鎹�
+ * @param query
+ * @returns {*}
+ */
+export const getCurrentSubmitByPage = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => {
+  return request({
+    url: '/workflow/processInstance/getCurrentSubmitByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鎾ら攢娴佺▼
+ * @param processInstanceId 娴佺▼瀹炰緥id
+ * @returns
+ */
+export const cancelProcessApply = (processInstanceId: string) => {
+  return request({
+    url: `/workflow/processInstance/cancelProcessApply/${processInstanceId}`,
+    method: 'post'
+  });
+};
diff --git a/src/api/workflow/processInstance/types.ts b/src/api/workflow/processInstance/types.ts
new file mode 100644
index 0000000..99d0511
--- /dev/null
+++ b/src/api/workflow/processInstance/types.ts
@@ -0,0 +1,27 @@
+import { TaskVO } from '@/api/workflow/task/types';
+
+export interface ProcessInstanceQuery extends PageQuery {
+  categoryCode?: string;
+  name?: string;
+  key?: string;
+  startUserId?: string;
+  businessKey?: string;
+}
+
+export interface ProcessInstanceVO extends BaseEntity {
+  id: string;
+  processDefinitionId: string;
+  processDefinitionName: string;
+  processDefinitionKey: string;
+  processDefinitionVersion: string;
+  deploymentId: string;
+  businessKey: string;
+  isSuspended?: any;
+  tenantId: string;
+  startTime: string;
+  endTime?: string;
+  startUserId: string;
+  businessStatus: string;
+  businessStatusName: string;
+  taskVoList: TaskVO[];
+}
diff --git a/src/api/workflow/task/index.ts b/src/api/workflow/task/index.ts
new file mode 100644
index 0000000..7be76f7
--- /dev/null
+++ b/src/api/workflow/task/index.ts
@@ -0,0 +1,181 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
+/**
+ * 鏌ヨ寰呭姙鍒楄〃
+ * @param query
+ * @returns {*}
+ */
+export const getTaskWaitByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
+  return request({
+    url: '/workflow/task/getTaskWaitByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ宸插姙鍒楄〃
+ * @param query
+ * @returns {*}
+ */
+export const getTaskFinishByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
+  return request({
+    url: '/workflow/task/getTaskFinishByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫佸垪琛�
+ * @param query
+ * @returns {*}
+ */
+export const getTaskCopyByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
+  return request({
+    url: '/workflow/task/getTaskCopyByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 褰撳墠绉熸埛鎵�鏈夊緟鍔炰换鍔�
+ * @param query
+ * @returns {*}
+ */
+export const getAllTaskWaitByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
+  return request({
+    url: '/workflow/task/getAllTaskWaitByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 褰撳墠绉熸埛鎵�鏈夊凡鍔炰换鍔�
+ * @param query
+ * @returns {*}
+ */
+export const getAllTaskFinishByPage = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
+  return request({
+    url: '/workflow/task/getAllTaskFinishByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鍚姩娴佺▼
+ * @param data
+ * @returns {*}
+ */
+export const startWorkFlow = (data: object) => {
+  return request({
+    url: '/workflow/task/startWorkFlow',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 鍔炵悊娴佺▼
+ * @param data
+ * @returns {*}
+ */
+export const completeTask = (data: object) => {
+  return request({
+    url: '/workflow/task/completeTask',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 璁ら浠诲姟
+ * @param taskId
+ * @returns {*}
+ */
+export const claim = (taskId: string) => {
+  return request({
+    url: '/workflow/task/claim/' + taskId,
+    method: 'post'
+  });
+};
+
+/**
+ * 褰掕繕浠诲姟
+ * @param taskId
+ * @returns {*}
+ */
+export const returnTask = (taskId: string) => {
+  return request({
+    url: '/workflow/task/returnTask/' + taskId,
+    method: 'post'
+  });
+};
+
+/**
+ * 浠诲姟椹冲洖
+ * @param taskId
+ * @returns {*}
+ */
+export const backProcess = (data: object) => {
+  return request({
+    url: '/workflow/task/backProcess',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 鑾峰彇娴佺▼鐘舵��
+ * @param taskId
+ * @returns
+ */
+export const getBusinessStatus = (taskId: string) => {
+  return request({
+    url: '/workflow/task/getBusinessStatus/' + taskId,
+    method: 'get'
+  });
+};
+
+/**
+ * 鍔犵
+ * @param data
+ * @returns
+ */
+export const addMultiInstanceExecution = (data: object) => {
+  return request({
+    url: '/workflow/task/addMultiInstanceExecution',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 鍑忕
+ * @param data
+ * @returns
+ */
+export const deleteMultiInstanceExecution = (data: object) => {
+  return request({
+    url: '/workflow/task/deleteMultiInstanceExecution',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 淇敼浠诲姟鍔炵悊浜�
+ * @param taskIds 
+ * @param userId 
+ * @returns 
+ */
+export const updateAssignee = (taskIds: Array<string>,userId: string) => {
+  return request({
+    url: `/workflow/task/updateAssignee/${taskIds}/${userId}`,
+    method: 'put'
+  });
+};
diff --git a/src/api/workflow/task/types.ts b/src/api/workflow/task/types.ts
new file mode 100644
index 0000000..11cc48b
--- /dev/null
+++ b/src/api/workflow/task/types.ts
@@ -0,0 +1,39 @@
+export interface TaskQuery extends PageQuery {
+  name?: string;
+  processDefinitionKey?: string;
+  processDefinitionName?: string;
+}
+
+export interface ParticipantVo {
+  groupIds?: string[] | number[];
+  candidate: string[] | number[];
+  candidateName: string[];
+  claim: boolean;
+}
+
+export interface TaskVO extends BaseEntity {
+  id: string;
+  name: string;
+  description?: string;
+  priority: number;
+  owner?: string;
+  assignee?: string | number;
+  assigneeName?: string;
+  processInstanceId: string;
+  executionId: string;
+  taskDefinitionId?: any;
+  processDefinitionId: string;
+  endTime?: string;
+  taskDefinitionKey: string;
+  dueDate?: string;
+  category?: any;
+  parentTaskId?: any;
+  tenantId: string;
+  claimTime?: string;
+  businessStatus: string;
+  businessStatusName: string;
+  processDefinitionName: string;
+  processDefinitionKey: string;
+  participantVo: ParticipantVo;
+  multiInstance: boolean;
+}
diff --git a/src/api/workflow/workflowUser/index.ts b/src/api/workflow/workflowUser/index.ts
new file mode 100644
index 0000000..e3ed8d9
--- /dev/null
+++ b/src/api/workflow/workflowUser/index.ts
@@ -0,0 +1,53 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { UserVO } from '@/api/system/user/types';
+
+/**
+ * 鍒嗛〉鏌ヨ宸ヤ綔娴侀�夋嫨鍔犵浜哄憳
+ * @param query
+ * @returns {*}
+ */
+export const getWorkflowAddMultiListByPage = (query: object) => {
+  return request({
+    url: '/workflow/user/getWorkflowAddMultiListByPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 鏌ヨ宸ヤ綔娴侀�夋嫨鍑忕浜哄憳
+ * @param query
+ * @returns {*}
+ */
+export const getWorkflowDeleteMultiInstanceList = (taskId: string) => {
+  return request({
+    url: '/workflow/user/getWorkflowDeleteMultiInstanceList/' + taskId,
+    method: 'get'
+  });
+};
+
+/**
+ * 鎸夌収鐢ㄦ埛id鏌ヨ鐢ㄦ埛
+ * @param userIdList
+ * @returns {*}
+ */
+export const getUserListByIds = (userIdList: any[]): AxiosPromise<UserVO[]> => {
+  return request({
+    url: '/workflow/user/getUserListByIds/' + userIdList,
+    method: 'get'
+  });
+};
+
+/**
+ * 鍒嗛〉鏌ヨ鐢ㄦ埛
+ * @param query
+ * @returns {*}
+ */
+export const getUserListByPage = (query: object) => {
+  return request({
+    url: '/workflow/user/getUserListByPage',
+    method: 'get',
+    params: query
+  });
+};
diff --git a/src/assets/icons/svg/caret-back.svg b/src/assets/icons/svg/caret-back.svg
new file mode 100644
index 0000000..9bae722
--- /dev/null
+++ b/src/assets/icons/svg/caret-back.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M321.94 98L158.82 237.78a24 24 0 000 36.44L321.94 414c15.57 13.34 39.62 2.28 39.62-18.22v-279.6c0-20.5-24.05-31.56-39.62-18.18z"/></svg>
\ No newline at end of file
diff --git a/src/assets/icons/svg/caret-forward.svg b/src/assets/icons/svg/caret-forward.svg
new file mode 100644
index 0000000..1ec3f7d
--- /dev/null
+++ b/src/assets/icons/svg/caret-forward.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M190.06 414l163.12-139.78a24 24 0 000-36.44L190.06 98c-15.57-13.34-39.62-2.28-39.62 18.22v279.6c0 20.5 24.05 31.56 39.62 18.18z"/></svg>
\ No newline at end of file
diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss
index 43c093c..87326ee 100644
--- a/src/assets/styles/element-ui.scss
+++ b/src/assets/styles/element-ui.scss
@@ -1,4 +1,15 @@
-// cover some element-ui styles
+
+.el-collapse {
+  .collapse__title {
+    font-weight: 600;
+    padding: 0 8px;
+    font-size: 1.2em;
+    line-height: 1.1em;
+  }
+  .el-collapse-item__content {
+    padding: 0 8px;
+  }
+}
 
 .el-divider--horizontal {
   margin-bottom: 10px;
@@ -68,6 +79,12 @@
       .el-dialog__body {
         padding: 15px !important;
       }
+      .el-dialog__header {
+        padding: 16px 16px 8px 16px;
+        box-sizing: border-box;
+        border-bottom: 1px solid #e8e8e8;
+        margin-right: 0;
+      }
     }
   }
 }
diff --git a/src/components/BpmnDesign/assets/defaultXML.ts b/src/components/BpmnDesign/assets/defaultXML.ts
new file mode 100644
index 0000000..dff0349
--- /dev/null
+++ b/src/components/BpmnDesign/assets/defaultXML.ts
@@ -0,0 +1,23 @@
+function generateRandomValue() {
+  // 鐢熸垚涓�涓殢鏈烘暟
+  const randomValue = Math.random().toString(36).slice(2, 12);
+  return `Process_${randomValue}`;
+}
+
+const cartage: string = 'default';
+export default `<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef">
+  <process id="process_${generateRandomValue()}" name="name_${generateRandomValue()}">
+    <startEvent id="startNode1" name="寮�濮�" />
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_flow">
+    <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c">
+      <bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke="">
+        <omgdc:Bounds x="240" y="200" width="30" height="30" />
+        <bpmndi:BPMNLabel>
+          <omgdc:Bounds x="242" y="237" width="23" height="14" />
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNShape>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>`;
diff --git a/src/components/BpmnDesign/assets/lang/zh.ts b/src/components/BpmnDesign/assets/lang/zh.ts
new file mode 100644
index 0000000..61921a5
--- /dev/null
+++ b/src/components/BpmnDesign/assets/lang/zh.ts
@@ -0,0 +1,118 @@
+export const NodeName = {
+  'bpmn:Process': '娴佺▼',
+  'bpmn:StartEvent': '寮�濮嬩簨浠�',
+  'bpmn:IntermediateThrowEvent': '涓棿浜嬩欢',
+  'bpmn:Task': '浠诲姟',
+  'bpmn:SendTask': '鍙戦�佷换鍔�',
+  'bpmn:ReceiveTask': '鎺ユ敹浠诲姟',
+  'bpmn:UserTask': '鐢ㄦ埛浠诲姟',
+  'bpmn:ManualTask': '鎵嬪伐浠诲姟',
+  'bpmn:BusinessRuleTask': '涓氬姟瑙勫垯浠诲姟',
+  'bpmn:ServiceTask': '鏈嶅姟浠诲姟',
+  'bpmn:ScriptTask': '鑴氭湰浠诲姟',
+  'bpmn:EndEvent': '缁撴潫浜嬩欢',
+  'bpmn:SequenceFlow': '娴佺▼绾�',
+  'bpmn:ExclusiveGateway': '浜掓枼缃戝叧',
+  'bpmn:ParallelGateway': '骞惰缃戝叧',
+  'bpmn:InclusiveGateway': '鐩稿缃戝叧',
+  'bpmn:ComplexGateway': '澶嶆潅缃戝叧',
+  'bpmn:EventBasedGateway': '浜嬩欢缃戝叧'
+};
+
+export default {
+  'Activate hand tool': '鍚姩鎵嬪姩宸ュ叿',
+  'Activate lasso tool': '鍚姩 Lasso 宸ュ叿',
+  'Activate create/remove space tool': '鍚姩鍒涘缓/鍒犻櫎绌洪棿宸ュ叿',
+  'Activate global connect tool': '鍚姩鍏ㄥ眬杩炴帴宸ュ叿',
+  'Ad-hoc': 'Ad-hoc',
+  'Add lane above': '鍦ㄤ笂鏂规坊鍔犳吵閬�',
+  'Add lane below': '鍦ㄤ笅鏂规坊鍔犳吵閬�',
+  'Business rule task': '瑙勫垯浠诲姟',
+  'Call activity': '寮曠敤娴佺▼',
+  'Compensation end event': '缁撴潫琛ュ伩浜嬩欢',
+  'Compensation intermediate throw event': '涓棿琛ュ伩鎶涘嚭浜嬩欢',
+  'Complex gateway': '澶嶆潅缃戝叧',
+  'Conditional intermediate catch event': '涓棿鏉′欢鎹曡幏浜嬩欢',
+  'Conditional start event (non-interrupting)': '鏉′欢鍚姩浜嬩欢 (闈炰腑鏂�)',
+  'Conditional start event': '鏉′欢鍚姩浜嬩欢',
+  'Connect using association': '鏂囨湰鍏宠仈',
+  'Connect using sequence/message flow or association': '娑堟伅鍏宠仈',
+  'Change element': '鏇存敼鍏冪礌',
+  'Change type': '鏇存敼绫诲瀷',
+  'Create data object reference': '鍒涘缓鏁版嵁瀵硅薄寮曠敤',
+  'Create data store reference': '鍒涘缓鏁版嵁瀛樺偍寮曠敤',
+  'Create expanded sub-process': '鍒涘缓鍙姌鍙犲瓙娴佺▼',
+  'Create pool/participant': '鍒涘缓姹�/鍙備笌鑰�',
+  'Collection': '闆嗗悎',
+  'Connect using data input association': '鏁版嵁杈撳叆鍏宠仈',
+  'Data store reference': '鏁版嵁瀛樺偍寮曠敤',
+  'Data object reference': '鏁版嵁瀵硅薄寮曠敤',
+  'Divide into two lanes': '鍒嗘垚涓や釜娉抽亾',
+  'Divide into three lanes': '鍒嗘垚涓変釜娉抽亾',
+  'End event': '缁撴潫浜嬩欢',
+  'Error end event': '缁撴潫閿欒浜嬩欢',
+  'Escalation end event': '缁撴潫鍗囩骇浜嬩欢',
+  'Escalation intermediate throw event': '涓棿鍗囩骇鎶涘嚭浜嬩欢',
+  'Event sub-process': '浜嬩欢瀛愭祦绋�',
+  'Event-based gateway': '浜嬩欢缃戝叧',
+  'Exclusive gateway': '浜掓枼缃戝叧',
+  'Empty pool/participant (removes content)': '娓呯┖姹�/鍙備笌鑰� (鍒犻櫎鍐呭)',
+  'Inclusive gateway': '鐩稿缃戝叧',
+  'Intermediate throw event': '涓棿鎶涘嚭浜嬩欢',
+  'Loop': '寰幆',
+  'Link intermediate catch event': '涓棿閾炬帴鎹曡幏浜嬩欢',
+  'Link intermediate throw event': '涓棿閾炬帴鎶涘嚭浜嬩欢',
+  'Manual task': '鎵嬪姩浠诲姟',
+  'Message end event': '缁撴潫娑堟伅浜嬩欢',
+  'Message intermediate catch event': '涓棿娑堟伅鎹曡幏浜嬩欢',
+  'Message intermediate throw event': '涓棿娑堟伅鎶涘嚭浜嬩欢',
+  'Message start event': '娑堟伅鍚姩浜嬩欢',
+  'Parallel gateway': '骞惰缃戝叧',
+  'Parallel multi-instance': '骞惰澶氬疄渚�',
+  'Participant multiplicity': '鍙備笌鑰呭閲嶆��',
+  'Receive task': '鎺ュ彈浠诲姟',
+  'Remove': '绉婚櫎',
+  'Script task': '鑴氭湰浠诲姟',
+  'Send task': '鍙戦�佷换鍔�',
+  'Sequential multi-instance': '涓茶澶氬疄渚�',
+  'Service task': '鏈嶅姟浠诲姟',
+  'Signal end event': '缁撴潫淇″彿浜嬩欢',
+  'Signal intermediate catch event': '涓棿淇″彿鎹曡幏浜嬩欢',
+  'Signal intermediate throw event': '涓棿淇″彿鎶涘嚭浜嬩欢',
+  'Signal start event (non-interrupting)': '淇″彿鍚姩浜嬩欢 (闈炰腑鏂�)',
+  'Signal start event': '淇″彿鍚姩浜嬩欢',
+  'Start event': '寮�濮嬩簨浠�',
+  'Sub-process (collapsed)': '鍙姌鍙犲瓙娴佺▼',
+  'Sub-process (expanded)': '鍙睍寮�瀛愭祦绋�',
+  'Sub rocess': '瀛愭祦绋�',
+  'Task': '浠诲姟',
+  'Transaction': '浜嬪姟',
+  'Terminate end event': '缁堟杈圭晫浜嬩欢',
+  'Timer intermediate catch event': '涓棿瀹氭椂鎹曡幏浜嬩欢',
+  'Timer start event (non-interrupting)': '瀹氭椂鍚姩浜嬩欢 (闈炰腑鏂�)',
+  'Timer start event': '瀹氭椂鍚姩浜嬩欢',
+  'User task': '鐢ㄦ埛浠诲姟',
+  'Create start event': '鍒涘缓寮�濮嬩簨浠�',
+  'Create gateway': '鍒涘缓缃戝叧',
+  'Create intermediate/boundary event': '鍒涘缓涓棿/杈圭晫浜嬩欢',
+  'Create end event': '鍒涘缓缁撴潫浜嬩欢',
+  'Create group': '鍒涘缓缁�',
+  'Create startEvent': '寮�濮嬭妭鐐�',
+  'Create endEvent': '缁撴潫鑺傜偣',
+  'Create exclusiveGateway': '浜掓枼缃戝叧',
+  'Create parallelGateway': '骞惰缃戝叧',
+  'Create task': '浠诲姟鑺傜偣',
+  'Create userTask': '鐢ㄦ埛浠诲姟鑺傜偣',
+  'Condition type': '鏉′欢绫诲瀷',
+  'Append end event': '杩藉姞缁撴潫浜嬩欢鑺傜偣',
+  'Append gateway': '杩藉姞缃戝叧鑺傜偣',
+  'Append task': '杩藉姞浠诲姟',
+  'Append user task': '杩藉姞鐢ㄦ埛浠诲姟鑺傜偣',
+  'Append text annotation': '杩藉姞鏂囨湰娉ㄩ噴',
+  'Append intermediate/boundary event': '杩藉姞涓棿鎴栬竟鐣屼簨浠�',
+  'Append receive task': '杩藉姞鎺ユ敹浠诲姟鑺傜偣',
+  'Append message intermediate catch event': '杩藉姞涓棿娑堟伅鎹曡幏浜嬩欢',
+  'Append timer intermediate catch event': '杩藉姞涓棿瀹氭椂鎹曡幏浜嬩欢',
+  'Append conditional intermediate catch event': '杩藉姞涓棿鏉′欢鎹曡幏浜嬩欢',
+  'Append signal intermediate catch event': '杩藉姞涓棿淇″彿鎹曡幏浜嬩欢'
+};
diff --git a/src/components/BpmnDesign/assets/moddle/flowable.ts b/src/components/BpmnDesign/assets/moddle/flowable.ts
new file mode 100644
index 0000000..de959a6
--- /dev/null
+++ b/src/components/BpmnDesign/assets/moddle/flowable.ts
@@ -0,0 +1,1250 @@
+export default {
+  'name': 'Flowable',
+  'uri': 'http://flowable.org/bpmn',
+  'prefix': 'flowable',
+  'xml': {
+    'tagAlias': 'lowerCase'
+  },
+  'associations': [],
+  'types': [
+    {
+      'name': 'flowable:extCandidateUsers',
+      'isAbstract': true,
+      'extends': [],
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['*']
+      },
+      'properties': [
+        {
+          'name': 'body',
+          'type': 'String',
+          'isBody': true
+        }
+      ]
+    },
+    {
+      'name': 'flowable:extAssignee',
+      'isAbstract': true,
+      'extends': [],
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['*']
+      },
+      'properties': [
+        {
+          'name': 'body',
+          'type': 'String',
+          'isBody': true
+        }
+      ]
+    },
+    {
+      'name': 'flowable:property',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'id',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'name',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'value',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'flowable:properties',
+      'isAbstract': true,
+      'extends': [],
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['*']
+      },
+      'properties': [
+        {
+          'name': 'values',
+          'type': 'flowable:property',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'InOutBinding',
+      'superClass': ['Element'],
+      'isAbstract': true,
+      'properties': [
+        {
+          'name': 'source',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'sourceExpression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'target',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'businessKey',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'local',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': false
+        },
+        {
+          'name': 'variables',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'In',
+      'superClass': ['InOutBinding'],
+      'meta': {
+        'allowedIn': ['bpmn:CallActivity']
+      }
+    },
+    {
+      'name': 'Out',
+      'superClass': ['InOutBinding'],
+      'meta': {
+        'allowedIn': ['bpmn:CallActivity']
+      }
+    },
+    {
+      'name': 'AsyncCapable',
+      'isAbstract': true,
+      'extends': ['bpmn:Activity', 'bpmn:Gateway', 'bpmn:Event'],
+      'properties': [
+        {
+          'name': 'async',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': false
+        },
+        {
+          'name': 'asyncBefore',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': false
+        },
+        {
+          'name': 'asyncAfter',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': false
+        },
+        {
+          'name': 'exclusive',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': true
+        }
+      ]
+    },
+    {
+      'name': 'flowable:in',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'source',
+          'type': 'string',
+          'isAttr': true
+        },
+        {
+          'name': 'target',
+          'type': 'string',
+          'isAttr': true
+        }
+      ]
+    },
+    {
+      'name': 'flowable:out',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'source',
+          'type': 'string',
+          'isAttr': true
+        },
+        {
+          'name': 'target',
+          'type': 'string',
+          'isAttr': true
+        }
+      ]
+    },
+    {
+      'name': 'BoundaryEvent',
+      'superClass': ['CatchEvent'],
+      'properties': [
+        {
+          'name': 'cancelActivity',
+          'default': true,
+          'isAttr': true,
+          'type': 'Boolean'
+        },
+        {
+          'name': 'attachedToRef',
+          'type': 'Activity',
+          'isAttr': true,
+          'isReference': true
+        }
+      ]
+    },
+    {
+      'name': 'JobPriorized',
+      'isAbstract': true,
+      'extends': ['bpmn:Process', 'flowable:AsyncCapable'],
+      'properties': [
+        {
+          'name': 'jobPriority',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'SignalEventDefinition',
+      'isAbstract': true,
+      'extends': ['bpmn:SignalEventDefinition'],
+      'properties': [
+        {
+          'name': 'async',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': false
+        }
+      ]
+    },
+    {
+      'name': 'ErrorEventDefinition',
+      'isAbstract': true,
+      'extends': ['bpmn:ErrorEventDefinition'],
+      'properties': [
+        {
+          'name': 'errorCodeVariable',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'errorMessageVariable',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Error',
+      'isAbstract': true,
+      'extends': ['bpmn:Error'],
+      'properties': [
+        {
+          'name': 'flowable:errorMessage',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'PotentialStarter',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'resourceAssignmentExpression',
+          'type': 'bpmn:ResourceAssignmentExpression'
+        }
+      ]
+    },
+    {
+      'name': 'UserTask',
+      'isAbstract': true,
+      'extends': ['bpmn:UserTask'],
+      'properties': [
+        {
+          'name': 'timerEventDefinition',
+          'type': 'Expression'
+        },
+        {
+          'name': 'multiInstanceLoopCharacteristics',
+          'type': 'MultiInstanceLoopCharacteristics'
+        }
+      ]
+    },
+    {
+      'name': 'StartEvent',
+      'isAbstract': true,
+      'extends': ['bpmn:StartEvent'],
+      'properties': [
+        {
+          'name': 'timerEventDefinition',
+          'type': 'Expression'
+        }
+      ]
+    },
+    {
+      'name': 'FormSupported',
+      'isAbstract': true,
+      'extends': ['bpmn:StartEvent', 'bpmn:UserTask'],
+      'properties': [
+        {
+          'name': 'formHandlerClass',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'formKey',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'TemplateSupported',
+      'isAbstract': true,
+      'extends': ['bpmn:Process', 'bpmn:FlowElement'],
+      'properties': [
+        {
+          'name': 'modelerTemplate',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Initiator',
+      'isAbstract': true,
+      'extends': ['bpmn:StartEvent'],
+      'properties': [
+        {
+          'name': 'initiator',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'ScriptTask',
+      'isAbstract': true,
+      'extends': ['bpmn:ScriptTask'],
+      'properties': [
+        {
+          'name': 'resultVariable',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'resource',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Process',
+      'isAbstract': true,
+      'extends': ['bpmn:Process'],
+      'properties': [
+        {
+          'name': 'candidateStarterGroups',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'candidateStarterUsers',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'versionTag',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'historyTimeToLive',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'isStartableInTasklist',
+          'isAttr': true,
+          'type': 'Boolean',
+          'default': true
+        }
+      ]
+    },
+    {
+      'name': 'EscalationEventDefinition',
+      'isAbstract': true,
+      'extends': ['bpmn:EscalationEventDefinition'],
+      'properties': [
+        {
+          'name': 'escalationCodeVariable',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'FormalExpression',
+      'isAbstract': true,
+      'extends': ['bpmn:FormalExpression'],
+      'properties': [
+        {
+          'name': 'resource',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Assignable',
+      'extends': ['bpmn:UserTask'],
+      'properties': [
+        {
+          'name': 'candidateGroups',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'dueDate',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'followUpDate',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'priority',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'CallActivity',
+      'extends': ['bpmn:CallActivity'],
+      'properties': [
+        {
+          'name': 'calledElementBinding',
+          'isAttr': true,
+          'type': 'String',
+          'default': 'latest'
+        },
+        {
+          'name': 'calledElementVersion',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'calledElementVersionTag',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'calledElementTenantId',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'caseRef',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'caseBinding',
+          'isAttr': true,
+          'type': 'String',
+          'default': 'latest'
+        },
+        {
+          'name': 'caseVersion',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'caseTenantId',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'variableMappingClass',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'variableMappingDelegateExpression',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'ServiceTaskLike',
+      'extends': ['bpmn:ServiceTask', 'bpmn:BusinessRuleTask', 'bpmn:SendTask', 'bpmn:MessageEventDefinition'],
+      'properties': [
+        {
+          'name': 'expression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'class',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'delegateExpression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'resultVariable',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'ExclusiveGateway',
+      'isAbstract': true,
+      'extends': ['bpmn:ExclusiveGateway'],
+      'properties': [
+        {
+          'name': 'serviceClass',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'DmnCapable',
+      'extends': ['bpmn:BusinessRuleTask'],
+      'properties': [
+        {
+          'name': 'decisionRef',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'decisionRefBinding',
+          'isAttr': true,
+          'type': 'String',
+          'default': 'latest'
+        },
+        {
+          'name': 'decisionRefVersion',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'mapDecisionResult',
+          'isAttr': true,
+          'type': 'String',
+          'default': 'resultList'
+        },
+        {
+          'name': 'decisionRefTenantId',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'ExternalCapable',
+      'extends': ['flowable:ServiceTaskLike'],
+      'properties': [
+        {
+          'name': 'type',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'topic',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'TaskPriorized',
+      'extends': ['bpmn:Process', 'flowable:ExternalCapable'],
+      'properties': [
+        {
+          'name': 'taskPriority',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Properties',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['*']
+      },
+      'properties': [
+        {
+          'name': 'values',
+          'type': 'Property',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'Property',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'id',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'name',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'value',
+          'type': 'String',
+          'isAttr': true
+        }
+      ]
+    },
+    {
+      'name': 'Connector',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['flowable:ServiceTaskLike']
+      },
+      'properties': [
+        {
+          'name': 'inputOutput',
+          'type': 'InputOutput'
+        },
+        {
+          'name': 'connectorId',
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'InputOutput',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['bpmn:FlowNode', 'flowable:Connector']
+      },
+      'properties': [
+        {
+          'name': 'inputOutput',
+          'type': 'InputOutput'
+        },
+        {
+          'name': 'connectorId',
+          'type': 'String'
+        },
+        {
+          'name': 'inputParameters',
+          'isMany': true,
+          'type': 'InputParameter'
+        },
+        {
+          'name': 'outputParameters',
+          'isMany': true,
+          'type': 'OutputParameter'
+        }
+      ]
+    },
+    {
+      'name': 'InputOutputParameter',
+      'properties': [
+        {
+          'name': 'name',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'value',
+          'isBody': true,
+          'type': 'String'
+        },
+        {
+          'name': 'definition',
+          'type': 'InputOutputParameterDefinition'
+        }
+      ]
+    },
+    {
+      'name': 'InputOutputParameterDefinition',
+      'isAbstract': true
+    },
+    {
+      'name': 'List',
+      'superClass': ['InputOutputParameterDefinition'],
+      'properties': [
+        {
+          'name': 'items',
+          'isMany': true,
+          'type': 'InputOutputParameterDefinition'
+        }
+      ]
+    },
+    {
+      'name': 'Map',
+      'superClass': ['InputOutputParameterDefinition'],
+      'properties': [
+        {
+          'name': 'entries',
+          'isMany': true,
+          'type': 'Entry'
+        }
+      ]
+    },
+    {
+      'name': 'Entry',
+      'properties': [
+        {
+          'name': 'key',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'value',
+          'isBody': true,
+          'type': 'String'
+        },
+        {
+          'name': 'definition',
+          'type': 'InputOutputParameterDefinition'
+        }
+      ]
+    },
+    {
+      'name': 'Value',
+      'superClass': ['InputOutputParameterDefinition'],
+      'properties': [
+        {
+          'name': 'id',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'name',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'value',
+          'isBody': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Script',
+      'superClass': ['InputOutputParameterDefinition'],
+      'properties': [
+        {
+          'name': 'scriptFormat',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'resource',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'value',
+          'isBody': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'Field',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['flowable:ServiceTaskLike', 'flowable:ExecutionListener', 'flowable:TaskListener']
+      },
+      'properties': [
+        {
+          'name': 'name',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'expression',
+          'isAttr': true,
+          'type': 'expression'
+        },
+        {
+          'name': 'string',
+          'type': 'string'
+        },
+        {
+          'name': 'stringValue',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'string',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['flowable:Field']
+      },
+      'properties': [
+        {
+          'name': 'body',
+          'isBody': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'expression',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['flowable:Field']
+      },
+      'properties': [
+        {
+          'name': 'body',
+          'isBody': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'InputParameter',
+      'superClass': ['InputOutputParameter']
+    },
+    {
+      'name': 'OutputParameter',
+      'superClass': ['InputOutputParameter']
+    },
+    {
+      'name': 'Collectable',
+      'isAbstract': true,
+      'extends': ['bpmn:MultiInstanceLoopCharacteristics'],
+      'superClass': ['flowable:AsyncCapable'],
+      'properties': [
+        {
+          'name': 'collection',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'elementVariable',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'SequenceFlow',
+      'superClass': ['FlowElement'],
+      'properties': [
+        {
+          'name': 'isImmediate',
+          'isAttr': true,
+          'type': 'Boolean'
+        },
+        {
+          'name': 'conditionExpression',
+          'type': 'Expression'
+        },
+        {
+          'name': 'sourceRef',
+          'type': 'FlowNode',
+          'isAttr': true,
+          'isReference': true
+        },
+        {
+          'name': 'targetRef',
+          'type': 'FlowNode',
+          'isAttr': true,
+          'isReference': true
+        }
+      ]
+    },
+    {
+      'name': 'MultiInstanceLoopCharacteristics',
+      'superClass': ['LoopCharacteristics'],
+      'properties': [
+        {
+          'name': 'isSequential',
+          'default': false,
+          'isAttr': true,
+          'type': 'Boolean'
+        },
+        {
+          'name': 'behavior',
+          'type': 'MultiInstanceBehavior',
+          'default': 'All',
+          'isAttr': true
+        },
+        {
+          'name': 'loopCardinality',
+          'type': 'Expression',
+          'xml': {
+            'serialize': 'xsi:type'
+          }
+        },
+        {
+          'name': 'loopDataInputRef',
+          'type': 'ItemAwareElement',
+          'isReference': true
+        },
+        {
+          'name': 'loopDataOutputRef',
+          'type': 'ItemAwareElement',
+          'isReference': true
+        },
+        {
+          'name': 'inputDataItem',
+          'type': 'DataInput',
+          'xml': {
+            'serialize': 'property'
+          }
+        },
+        {
+          'name': 'outputDataItem',
+          'type': 'DataOutput',
+          'xml': {
+            'serialize': 'property'
+          }
+        },
+        {
+          'name': 'complexBehaviorDefinition',
+          'type': 'ComplexBehaviorDefinition',
+          'isMany': true
+        },
+        {
+          'name': 'completionCondition',
+          'type': 'Expression',
+          'xml': {
+            'serialize': 'xsi:type'
+          }
+        },
+        {
+          'name': 'oneBehaviorEventRef',
+          'type': 'EventDefinition',
+          'isAttr': true,
+          'isReference': true
+        },
+        {
+          'name': 'noneBehaviorEventRef',
+          'type': 'EventDefinition',
+          'isAttr': true,
+          'isReference': true
+        }
+      ]
+    },
+    {
+      'name': 'FailedJobRetryTimeCycle',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['flowable:AsyncCapable', 'bpmn:MultiInstanceLoopCharacteristics']
+      },
+      'properties': [
+        {
+          'name': 'body',
+          'isBody': true,
+          'type': 'String'
+        }
+      ]
+    },
+    {
+      'name': 'ExecutionListener',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': [
+          'bpmn:Task',
+          'bpmn:ServiceTask',
+          'bpmn:UserTask',
+          'bpmn:BusinessRuleTask',
+          'bpmn:ScriptTask',
+          'bpmn:ReceiveTask',
+          'bpmn:ManualTask',
+          'bpmn:ExclusiveGateway',
+          'bpmn:SequenceFlow',
+          'bpmn:ParallelGateway',
+          'bpmn:InclusiveGateway',
+          'bpmn:EventBasedGateway',
+          'bpmn:StartEvent',
+          'bpmn:IntermediateCatchEvent',
+          'bpmn:IntermediateThrowEvent',
+          'bpmn:EndEvent',
+          'bpmn:BoundaryEvent',
+          'bpmn:CallActivity',
+          'bpmn:SubProcess',
+          'bpmn:Process'
+        ]
+      },
+      'properties': [
+        {
+          'name': 'expression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'class',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'delegateExpression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'event',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'script',
+          'type': 'Script'
+        },
+        {
+          'name': 'fields',
+          'type': 'Field',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'TaskListener',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['bpmn:UserTask']
+      },
+      'properties': [
+        {
+          'name': 'expression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'class',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'delegateExpression',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'event',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'script',
+          'type': 'Script'
+        },
+        {
+          'name': 'fields',
+          'type': 'Field',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'FormProperty',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['bpmn:StartEvent', 'bpmn:UserTask']
+      },
+      'properties': [
+        {
+          'name': 'id',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'name',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'type',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'required',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'readable',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'writable',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'variable',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'expression',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'datePattern',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'default',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'values',
+          'type': 'Value',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'FormData',
+      'superClass': ['Element'],
+      'meta': {
+        'allowedIn': ['bpmn:StartEvent', 'bpmn:UserTask']
+      },
+      'properties': [
+        {
+          'name': 'fields',
+          'type': 'FormField',
+          'isMany': true
+        },
+        {
+          'name': 'businessKey',
+          'type': 'String',
+          'isAttr': true
+        }
+      ]
+    },
+    {
+      'name': 'FormField',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'id',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'label',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'type',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'datePattern',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'defaultValue',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'properties',
+          'type': 'Properties'
+        },
+        {
+          'name': 'validation',
+          'type': 'Validation'
+        },
+        {
+          'name': 'values',
+          'type': 'Value',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'Validation',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'constraints',
+          'type': 'Constraint',
+          'isMany': true
+        }
+      ]
+    },
+    {
+      'name': 'Constraint',
+      'superClass': ['Element'],
+      'properties': [
+        {
+          'name': 'name',
+          'type': 'String',
+          'isAttr': true
+        },
+        {
+          'name': 'config',
+          'type': 'String',
+          'isAttr': true
+        }
+      ]
+    },
+    {
+      'name': 'ConditionalEventDefinition',
+      'isAbstract': true,
+      'extends': ['bpmn:ConditionalEventDefinition'],
+      'properties': [
+        {
+          'name': 'variableName',
+          'isAttr': true,
+          'type': 'String'
+        },
+        {
+          'name': 'variableEvent',
+          'isAttr': true,
+          'type': 'String'
+        }
+      ]
+    }
+  ],
+  'emumerations': []
+};
diff --git a/src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts b/src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts
new file mode 100644
index 0000000..9f12ff2
--- /dev/null
+++ b/src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts
@@ -0,0 +1,138 @@
+import ContextPadProvider from 'bpmn-js/lib/features/context-pad/ContextPadProvider';
+import { Injector } from 'didi';
+import EventBus from 'diagram-js/lib/core/EventBus';
+import ContextPad from 'diagram-js/lib/features/context-pad/ContextPad';
+import Modeling from 'bpmn-js/lib/features/modeling/Modeling.js';
+import ElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory';
+import Connect from 'diagram-js/lib/features/connect/Connect';
+import Create from 'diagram-js/lib/features/create/Create';
+import PopupMenu from 'diagram-js/lib/features/popup-menu/PopupMenu';
+import Canvas from 'diagram-js/lib/core/Canvas';
+import Rules from 'diagram-js/lib/features/rules/Rules';
+import { Element, Shape } from 'diagram-js/lib/model/Types';
+import BpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory';
+import modeler from '@/store/modules/modeler';
+
+// @Description: 澧炲己鍏冪礌杩炵嚎浜嬩欢
+
+class CustomContextPadProvider extends ContextPadProvider {
+  private _contextPad: ContextPad;
+  private _modeling: Modeling;
+  private _elementFactory: ElementFactory;
+  private _autoPlace: any;
+  private _connect: Connect;
+  private _create: Create;
+  private _popupMenu: PopupMenu;
+  private _canvas: Canvas;
+  private _rules: Rules;
+
+  constructor(
+    config: any,
+    injector: Injector,
+    eventBus: EventBus,
+    contextPad: ContextPad,
+    modeling: Modeling,
+    elementFactory: ElementFactory,
+    connect: Connect,
+    create: Create,
+    popupMenu: PopupMenu,
+    canvas: Canvas,
+    rules: Rules,
+    translate
+  ) {
+    // @ts-ignore
+    super(config, injector, eventBus, contextPad, modeling, elementFactory, connect, create, popupMenu, canvas, rules, translate);
+
+    this._contextPad = contextPad;
+    this._modeling = modeling;
+    this._elementFactory = elementFactory;
+    this._connect = connect;
+    this._create = create;
+    this._popupMenu = popupMenu;
+    this._canvas = canvas;
+    this._rules = rules;
+
+    this._autoPlace = injector.get('autoPlace', false);
+  }
+
+  getContextPadEntries(element: Element) {
+    const actions: Record<string, any> = {};
+
+    const appendUserTask = (event: Event, element: Shape) => {
+      const shape = this._elementFactory.createShape({ type: 'bpmn:UserTask' });
+      this._create.start(event, shape, {
+        source: element
+      });
+    };
+
+    const appendMultiInstanceUserTask = (event: Event, element: Shape) => {
+      const store = modeler();
+      const bpmnFactory = store.getModeler().get('bpmnFactory') as BpmnFactory;
+      const businessObject = bpmnFactory.create('bpmn:UserTask', {
+        // name: '澶氬疄渚嬬敤鎴蜂换鍔�',
+        isForCompensation: false
+      });
+      businessObject.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
+      // 鍒涘缓 Shape
+      const shape = this._elementFactory.createShape({
+        type: 'bpmn:UserTask',
+        businessObject: businessObject
+      });
+      this._create.start(event, shape, { source: element });
+    };
+
+    const appendTask = this._autoPlace
+      ? (event, element) => {
+          const bpmnFactory: BpmnFactory | undefined = modeler().getModeler().get('bpmnFactory');
+          const businessObject = bpmnFactory.create('bpmn:UserTask', {
+            // name: '澶氬疄渚嬬敤鎴蜂换鍔�',// 鍙抽敭鍒涘缓鏄剧ず
+            isForCompensation: false
+          });
+
+          // 鍒涘缓澶氬疄渚嬪睘鎬у苟鍒嗛厤缁欑敤鎴蜂换鍔$殑 loopCharacteristics
+          businessObject.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
+
+          // 鍒涘缓 Shape
+          const shape = this._elementFactory.createShape({
+            type: 'bpmn:UserTask',
+            businessObject: businessObject
+          });
+
+          this._autoPlace.append(element, shape);
+        }
+      : appendMultiInstanceUserTask;
+
+    const append = this._autoPlace
+      ? (event: Event, element: Shape) => {
+          const shape = this._elementFactory.createShape({ type: 'bpmn:UserTask' });
+          this._autoPlace.append(element, shape);
+        }
+      : appendUserTask;
+
+    // // 娣诲姞鍒涘缓鐢ㄦ埛浠诲姟鎸夐挳
+    actions['append.append-user-task'] = {
+      group: 'model',
+      className: 'bpmn-icon-user-task',
+      title: '鐢ㄦ埛浠诲姟',
+      action: {
+        dragstart: appendUserTask,
+        click: append
+      }
+    };
+
+    // 娣诲姞鍒涘缓澶氬疄渚嬬敤鎴蜂换鍔℃寜閽�
+    actions['append.append-multi-instance-user-task'] = {
+      group: 'model',
+      className: 'bpmn-icon-user', // 浣犲彲浠ヤ娇鐢ㄥ瀹炰緥鐢ㄦ埛浠诲姟鐨勫浘鏍�  bpmn-icon-user   bpmn-icon-user-task
+      title: '澶氬疄渚嬬敤鎴蜂换鍔�',
+      action: {
+        dragstart: appendMultiInstanceUserTask,
+        click: appendTask
+      }
+    };
+
+    return actions;
+  }
+}
+
+export default CustomContextPadProvider;
diff --git a/src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts b/src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts
new file mode 100644
index 0000000..38f1cea
--- /dev/null
+++ b/src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts
@@ -0,0 +1,119 @@
+import { assign } from 'min-dash';
+import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
+import ElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory';
+import Create from 'diagram-js/lib/features/create/Create';
+import SpaceTool from 'diagram-js/lib/features/space-tool/SpaceTool';
+import LassoTool from 'diagram-js/lib/features/lasso-tool/LassoTool';
+import HandTool from 'diagram-js/lib/features/hand-tool/HandTool';
+import GlobalConnect from 'diagram-js/lib/features/global-connect/GlobalConnect';
+import Palette from 'diagram-js/lib/features/palette/Palette';
+import modeler from '@/store/modules/modeler';
+import BpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory';
+
+// @Description: 澧炲己宸︿晶闈㈡澘
+class CustomPaletteProvider extends PaletteProvider {
+  private readonly _palette: Palette;
+  private readonly _create: Create;
+  private readonly _elementFactory: ElementFactory;
+  private readonly _spaceTool: SpaceTool;
+  private readonly _lassoTool: LassoTool;
+  private readonly _handTool: HandTool;
+  private readonly _globalConnect: GlobalConnect;
+  private readonly _translate: any;
+
+  constructor(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) {
+    super(
+      palette,
+      create,
+      elementFactory,
+      spaceTool,
+      lassoTool,
+      handTool,
+      globalConnect,
+      translate
+      // 2000
+    );
+    this._palette = palette;
+    this._create = create;
+    this._elementFactory = elementFactory;
+    this._spaceTool = spaceTool;
+    this._lassoTool = lassoTool;
+    this._handTool = handTool;
+    this._globalConnect = globalConnect;
+    this._translate = translate;
+  }
+
+  getPaletteEntries() {
+    const actions = {},
+      create = this._create,
+      elementFactory = this._elementFactory,
+      translate = this._translate;
+
+    function createAction(type: string, group: string, className: string, title: string, options?: object) {
+      function createListener(event) {
+        const shape = elementFactory.createShape(assign({ type: type }, options));
+        if (options) {
+          !shape.businessObject.di && (shape.businessObject.di = {});
+          shape.businessObject.di.isExpanded = (options as { [key: string]: any }).isExpanded;
+        }
+        create.start(event, shape, null);
+      }
+      const shortType = type.replace(/^bpmn:/, '');
+      return {
+        group: group,
+        className: className,
+        title: title || translate('Create {type}', { type: shortType }),
+        action: {
+          dragstart: createListener,
+          click: createListener
+        }
+      };
+    }
+
+    function createMultiInstanceUserTask(event) {
+      const bpmnFactory: BpmnFactory | undefined = modeler().getBpmnFactory();
+      // 鍒涘缓涓�涓� bpmn:UserTask
+      const userTask = bpmnFactory.create('bpmn:UserTask', {
+        // name: '澶氬疄渚嬬敤鎴蜂换鍔�', // 鍦ㄧ敾鏉夸腑鏄剧ず瀛楁
+        isForCompensation: false
+      });
+      // 灏嗗瀹炰緥灞炴�у垎閰嶇粰 bpmn:UserTask 鐨� loopCharacteristics
+      userTask.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
+      const customUserTask = elementFactory.createShape({
+        type: 'bpmn:UserTask',
+        businessObject: userTask // 鍒嗛厤鍒涘缓鐨� userTask 鍒� businessObject
+      });
+      create.start(event, customUserTask, {});
+    }
+
+    assign(actions, {
+      'create.parallel-gateway': createAction('bpmn:ParallelGateway', 'gateway', 'bpmn-icon-gateway-parallel', '骞惰缃戝叧'),
+      'create.event-base-gateway': createAction('bpmn:EventBasedGateway', 'gateway', 'bpmn-icon-gateway-eventbased', '浜嬩欢缃戝叧'),
+      // 鍒嗙粍绾�
+      'gateway-separator': {
+        group: 'gateway',
+        separator: true
+      },
+      'create.user-task': createAction('bpmn:UserTask', 'activity', 'bpmn-icon-user-task', '鍒涘缓鐢ㄦ埛浠诲姟'),
+      'create.multi-instance-user-task': {
+        group: 'activity',
+        type: 'bpmn:UserTask',
+        className: 'bpmn-icon-user task-multi-instance',
+        title: '鍒涘缓澶氬疄渚嬬敤鎴蜂换鍔�',
+        action: {
+          click: createMultiInstanceUserTask,
+          dragstart: createMultiInstanceUserTask
+        }
+      },
+      'task-separator': {
+        group: 'activity',
+        separator: true
+      }
+    });
+    return actions;
+  }
+}
+
+CustomPaletteProvider['$inject'] = ['palette', 'create', 'elementFactory', 'spaceTool', 'lassoTool', 'handTool', 'globalConnect', 'translate'];
+
+export default CustomPaletteProvider;
diff --git a/src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts b/src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts
new file mode 100644
index 0000000..6a4eb1a
--- /dev/null
+++ b/src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts
@@ -0,0 +1,56 @@
+import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
+import {
+  append as svgAppend,
+  attr as svgAttr,
+  create as svgCreate,
+  select as svgSelect,
+  selectAll as svgSelectAll,
+  clone as svgClone,
+  clear as svgClear,
+  remove as svgRemove
+} from 'tiny-svg';
+
+const HIGH_PRIORITY = 1500;
+export default class CustomRenderer extends BaseRenderer {
+  bpmnRenderer: BaseRenderer;
+  modeling: any;
+  constructor(eventBus, bpmnRenderer, modeling) {
+    super(eventBus, HIGH_PRIORITY);
+    this.bpmnRenderer = bpmnRenderer;
+    this.modeling = modeling;
+  }
+  canRender(element) {
+    // ignore labels
+    return !element.labelTarget;
+  }
+
+  /**
+   * 鑷畾涔夎妭鐐瑰浘褰�
+   * @param {*} parentNode 褰撳墠鍏冪礌鐨剆vgNode
+   * @param {*} element
+   * @returns
+   */
+  drawShape(parentNode, element) {
+    const shape = this.bpmnRenderer.drawShape(parentNode, element);
+    const { type, width, height } = element;
+    // 寮�濮� 濉厖缁胯壊
+    if (type === 'bpmn:StartEvent') {
+      svgAttr(shape, { fill: '#77DF6D' });
+      return shape;
+    }
+    if (type === 'bpmn:EndEvent') {
+      svgAttr(shape, { fill: '#EE7B77' });
+      return shape;
+    }
+    if (type === 'bpmn:UserTask') {
+      svgAttr(shape, { fill: '#A9C4F8' });
+      return shape;
+    }
+    return shape;
+  }
+
+  getShapePath(shape) {
+    return this.bpmnRenderer.getShapePath(shape);
+  }
+}
+CustomRenderer['$inject'] = ['eventBus', 'bpmnRenderer'];
diff --git a/src/components/BpmnDesign/assets/module/Translate/index.ts b/src/components/BpmnDesign/assets/module/Translate/index.ts
new file mode 100644
index 0000000..7324c77
--- /dev/null
+++ b/src/components/BpmnDesign/assets/module/Translate/index.ts
@@ -0,0 +1,15 @@
+import zh from '@/components/BpmnDesign/assets/lang/zh';
+
+const customTranslate = (template: any, replacements: any) => {
+  replacements = replacements || {};
+  template = zh[template] || template;
+  return template.replace(/{([^}]+)}/g, function (_: any, key: any) {
+    return replacements[key] || '{' + key + '}';
+  });
+};
+
+export const translateModule = {
+  translate: ['value', customTranslate]
+};
+
+export default translateModule;
diff --git a/src/components/BpmnDesign/assets/module/index.ts b/src/components/BpmnDesign/assets/module/index.ts
new file mode 100644
index 0000000..55f6b9f
--- /dev/null
+++ b/src/components/BpmnDesign/assets/module/index.ts
@@ -0,0 +1,17 @@
+// 缈昏瘧妯″潡
+import TranslationModule from './Translate';
+import { ModuleDeclaration } from 'didi';
+import CustomPaletteProvider from './Palette/CustomPaletteProvider';
+import CustomRenderer from './Renderer/CustomRenderer';
+import CustomContextPadProvider from './ContextPad/CustomContextPadProvider';
+
+const Module: ModuleDeclaration[] = [
+  {
+    __init__: ['customPaletteProvider', 'customContextPadProvider', 'customRenderer'],
+    customPaletteProvider: ['type', CustomPaletteProvider],
+    customRenderer: ['type', CustomRenderer],
+    customContextPadProvider: ['type', CustomContextPadProvider]
+  },
+  TranslationModule
+];
+export default Module;
diff --git a/src/components/BpmnDesign/assets/showConfig.ts b/src/components/BpmnDesign/assets/showConfig.ts
new file mode 100644
index 0000000..853eb35
--- /dev/null
+++ b/src/components/BpmnDesign/assets/showConfig.ts
@@ -0,0 +1,50 @@
+export default {
+  'bpmn:EndEvent': {},
+  'bpmn:StartEvent': {
+    initiator: true,
+    formKey: true
+  },
+  'bpmn:UserTask': {
+    allocationType: true,
+    specifyDesc: true,
+    multipleUserAuditType: true,
+    async: true,
+    priority: true,
+    skipExpression: true,
+    dueDate: true,
+    taskListener: true,
+    executionListener: true
+  },
+  'bpmn:ServiceTask': {
+    async: true,
+    skipExpression: true,
+    isForCompensation: true,
+    triggerable: true,
+    class: true
+  },
+  'bpmn:ScriptTask': {
+    async: true,
+    isForCompensation: true,
+    autoStoreVariables: true
+  },
+  'bpmn:ManualTask': {
+    async: true,
+    isForCompensation: true
+  },
+  'bpmn:ReceiveTask': {
+    async: true,
+    isForCompensation: true
+  },
+  'bpmn:SendTask': {
+    async: true,
+    isForCompensation: true
+  },
+  'bpmn:BusinessRuleTask': {
+    async: true,
+    isForCompensation: true,
+    ruleVariablesInput: true,
+    rules: true,
+    resultVariable: true,
+    exclude: true
+  }
+};
diff --git a/src/components/BpmnDesign/assets/style/index.scss b/src/components/BpmnDesign/assets/style/index.scss
new file mode 100644
index 0000000..d3ce379
--- /dev/null
+++ b/src/components/BpmnDesign/assets/style/index.scss
@@ -0,0 +1,230 @@
+.djs-palette {
+  width: 300px;
+
+  .bpmn-icon-hand-tool:hover {
+    &:after {
+      content: '鍚姩鎵嬪姩宸ュ叿';
+      position: absolute;
+      left: 45px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-lasso-tool:hover {
+    &:after {
+      content: '鍚姩濂楃储宸ュ叿';
+      position: absolute;
+      left: 100px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-space-tool:hover {
+    &:after {
+      content: '鍚姩鍒涘缓/鍒犻櫎绌洪棿宸ュ叿';
+      position: absolute;
+      left: 45px;
+      width: 170px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-connection-multi:hover {
+    &:after {
+      content: '鍚姩鍏ㄥ眬杩炴帴宸ュ叿';
+      position: absolute;
+      left: 100px;
+      width: 140px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-start-event-none:hover {
+    &:after {
+      content: '鍒涘缓寮�濮嬩簨浠�';
+      position: absolute;
+      left: 45px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-intermediate-event-none:hover {
+    &:after {
+      content: '鍒涘缓涓棿/杈圭晫浜嬩欢';
+      position: absolute;
+      left: 100px;
+      width: 140px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-end-event-none:hover {
+    &:after {
+      content: '鍒涘缓缁撴潫浜嬩欢';
+      position: absolute;
+      left: 45px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-gateway-none:hover {
+    &:after {
+      content: '鍒涘缓缃戝叧';
+      position: absolute;
+      left: 100px;
+      width: 90px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-gateway-parallel:hover {
+    &:after {
+      content: '鍒涘缓骞惰缃戝叧';
+      position: absolute;
+      left: 45px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-gateway-eventbased:hover {
+    &:after {
+      content: '鍒涘缓浜嬩欢缃戝叧';
+      position: absolute;
+      left: 100px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-task:hover {
+    &:after {
+      content: '鍒涘缓浠诲姟';
+      position: absolute;
+      left: 45px;
+      width: 80px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-subprocess-expanded:hover {
+    &:after {
+      content: '鍒涘缓鍙姌鍙犲瓙娴佺▼';
+      position: absolute;
+      left: 100px;
+      width: 140px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-user-task:hover {
+    &:after {
+      content: '鍒涘缓鐢ㄦ埛浠诲姟';
+      position: absolute;
+      left: 45px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+
+  .task-multi-instance:hover {
+    &:after {
+      content: '鍒涘缓澶氬疄渚嬬敤鎴蜂换鍔�';
+      position: absolute;
+      left: 100px;
+      width: 160px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+  .bpmn-icon-participant:hover {
+    &:after {
+      content: '鍒涘缓娉虫睜/娉抽亾';
+      position: absolute;
+      left: 45px;
+      width: 120px;
+      font-size: 15px;
+      font-weight: bold;
+      color: #3a84de;
+      border-radius: 2px;
+      border: 1px solid #cccccc;
+      background-color: #fafafa;
+      opacity: 0.8;
+    }
+  }
+}
diff --git a/src/components/BpmnDesign/hooks/usePanel.ts b/src/components/BpmnDesign/hooks/usePanel.ts
new file mode 100644
index 0000000..0dbc3f3
--- /dev/null
+++ b/src/components/BpmnDesign/hooks/usePanel.ts
@@ -0,0 +1,132 @@
+import showConfig from '@/components/BpmnDesign/assets/showConfig';
+import { ModdleElement } from 'bpmn';
+import useModelerStore from '@/store/modules/modeler';
+interface Options {
+  element: ModdleElement;
+}
+
+export default (ops: Options) => {
+  const { element } = ops;
+  const { getModeling, getModdle } = useModelerStore();
+  const modeling = getModeling();
+  const moddle = getModdle();
+
+  /**
+   * 褰撳墠鑺傜偣绫诲瀷
+   */
+  const elementType = computed(() => {
+    const bizObj = element.businessObject;
+    return bizObj.eventDefinitions ? bizObj.eventDefinitions[0].$type : bizObj.$type;
+  });
+
+  /**
+   * 鐢ㄤ簬鎺у埗闈㈡澘瀛楁鏄剧ず涓庨殣钘忕殑閰嶇疆
+   */
+  const config = computed(() => showConfig[elementType.value] || {});
+
+  /**
+   * 鍒涘缓涓�涓妭鐐�
+   * @param elementType 鑺傜偣绫诲瀷
+   * @param properties 灞炴��
+   * @param parent 鐖惰妭鐐�
+   */
+  const createModdleElement = (elementType: string, properties: any, parent: ModdleElement) => {
+    const element = moddle.create(elementType, properties);
+    parent && (element.$parent = parent);
+    return element;
+  };
+
+  /**
+   * 鑾峰彇鎵╁睍灞炴�э紝濡傛灉涓嶅瓨鍦ㄤ細鑷姩鍒涘缓
+   */
+  const getExtensionElements = (create = true) => {
+    let extensionElements = element.businessObject.get<ModdleElement>('extensionElements');
+    if (!extensionElements && create) {
+      extensionElements = createModdleElement('bpmn:ExtensionElements', { values: [] }, element.businessObject);
+      modeling.updateModdleProperties(element, element.businessObject, { extensionElements });
+    }
+    return extensionElements;
+  };
+
+  /**
+   * 鑾峰彇extensionElements涓嬬殑properties
+   * @param extensionElements 鍙�夊弬鏁帮紝榛樿鑾峰彇褰撳墠Element涓嬬殑extensionElements涓嬬殑Properties
+   */
+  const getPropertiesElements = (extensionElements?: ModdleElement) => {
+    if (!extensionElements) {
+      extensionElements = getExtensionElements();
+    }
+    let propertiesElements = extensionElements.values.find((item) => item.$type === 'flowable:properties');
+    if (!propertiesElements) {
+      propertiesElements = createModdleElement('flowable:properties', { values: [] }, extensionElements);
+      modeling.updateModdleProperties(element, extensionElements, {
+        values: [...extensionElements.get<[]>('values'), propertiesElements]
+      });
+    }
+    return propertiesElements;
+  };
+
+  /**
+   * 鏇存柊鑺傜偣灞炴��
+   * @param properties 灞炴�у��
+   */
+  const updateProperties = (properties: any) => {
+    modeling.updateProperties(element, properties);
+  };
+
+  /**
+   * 鏇存柊鑺傜偣淇℃伅
+   * @param updateElement 闇�瑕佹洿鏂扮殑鑺傜偣
+   * @param properties 灞炴��
+   */
+  const updateModdleProperties = (updateElement, properties: any) => {
+    modeling.updateModdleProperties(element, updateElement, properties);
+  };
+
+  /**
+   * 鏇存柊Property灞炴��
+   * @param name key鍊�
+   * @param value 鍊�
+   */
+  const updateProperty = (name: string, value: string) => {
+    const propertiesElements = getPropertiesElements();
+
+    let propertyElements = propertiesElements.values.find((item) => item.name === name);
+    if (!propertyElements) {
+      propertyElements = createModdleElement('flowable:property', { name: name, value: value }, propertiesElements);
+      modeling.updateModdleProperties(element, propertiesElements, {
+        values: [...propertiesElements.get('values'), propertyElements]
+      });
+    } else {
+      propertyElements.name = name;
+      propertyElements.value = value;
+    }
+    return propertyElements;
+  };
+
+  const idChange = (newVal: string) => {
+    if (newVal) {
+      updateProperties({ id: newVal });
+    }
+  };
+  const nameChange = (newVal: string) => {
+    if (newVal) {
+      updateProperties({ name: newVal });
+    }
+  };
+  return {
+    elementType,
+    showConfig: config,
+
+    updateProperties,
+    updateProperty,
+    updateModdleProperties,
+
+    createModdleElement,
+    idChange,
+    nameChange,
+
+    getExtensionElements,
+    getPropertiesElements
+  };
+};
diff --git a/src/components/BpmnDesign/hooks/useParseElement.ts b/src/components/BpmnDesign/hooks/useParseElement.ts
new file mode 100644
index 0000000..a5a255d
--- /dev/null
+++ b/src/components/BpmnDesign/hooks/useParseElement.ts
@@ -0,0 +1,34 @@
+import { ModdleElement } from 'bpmn';
+
+interface Options {
+  element: ModdleElement;
+}
+
+interface Data {
+  id: string;
+}
+
+export default (ops: Options) => {
+  const { element } = ops;
+
+  const parseData = <T>(): T => {
+    const result = {
+      ...element.businessObject,
+      ...element.businessObject.$attrs
+    };
+
+    // 绉婚櫎flowable鍓嶇紑锛屾牸寮忓寲鏁扮粍
+    for (const key in result) {
+      if (key.indexOf('flowable:') === 0) {
+        const newKey = key.replace('flowable:', '');
+        result[newKey] = result[key];
+        delete result[key];
+      }
+    }
+    return { ...result } as T;
+  };
+
+  return {
+    parseData
+  };
+};
diff --git a/src/components/BpmnDesign/index.vue b/src/components/BpmnDesign/index.vue
new file mode 100644
index 0000000..1568a3e
--- /dev/null
+++ b/src/components/BpmnDesign/index.vue
@@ -0,0 +1,409 @@
+<template>
+  <div class="containers">
+    <div class="app-containers">
+      <el-container class="h-full">
+        <el-container style="align-items: stretch">
+          <el-header>
+            <div class="process-toolbar">
+              <el-space wrap :size="10">
+                <el-button size="small" type="primary" @click="saveXml">淇� 瀛�</el-button>
+                <el-dropdown size="small">
+                  <el-button size="small" type="primary"> 棰� 瑙� </el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item icon="Document" @click="previewXML">XML棰勮</el-dropdown-item>
+                      <el-dropdown-item icon="View" @click="previewSVG"> SVG棰勮</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+
+                <el-dropdown size="small">
+                  <el-button size="small" type="primary"> 涓� 杞� </el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item icon="Download" @click="downloadXML">涓嬭浇XML</el-dropdown-item>
+                      <el-dropdown-item icon="Download" @click="downloadSVG"> 涓嬭浇SVG</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+                <el-tooltip effect="dark" content="鏂板缓" placement="bottom">
+                  <el-button size="small" icon="CirclePlus" @click="newDiagram" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="鑷�傚簲灞忓箷" placement="bottom">
+                  <el-button size="small" icon="Rank" @click="fitViewport" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="鏀惧ぇ" placement="bottom">
+                  <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="缂╁皬" placement="bottom">
+                  <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="鍚庨��" placement="bottom">
+                  <el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="鍓嶈繘" placement="bottom">
+                  <el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" />
+                </el-tooltip>
+              </el-space>
+            </div>
+          </el-header>
+          <div ref="canvas" class="canvas" />
+        </el-container>
+        <div :class="{ 'process-panel': true, 'hide': panelFlag }">
+          <div class="process-panel-bar" @click="panelBarClick">
+            <div class="open-bar">
+              <el-link type="default" :underline="false">
+                <svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon>
+              </el-link>
+            </div>
+          </div>
+          <transition enter-active-class="animate__animated animate__fadeIn">
+            <div v-show="showPanel" v-if="bpmnModeler" class="panel-content">
+              <PropertyPanel :modeler="bpmnModeler" />
+            </div>
+          </transition>
+        </div>
+      </el-container>
+    </div>
+  </div>
+  <div>
+    <el-dialog v-model="perviewXMLShow" title="XML棰勮" width="80%" append-to-body>
+      <highlightjs :code="xmlStr" language="XML" />
+    </el-dialog>
+  </div>
+  <div>
+    <el-dialog v-model="perviewSVGShow" title="SVG棰勮" width="80%" append-to-body>
+      <div style="text-align: center" v-html="svgData" />
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="BpmnDesign">
+import 'bpmn-js/dist/assets/diagram-js.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
+import './assets/style/index.scss';
+import { Canvas, Modeler } from 'bpmn';
+import PropertyPanel from './panel/index.vue';
+import BpmnModeler from 'bpmn-js/lib/Modeler.js';
+import defaultXML from '@/components/BpmnDesign/assets/defaultXML';
+import flowableModdle from '@/components/BpmnDesign/assets/moddle/flowable';
+import Modules from './assets/module/index';
+import useModelerStore from '@/store/modules/modeler';
+import useDialog from '@/hooks/useDialog';
+
+const emit = defineEmits(['closeCallBack', 'saveCallBack']);
+
+const { visible, title, openDialog, closeDialog } = useDialog({
+  title: '缂栬緫娴佺▼'
+});
+const modelerStore = useModelerStore();
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const panelFlag = ref(false);
+const showPanel = ref(true);
+const canvas = ref<HTMLDivElement>();
+const panel = ref<HTMLDivElement>();
+const bpmnModeler = ref<Modeler>();
+const zoom = ref(1);
+const perviewXMLShow = ref(false);
+const perviewSVGShow = ref(false);
+const xmlStr = ref('');
+const svgData = ref('');
+
+const panelBarClick = () => {
+  // 寤惰繜鎵ц锛屽惁鍒欎細瀵艰嚧闈㈡澘鏀惰捣鏃讹紝灞炴�ч潰鏉夸笉鏄剧ず
+  panelFlag.value = !panelFlag.value;
+  setTimeout(() => {
+    showPanel.value = !panelFlag.value;
+  }, 100);
+};
+
+/**
+ * 鍒濆鍖朇anvas
+ */
+const initCanvas = () => {
+  bpmnModeler.value = new BpmnModeler({
+    container: canvas.value,
+    // 閿洏
+    keyboard: {
+      bindTo: window // 鎴栬�厀indow锛屾敞鎰忎笌澶栭儴琛ㄥ崟鐨勯敭鐩樼洃鍚簨浠舵槸鍚﹀啿绐�
+    },
+    propertiesPanel: {
+      parent: panel.value
+    },
+    additionalModules: Modules,
+    moddleExtensions: {
+      flowable: flowableModdle
+    }
+  });
+};
+
+/**
+ * 鍒濆鍖朚odel
+ */
+const initModel = () => {
+  if (modelerStore.getModeler()) {
+    modelerStore.getModeler().destroy();
+    modelerStore.setModeler(undefined);
+  }
+  modelerStore.setModeler(bpmnModeler.value);
+};
+
+/**
+ * 鏂板缓
+ */
+const newDiagram = async () => {
+  await proxy?.$modal.confirm('鏄惁纭鏂板缓');
+  initDiagram();
+};
+
+/**
+ * 鍒濆鍖�
+ */
+const initDiagram = (xml?: string) => {
+  if (!xml) xml = defaultXML;
+  bpmnModeler.value.importXML(xml);
+};
+
+/**
+ * 鑷�傚簲灞忓箷
+ */
+const fitViewport = () => {
+  zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom('fit-viewport');
+  const bbox = document.querySelector<SVGGElement>('.app-containers .viewport').getBBox();
+  const currentViewBox = bpmnModeler.value.get<Canvas>('canvas').viewbox();
+  const elementMid = {
+    x: bbox.x + bbox.width / 2 - 65,
+    y: bbox.y + bbox.height / 2
+  };
+  bpmnModeler.value.get<Canvas>('canvas').viewbox({
+    x: elementMid.x - currentViewBox.width / 2,
+    y: elementMid.y - currentViewBox.height / 2,
+    width: currentViewBox.width,
+    height: currentViewBox.height
+  });
+  zoom.value = (bbox.width / currentViewBox.width) * 1.8;
+};
+/**
+ * 鏀惧ぇ鎴栬�呯缉灏�
+ * @param zoomIn true 鏀惧ぇ | false 缂╁皬
+ */
+const zoomViewport = (zoomIn = true) => {
+  zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom();
+  zoom.value += zoomIn ? 0.1 : -0.1;
+  bpmnModeler.value.get<Canvas>('canvas').zoom(zoom.value);
+};
+
+/**
+ * 涓嬭浇XML
+ */
+const downloadXML = async () => {
+  try {
+    const { xml } = await bpmnModeler.value.saveXML({ format: true });
+    downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml');
+  } catch (e) {
+    proxy?.$modal.msgError(e);
+  }
+};
+
+/**
+ * 涓嬭浇SVG
+ */
+const downloadSVG = async () => {
+  try {
+    const { svg } = await bpmnModeler.value.saveSVG();
+    downloadFile(getProcessElement().name, svg, 'image/svg+xml');
+  } catch (e) {
+    proxy?.$modal.msgError(e);
+  }
+};
+
+/**
+ * XML棰勮
+ */
+const previewXML = async () => {
+  try {
+    const { xml } = await bpmnModeler.value.saveXML({ format: true });
+    xmlStr.value = xml;
+    perviewXMLShow.value = true;
+  } catch (e) {
+    proxy?.$modal.msgError(e);
+  }
+};
+
+/**
+ * SVG棰勮
+ */
+const previewSVG = async () => {
+  try {
+    const { svg } = await bpmnModeler.value.saveSVG();
+    svgData.value = svg;
+    perviewSVGShow.value = true;
+  } catch (e) {
+    proxy?.$modal.msgError(e);
+  }
+};
+
+const curNodeInfo = reactive({
+  curType: '', // 浠诲姟绫诲瀷 鐢ㄦ埛浠诲姟
+  curNode: '',
+  expValue: '' //澶氱敤鎴峰拰閮ㄩ棬瑙掕壊瀹炵幇
+});
+
+const downloadFile = (fileName: string, data: any, type: string) => {
+  const a = document.createElement('a');
+  const url = window.URL.createObjectURL(new Blob([data], { type: type }));
+  a.href = url;
+  a.download = fileName;
+  a.click();
+  window.URL.revokeObjectURL(url);
+};
+
+const getProcessElement = () => {
+  const rootElements = bpmnModeler.value?.getDefinitions().rootElements;
+  for (let i = 0; i < rootElements.length; i++) {
+    if (rootElements[i].$type === 'bpmn:Process') return rootElements[i];
+  }
+};
+
+const getProcess = () => {
+  const element = getProcessElement();
+  return {
+    id: element.id,
+    name: element.name
+  };
+};
+
+const saveXml = async () => {
+  const { xml } = await bpmnModeler.value.saveXML({ format: true });
+  const { svg } = await bpmnModeler.value.saveSVG();
+  const process = getProcess();
+  let data = {
+    xml: xml,
+    svg: svg,
+    key: process.id,
+    name: process.name
+  };
+  emit('saveCallBack', data);
+};
+
+const open = (xml?: string) => {
+  openDialog();
+  nextTick(() => {
+    initDiagram(xml);
+  });
+};
+const close = () => {
+  closeDialog();
+};
+
+onMounted(() => {
+  nextTick(() => {
+    initCanvas();
+    initModel();
+  });
+});
+
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  initDiagram,
+  saveXml,
+  open,
+  close
+});
+</script>
+
+<style lang="scss" scoped>
+.containers {
+  height: 100%;
+  .app-containers {
+    width: 100%;
+    height: 100%;
+    .canvas {
+      width: 100%;
+      height: 100%;
+      background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+');
+    }
+    .el-header {
+      height: 35px;
+      padding: 0;
+    }
+
+    .process-panel {
+      transition: width 0.25s ease-in;
+      .process-panel-bar {
+        width: 34px;
+        height: 40px;
+        .open-bar {
+          width: 34px;
+          line-height: 40px;
+        }
+      }
+      // 鏀惰捣闈㈡澘鏍峰紡
+      &.hide {
+        width: 34px;
+        overflow: hidden;
+        padding: 0;
+        .process-panel-bar {
+          width: 34px;
+          height: 100%;
+          box-sizing: border-box;
+          display: block;
+          text-align: left;
+          line-height: 34px;
+        }
+        .process-panel-bar:hover {
+          background-color: #f5f7fa;
+        }
+      }
+    }
+  }
+}
+pre {
+  margin: 0;
+  height: 100%;
+  max-height: calc(80vh - 32px);
+  overflow-x: hidden;
+  overflow-y: auto;
+  :deep(.hljs) {
+    word-break: break-word;
+    white-space: pre-wrap;
+    padding: 0.5em;
+  }
+}
+
+.open-bar {
+  font-size: 20px;
+  cursor: pointer;
+  text-align: center;
+}
+.process-panel {
+  box-sizing: border-box;
+  padding: 0 8px 0 8px;
+  border-left: 1px solid #eeeeee;
+  box-shadow: #cccccc 0 0 8px;
+  max-height: 100%;
+  width: 480px;
+  height: calc(100vh - 80px);
+  :deep(.el-collapse) {
+    height: calc(100vh - 162px);
+    overflow: auto;
+  }
+}
+
+// 浠诲姟鏍� 閫忔槑搴�
+//:deep(.djs-palette) {
+//  opacity: 0.3;
+//  transition: all 1s;
+//}
+//
+//:deep(.djs-palette:hover) {
+//  opacity: 1;
+//  transition: all 1s;
+//}
+</style>
diff --git a/src/components/BpmnDesign/panel/GatewayPanel.vue b/src/components/BpmnDesign/panel/GatewayPanel.vue
new file mode 100644
index 0000000..46c67b5
--- /dev/null
+++ b/src/components/BpmnDesign/panel/GatewayPanel.vue
@@ -0,0 +1,68 @@
+<template>
+  <div>
+    <el-collapse v-model="currentCollapseItem">
+      <el-collapse-item name="1">
+        <template #title>
+          <div class="collapse__title">
+            <el-icon>
+              <InfoFilled />
+            </el-icon>
+            甯歌
+          </div>
+        </template>
+        <div>
+          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
+            <el-form-item prop="id" label="鑺傜偣 ID">
+              <el-input v-model="formData.id" @change="idChange"> </el-input>
+            </el-form-item>
+            <el-form-item prop="name" label="鑺傜偣鍚嶇О">
+              <el-input v-model="formData.name" @change="nameChange"> </el-input>
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-collapse-item>
+
+      <el-collapse-item name="2">
+        <template #title>
+          <div class="collapse__title">
+            <el-icon>
+              <BellFilled />
+            </el-icon>
+            鎵ц鐩戝惉鍣�
+          </div>
+        </template>
+        <div>
+          <ExecutionListener :element="element"></ExecutionListener>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+<script setup lang="ts">
+import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import { Modeler, ModdleElement } from 'bpmn';
+import { GatewayPanel } from 'bpmnDesign';
+import ExecutionListener from '@/components/BpmnDesign/panel/property/ExecutionListener.vue';
+
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+const { nameChange, idChange } = usePanel({
+  element: toRaw(props.element)
+});
+const { parseData } = useParseElement({
+  element: toRaw(props.element)
+});
+const currentCollapseItem = ref(['1', '2']);
+const formData = ref(parseData<GatewayPanel>());
+
+const formRules = ref<ElFormRules>({
+  processCategory: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/BpmnDesign/panel/ProcessPanel.vue b/src/components/BpmnDesign/panel/ProcessPanel.vue
new file mode 100644
index 0000000..01e8f89
--- /dev/null
+++ b/src/components/BpmnDesign/panel/ProcessPanel.vue
@@ -0,0 +1,71 @@
+<template>
+  <div>
+    <el-collapse v-model="currentCollapseItem">
+      <el-collapse-item name="1">
+        <template #title>
+          <div class="collapse__title">
+            <el-icon>
+              <InfoFilled />
+            </el-icon>
+            甯歌
+          </div>
+        </template>
+        <div>
+          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
+            <el-form-item label="娴佺▼鏍囪瘑" prop="id">
+              <el-input v-model="formData.id" @change="idChange"></el-input>
+            </el-form-item>
+            <el-form-item label="娴佺▼鍚嶇О" prop="name">
+              <el-input v-model="formData.name" @change="nameChange"></el-input>
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-collapse-item>
+
+      <el-collapse-item name="2">
+        <template #title>
+          <div class="collapse__title">
+            <el-icon>
+              <BellFilled />
+            </el-icon>
+            鎵ц鐩戝惉鍣�
+          </div>
+        </template>
+        <div>
+          <ExecutionListener :element="element"></ExecutionListener>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ExecutionListener from './property/ExecutionListener.vue';
+import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import { Modeler, ModdleElement } from 'bpmn';
+import { ProcessPanel } from 'bpmnDesign';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+
+const { parseData } = useParseElement({
+  element: toRaw(props.element)
+});
+const { idChange, nameChange } = usePanel({
+  element: toRaw(props.element)
+});
+const currentCollapseItem = ref(['1', '2']);
+const formData = ref<ProcessPanel>(parseData<ProcessPanel>());
+
+const formRules = ref<ElFormRules>({
+  id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/components/BpmnDesign/panel/SequenceFlowPanel.vue b/src/components/BpmnDesign/panel/SequenceFlowPanel.vue
new file mode 100644
index 0000000..88ce350
--- /dev/null
+++ b/src/components/BpmnDesign/panel/SequenceFlowPanel.vue
@@ -0,0 +1,94 @@
+<template>
+  <div>
+    <el-collapse v-model="currentCollapseItem">
+      <el-collapse-item name="1">
+        <template #title>
+          <div class="collapse__title">
+            <el-icon>
+              <InfoFilled />
+            </el-icon>
+            甯歌
+          </div>
+        </template>
+        <div>
+          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px">
+            <el-form-item prop="id" label="鑺傜偣 ID">
+              <el-input v-model="formData.id" @change="idChange"> </el-input>
+            </el-form-item>
+            <el-form-item prop="name" label="鑺傜偣鍚嶇О">
+              <el-input v-model="formData.name" @change="nameChange"> </el-input>
+            </el-form-item>
+            <el-form-item prop="conditionExpression" label="璺宠浆鏉′欢">
+              <el-input v-model="formData.conditionExpressionValue" @change="conditionExpressionChange"> </el-input>
+            </el-form-item>
+            <el-form-item prop="skipExpression" label="璺宠繃琛ㄨ揪寮�">
+              <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input>
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-collapse-item>
+
+      <el-collapse-item name="2">
+        <template #title>
+          <div class="collapse__title">
+            <el-icon>
+              <BellFilled />
+            </el-icon>
+            鎵ц鐩戝惉鍣�
+          </div>
+        </template>
+        <div>
+          <ExecutionListener :element="element"></ExecutionListener>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+<script setup lang="ts">
+import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import { Modeler, ModdleElement } from 'bpmn';
+import { SequenceFlowPanel } from 'bpmnDesign';
+import useModelerStore from '@/store/modules/modeler';
+
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+const { nameChange, idChange, updateProperties } = usePanel({
+  element: toRaw(props.element)
+});
+const { parseData } = useParseElement({
+  element: toRaw(props.element)
+});
+const moddle = useModelerStore().getModdle();
+const currentCollapseItem = ref(['1', '2']);
+const formData = ref(parseData<SequenceFlowPanel>());
+
+const formRules = ref<ElFormRules>({
+  processCategory: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+
+const conditionExpressionChange = (val: string) => {
+  if (val) {
+    const newCondition = moddle.create('bpmn:FormalExpression', { body: val });
+    updateProperties({ conditionExpression: newCondition });
+  } else {
+    updateProperties({ conditionExpression: null });
+  }
+};
+
+const skipExpressionChange = (val: string) => {
+  updateProperties({ 'flowable:skipExpression': val });
+};
+
+onBeforeMount(() => {
+  if (formData.value.conditionExpression) {
+    formData.value.conditionExpressionValue = formData.value.conditionExpression.body;
+  }
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/BpmnDesign/panel/StartEndPanel.vue b/src/components/BpmnDesign/panel/StartEndPanel.vue
new file mode 100644
index 0000000..9cd0efc
--- /dev/null
+++ b/src/components/BpmnDesign/panel/StartEndPanel.vue
@@ -0,0 +1,40 @@
+<template>
+  <div>
+    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
+      <el-form-item prop="id" label="鑺傜偣 ID">
+        <el-input v-model="formData.id" @change="idChange"> </el-input>
+      </el-form-item>
+      <el-form-item prop="name" label="鑺傜偣鍚嶇О">
+        <el-input v-model="formData.name" @change="nameChange"> </el-input>
+      </el-form-item>
+      <el-form-item label="鎵ц鐩戝惉鍣�" style="margin-bottom: 0"> </el-form-item>
+      <ExecutionListener :element="element"></ExecutionListener>
+    </el-form>
+  </div>
+</template>
+<script setup lang="ts">
+import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import { Modeler, ModdleElement } from 'bpmn';
+import { StartEndPanel } from 'bpmnDesign';
+
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+const { nameChange, idChange } = usePanel({
+  element: toRaw(props.element)
+});
+const { parseData } = useParseElement({
+  element: toRaw(props.element)
+});
+
+const formData = ref(parseData<StartEndPanel>());
+
+const formRules = ref<ElFormRules>({
+  id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/BpmnDesign/panel/TaskPanel.vue b/src/components/BpmnDesign/panel/TaskPanel.vue
new file mode 100644
index 0000000..267ad30
--- /dev/null
+++ b/src/components/BpmnDesign/panel/TaskPanel.vue
@@ -0,0 +1,467 @@
+<template>
+  <div>
+    <el-form ref="formRef" size="default" :model="formData" :rules="formRules" label-width="100px">
+      <el-collapse v-model="currentCollapseItem">
+        <el-collapse-item name="1">
+          <template #title>
+            <div class="collapse__title">
+              <el-icon>
+                <InfoFilled />
+              </el-icon>
+              甯歌
+            </div>
+          </template>
+          <div>
+            <el-form-item prop="id" label="鑺傜偣 ID">
+              <el-input v-model="formData.id" @change="idChange"> </el-input>
+            </el-form-item>
+            <el-form-item prop="name" label="鑺傜偣鍚嶇О">
+              <el-input v-model="formData.name" @change="nameChange"> </el-input>
+            </el-form-item>
+            <el-form-item v-if="showConfig.skipExpression" prop="skipExpression" label="璺宠繃琛ㄨ揪寮�">
+              <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input>
+            </el-form-item>
+          </div>
+        </el-collapse-item>
+        <el-collapse-item name="2">
+          <template #title>
+            <div class="collapse__title">
+              <el-icon>
+                <Checked />
+              </el-icon>
+              浠诲姟
+            </div>
+          </template>
+          <div>
+            <el-form-item v-if="showConfig.async" prop="sync" label="鏄惁寮傛">
+              <el-switch v-model="formData.async" inline-prompt active-text="鏄�" inactive-text="鍚�" @change="syncChange" />
+            </el-form-item>
+
+            <el-tabs tab-position="left" class="demo-tabs" @tab-click="taskTabClick">
+              <el-tab-pane label="韬唤瀛樺偍">
+                <el-form-item label="鍒嗛厤浜哄憳">
+                  <el-input v-model="assignee.userName" disabled>
+                    <template #append>
+                      <el-button icon="Search" type="primary" @click="openSingleUserSelect" />
+                    </template>
+                  </el-input>
+                </el-form-item>
+                <el-form-item label="鍊欓�変汉鍛�">
+                  <el-badge :value="selectUserLength" :max="99">
+                    <el-button size="small" type="primary" @click="openUserSelect">閫夋嫨浜哄憳</el-button>
+                  </el-badge>
+                </el-form-item>
+                <el-form-item label="鍊欓�夌粍">
+                  <el-badge :value="selectRoleLength" :max="99">
+                    <el-button size="small" type="primary" @click="openRoleSelect">閫夋嫨缁�</el-button>
+                  </el-badge>
+                </el-form-item>
+              </el-tab-pane>
+
+              <el-tab-pane label="鍥哄畾鍊�">
+                <el-form-item prop="auditUserType" label="鍒嗛厤绫诲瀷">
+                  <el-select v-model="formData.allocationType">
+                    <el-option v-for="item in AllocationTypeSelect" :key="item.id" :value="item.value" :label="item.label"> </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item v-if="formData.allocationType === AllocationTypeEnum.USER" label="鍒嗛厤浜哄憳">
+                  <el-input v-model="formData.fixedAssignee" @change="fixedAssigneeChange">
+                    <template #append>
+                      <el-button icon="Search" size="small" type="primary" @click="proxy.$modal.msgWarning('寮�鍙戜腑銆傘�傘�傘�傘�傘��')" />
+                    </template>
+                  </el-input>
+                </el-form-item>
+                <div v-if="formData.allocationType === AllocationTypeEnum.CANDIDATE">
+                  <el-form-item label="鍊欓�変汉鍛�">
+                    <el-badge :value="selectUserLength" :max="99">
+                      <el-button size="small" type="primary" @click="openUserSelect">閫夋嫨浜哄憳</el-button>
+                    </el-badge>
+                  </el-form-item>
+                  <el-form-item label="鍊欓�夌粍">
+                    <el-badge :value="selectRoleLength" :max="99">
+                      <el-button size="small" type="primary" @click="openRoleSelect">閫夋嫨缁�</el-button>
+                    </el-badge>
+                  </el-form-item>
+                </div>
+                <el-form-item v-if="formData.allocationType === AllocationTypeEnum.SPECIFY && showConfig.specifyDesc" style="">
+                  <el-radio-group v-model="formData.specifyDesc" class="ml-4">
+                    <el-radio v-for="item in SpecifyDesc" :key="item.id" :label="item.value" size="large">{{ item.label }}</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-tab-pane>
+            </el-tabs>
+
+            <el-form-item v-if="showConfig.dueDate" prop="dueDate" label="鍒版湡鏃堕棿">
+              <el-input v-model="formData.dueDate" clearable @change="dueDateChange" @click="openDueDate">
+                <template #append>
+                  <el-button icon="Search" type="primary" @click="openDueDate" />
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item v-if="showConfig.priority" prop="priority" label="浼樺厛绾�">
+              <el-input-number v-model="formData.priority" :min="0" @change="priorityChange"> </el-input-number>
+            </el-form-item>
+          </div>
+        </el-collapse-item>
+        <el-collapse-item name="3">
+          <template #title>
+            <div class="collapse__title">
+              <el-icon>
+                <HelpFilled />
+              </el-icon>
+              澶氬疄渚�
+            </div>
+          </template>
+          <div>
+            <el-form-item label="澶氬疄渚嬬被鍨�">
+              <el-select v-model="formData.multiInstanceType" @change="multiInstanceTypeChange">
+                <el-option v-for="item in MultiInstanceType" :key="item.id" :value="item.value" :label="item.label"> </el-option>
+              </el-select>
+            </el-form-item>
+
+            <div v-if="formData.multiInstanceType !== MultiInstanceTypeEnum.NONE">
+              <el-form-item label="闆嗗悎">
+                <template #label>
+                  <span>
+                    闆嗗悎
+                    <el-tooltip placement="top">
+                      <el-icon><QuestionFilled /></el-icon>
+                      <template #content>
+                        灞炴�т細浣滀负琛ㄨ揪寮忚繘琛岃В鏋愩�傚鏋滆〃杈惧紡瑙f瀽涓哄瓧绗︿覆鑰屼笉鏄竴涓泦鍚堬紝<br />
+                        涓嶈鏄洜涓烘湰韬厤缃殑灏辨槸闈欐�佸瓧绗︿覆鍊硷紝杩樻槸琛ㄨ揪寮忚绠楃粨鏋滀负瀛楃涓诧紝<br />
+                        杩欎釜瀛楃涓查兘浼氳褰撳仛鍙橀噺鍚嶏紝骞朵粠娴佺▼鍙橀噺涓敤浜庤幏鍙栧疄闄呯殑闆嗗悎銆�
+                      </template>
+                    </el-tooltip>
+                  </span>
+                </template>
+                <el-input v-model="formData.collection" @change="collectionChange"></el-input>
+              </el-form-item>
+              <el-form-item label="鍏冪礌鍙橀噺">
+                <template #label>
+                  <span>
+                    鍏冪礌鍙橀噺
+                    <el-tooltip placement="top">
+                      <el-icon><QuestionFilled /></el-icon>
+                      <template #content>
+                        姣忓垱寤轰竴涓敤鎴蜂换鍔″墠锛屽厛浠ヨ鍏冪礌鍙橀噺涓簂abel锛岄泦鍚堜腑鐨勪竴椤逛负value锛�<br />
+                        鍒涘缓锛堝眬閮級娴佺▼鍙橀噺锛岃灞�閮ㄦ祦绋嬪彉閲忚鐢ㄤ簬鎸囨淳鐢ㄦ埛浠诲姟銆�<br />
+                        涓�鑸潵璇达紝璇ュ瓧绗︿覆搴斾笌鎸囧畾浜哄憳鍙橀噺鐩稿悓銆�
+                      </template>
+                    </el-tooltip>
+                  </span>
+                </template>
+                <el-input v-model="formData.elementVariable" @change="elementVariableChange"> </el-input>
+              </el-form-item>
+              <el-form-item label="瀹屾垚鏉′欢">
+                <template #label>
+                  <span>
+                    瀹屾垚鏉′欢
+                    <el-tooltip placement="top">
+                      <el-icon><QuestionFilled /></el-icon>
+                      <template #content>
+                        澶氬疄渚嬫椿鍔ㄥ湪鎵�鏈夊疄渚嬮兘瀹屾垚鏃剁粨鏉燂紝鐒惰�屼篃鍙互鎸囧畾涓�涓〃杈惧紡锛屽湪姣忎釜瀹炰緥<br />
+                        缁撴潫鏃惰繘琛岃绠椼�傚綋琛ㄨ揪寮忚绠椾负true鏃讹紝灏嗛攢姣佹墍鏈夊墿浣欑殑瀹炰緥锛屽苟缁撴潫澶氬疄渚�<br />
+                        娲诲姩锛岀户缁墽琛屾祦绋嬨�備緥濡� ${nrOfCompletedInstances/nrOfInstances >= 0.6 }锛�<br />
+                        琛ㄧず褰撲换鍔″畬鎴�60%鏃讹紝璇ヨ妭鐐瑰氨绠楀畬鎴�
+                      </template>
+                    </el-tooltip>
+                  </span>
+                </template>
+                <el-input v-model="formData.completionCondition" @change="completionConditionChange"> </el-input>
+              </el-form-item>
+            </div>
+          </div>
+        </el-collapse-item>
+        <el-collapse-item v-if="showConfig.taskListener" name="4">
+          <template #title>
+            <div class="collapse__title">
+              <el-icon>
+                <BellFilled />
+              </el-icon>
+              浠诲姟鐩戝惉鍣�
+            </div>
+          </template>
+          <div>
+            <TaskListener v-if="showConfig.taskListener" :element="element"></TaskListener>
+          </div>
+        </el-collapse-item>
+        <el-collapse-item v-if="showConfig.executionListener" name="5">
+          <template #title>
+            <div class="collapse__title">
+              <el-icon>
+                <BellFilled />
+              </el-icon>
+              鎵ц鐩戝惉鍣�
+            </div>
+          </template>
+          <div>
+            <ExecutionListener v-if="showConfig.executionListener" :element="element"></ExecutionListener>
+          </div>
+        </el-collapse-item>
+
+        <el-form-item v-if="showConfig.isForCompensation" prop="isForCompensation" label="鏄惁涓鸿ˉ鍋�">
+          <el-switch v-model="formData.isForCompensation" inline-prompt active-text="鏄�" inactive-text="鍚�" />
+        </el-form-item>
+        <el-form-item v-if="showConfig.triggerServiceTask" prop="triggerServiceTask" label="鏈嶅姟浠诲姟鍙Е鍙�">
+          <el-switch v-model="formData.triggerServiceTask" inline-prompt active-text="鏄�" inactive-text="鍚�" />
+        </el-form-item>
+        <el-form-item v-if="showConfig.autoStoreVariables" prop="autoStoreVariables" label="鑷姩瀛樺偍鍙橀噺">
+          <el-switch v-model="formData.autoStoreVariables" inline-prompt active-text="鏄�" inactive-text="鍚�" />
+        </el-form-item>
+        <el-form-item v-if="showConfig.ruleVariablesInput" prop="skipExpression" label="杈撳叆鍙橀噺">
+          <el-input v-model="formData.ruleVariablesInput"> </el-input>
+        </el-form-item>
+        <el-form-item v-if="showConfig.exclude" prop="exclude" label="鎺掗櫎">
+          <el-switch v-model="formData.exclude" inline-prompt active-text="鏄�" inactive-text="鍚�" />
+        </el-form-item>
+        <el-form-item v-if="showConfig.class" prop="class" label="绫�">
+          <el-input v-model="formData.class"> </el-input>
+        </el-form-item>
+      </el-collapse>
+    </el-form>
+    <UserSelect ref="userSelectRef" :data="formData.candidateUsers" @confirm-call-back="userSelectCallBack"></UserSelect>
+    <UserSelect ref="singleUserSelectRef" :data="formData.assignee" :multiple="false" @confirm-call-back="singleUserSelectCallBack"></UserSelect>
+    <RoleSelect ref="roleSelectRef" :data="formData.candidateGroups" @confirm-call-back="roleSelectCallBack"></RoleSelect>
+    <DueDate ref="dueDateRef" v-model="formData.dueDate" :data="formData.dueDate" @confirm-call-back="dueDateCallBack"></DueDate>
+  </div>
+</template>
+<script setup lang="ts">
+import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import UserSelect from '@/components/UserSelect';
+import RoleSelect from '@/components/RoleSelect';
+import DueDate from '@/components/BpmnDesign/panel/property/DueDate.vue';
+import { ModdleElement } from 'bpmn';
+import { TaskPanel } from 'bpmnDesign';
+import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums';
+import { UserVO } from '@/api/system/user/types';
+import { RoleVO } from '@/api/system/role/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+const { showConfig, nameChange, idChange, updateProperties, getExtensionElements, createModdleElement } = usePanel({
+  element: toRaw(props.element)
+});
+const { parseData } = useParseElement({
+  element: toRaw(props.element)
+});
+
+const initFormData = {
+  id: '',
+  name: '',
+  dueDate: '',
+  multiInstanceType: MultiInstanceTypeEnum.NONE,
+  allocationType: AllocationTypeEnum.USER,
+  specifyDesc: SpecifyDescEnum.SPECIFY_SINGLE
+};
+const formData = ref({ ...initFormData, ...parseData<TaskPanel>() });
+const assignee = ref<Partial<UserVO>>({
+  userName: ''
+});
+const currentCollapseItem = ref(['1', '2']);
+const userSelectRef = ref<InstanceType<typeof UserSelect>>();
+const singleUserSelectRef = ref<InstanceType<typeof UserSelect>>();
+const roleSelectRef = ref<InstanceType<typeof RoleSelect>>();
+const dueDateRef = ref<InstanceType<typeof DueDate>>();
+
+const isMultiple = ref(true);
+const openUserSelect = () => {
+  userSelectRef.value.open();
+};
+const openSingleUserSelect = () => {
+  singleUserSelectRef.value.open();
+};
+const openRoleSelect = () => {
+  roleSelectRef.value.open();
+};
+const openDueDate = (e) => {
+  dueDateRef.value.openDialog();
+};
+
+const singleUserSelectCallBack = (data: UserVO[]) => {
+  const user: UserVO = data.length !== 0 ? data[0] : undefined;
+  updateProperties({ 'flowable:assignee': user?.userId });
+  assignee.value = user ? user : { userName: '' };
+  formData.value.assignee = String(user?.userId);
+  let extensionElements = getExtensionElements();
+  extensionElements.values = extensionElements.get('values').filter((item) => item.$type !== 'flowable:extAssignee');
+  if (user) {
+    const extAssigneeElement = createModdleElement('flowable:extAssignee', { body: '' }, extensionElements);
+    extensionElements.get('values').push(extAssigneeElement);
+    extAssigneeElement.body = JSON.stringify({ userName: user.userName, userId: user.userId });
+  }
+  if (extensionElements.values.length === 0) {
+    extensionElements = undefined;
+  }
+  updateProperties({ extensionElements: extensionElements });
+};
+const userSelectCallBack = (data: UserVO[]) => {
+  let extensionElements = getExtensionElements();
+  extensionElements.values = extensionElements.values.filter((item) => item.$type !== 'flowable:extCandidateUsers');
+  if (data.length === 0) {
+    formData.value.candidateUsers = undefined;
+    updateProperties({ 'flowable:candidateUsers': undefined });
+  } else {
+    const userIds = data.map((item) => item.userId).join(',');
+    formData.value.candidateUsers = userIds;
+    updateProperties({ 'flowable:candidateUsers': userIds });
+    const extCandidateUsersElement = createModdleElement('flowable:extCandidateUsers', { body: '' }, extensionElements);
+    extensionElements.values.push(extCandidateUsersElement);
+    const users = data.map((item) => {
+      return {
+        userId: item.userId,
+        userName: item.userName
+      };
+    });
+    extCandidateUsersElement.body = JSON.stringify(users);
+  }
+  if (extensionElements.values.length === 0) {
+    extensionElements = undefined;
+  }
+  updateProperties({ extensionElements: extensionElements });
+};
+const roleSelectCallBack = (data: RoleVO[]) => {
+  if (data.length === 0) {
+    formData.value.candidateGroups = '';
+    updateProperties({ 'flowable:candidateGroups': undefined });
+  } else {
+    const roleIds = data.map((item) => item.roleId).join(',');
+    formData.value.candidateGroups = roleIds;
+    updateProperties({ 'flowable:candidateGroups': roleIds });
+  }
+};
+const dueDateCallBack = (data: string) => {
+  updateProperties({ 'flowable:dueDate': data });
+};
+
+const taskTabClick = (e) => {
+  formData.value.candidateGroups = '';
+  formData.value.candidateUsers = '';
+  formData.value.assignee = '';
+  assignee.value = {};
+};
+
+const syncChange = (newVal) => {
+  updateProperties({ 'flowable:async': newVal });
+};
+const skipExpressionChange = (newVal) => {
+  updateProperties({ 'flowable:skipExpression': newVal && newVal.length > 0 ? newVal : undefined });
+};
+const priorityChange = (newVal) => {
+  updateProperties({ 'flowable:priority': newVal });
+};
+const fixedAssigneeChange = (newVal) => {
+  updateProperties({ 'flowable:assignee': newVal && newVal.length > 0 ? newVal : undefined });
+};
+const multiInstanceTypeChange = (newVal) => {
+  if (newVal !== MultiInstanceTypeEnum.NONE) {
+    let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
+    if (!loopCharacteristics) {
+      loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
+    }
+    loopCharacteristics.isSequential = newVal === MultiInstanceTypeEnum.SERIAL;
+    updateProperties({ loopCharacteristics: loopCharacteristics });
+  } else {
+    updateProperties({ loopCharacteristics: undefined });
+  }
+};
+const collectionChange = (newVal) => {
+  let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
+  if (!loopCharacteristics) {
+    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
+  }
+  loopCharacteristics.collection = newVal && newVal.length > 0 ? newVal : undefined;
+  updateProperties({ loopCharacteristics: loopCharacteristics });
+};
+const elementVariableChange = (newVal) => {
+  let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
+  if (!loopCharacteristics) {
+    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
+  }
+  loopCharacteristics.elementVariable = newVal && newVal.length > 0 ? newVal : undefined;
+  updateProperties({ loopCharacteristics: loopCharacteristics });
+};
+const completionConditionChange = (newVal) => {
+  let loopCharacteristics = props.element.businessObject.get<ModdleElement>('loopCharacteristics');
+  if (!loopCharacteristics) {
+    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
+  }
+  if (newVal && newVal.length > 0) {
+    if (!loopCharacteristics.completionCondition) {
+      loopCharacteristics.completionCondition = createModdleElement('bpmn:Expression', { body: newVal }, loopCharacteristics);
+    } else {
+      loopCharacteristics.completionCondition.body = newVal;
+    }
+  } else {
+    loopCharacteristics.completionCondition = undefined;
+  }
+  updateProperties({ loopCharacteristics: loopCharacteristics });
+};
+const dueDateChange = (newVal) => {
+  updateProperties({ 'flowable:dueDate': newVal && newVal.length > 0 ? newVal : undefined });
+};
+const selectUserLength = computed(() => {
+  if (formData.value.candidateUsers) {
+    return formData.value.candidateUsers.split(',').length;
+  } else {
+    return 0;
+  }
+});
+const selectRoleLength = computed(() => {
+  if (formData.value.candidateGroups) {
+    return formData.value.candidateGroups.split(',').length;
+  } else {
+    return 0;
+  }
+});
+
+onBeforeMount(() => {
+  const extensionElements = getExtensionElements(false);
+  if (extensionElements && extensionElements.get('values')) {
+    let extAssigneeElement = extensionElements.get('values').find((item) => item.$type === 'flowable:extAssignee');
+    if (extAssigneeElement) {
+      assignee.value = JSON.parse(extAssigneeElement.body);
+    }
+  }
+
+  if (formData.value.loopCharacteristics) {
+    const loopCharacteristics = formData.value.loopCharacteristics;
+    formData.value.collection = loopCharacteristics.collection || '';
+    formData.value.elementVariable = loopCharacteristics.elementVariable || '';
+    formData.value.completionCondition = loopCharacteristics.completionCondition?.body || '';
+    formData.value.multiInstanceType = loopCharacteristics.isSequential ? MultiInstanceTypeEnum.SERIAL : MultiInstanceTypeEnum.PARALLEL;
+  }
+
+  if (formData.value.assignee) {
+    formData.value.fixedAssignee = formData.value.assignee;
+  }
+});
+
+const formRules = ref<ElFormRules>({
+  id: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+
+const AllocationTypeSelect = [
+  { id: 'b9cdf970-dd91-47c0-819f-42a7010ca2a6', label: '鎸囧畾浜哄憳', value: AllocationTypeEnum.USER },
+  { id: '3f7ccbcd-c464-4602-bb9d-e96649d10585', label: '鍊欓�変汉鍛�', value: AllocationTypeEnum.CANDIDATE },
+  { id: 'c49065e0-7f2d-4c09-aedb-ab2d47d9a454', label: '鍙戣捣浜鸿嚜宸�', value: AllocationTypeEnum.YOURSELF },
+  { id: '6ef40a03-7e9a-4898-89b2-c88fe9064542', label: '鍙戣捣浜烘寚瀹�', value: AllocationTypeEnum.SPECIFY }
+];
+const SpecifyDesc = [
+  { id: 'fa253b34-4335-458c-b1bc-b039e2a2b7a6', label: '鎸囧畾涓�涓汉', value: 'specifySingle' },
+  { id: '7365ff54-2e05-4312-9bfb-0b8edd779c5b', label: '鎸囧畾澶氫釜浜�', value: 'specifyMultiple' }
+];
+
+const MultiInstanceType = [
+  { id: '373d4b81-a0d1-4eb8-8685-0d2fb1b468e2', label: '鏃�', value: MultiInstanceTypeEnum.NONE },
+  { id: 'b5acea7c-b7e5-46b0-8778-390db091bdab', label: '涓茶', value: MultiInstanceTypeEnum.SERIAL },
+  { id: 'b4f0c683-1ccc-43c4-8380-e1b998986caf', label: '骞惰', value: MultiInstanceTypeEnum.PARALLEL }
+];
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/BpmnDesign/panel/index.vue b/src/components/BpmnDesign/panel/index.vue
new file mode 100644
index 0000000..aa39af1
--- /dev/null
+++ b/src/components/BpmnDesign/panel/index.vue
@@ -0,0 +1,104 @@
+<template>
+  <div ref="propertyPanel">
+    <div v-if="nodeName" class="node-name">{{ nodeName }}</div>
+    <component :is="component" v-if="element" :element="element" />
+  </div>
+</template>
+<script setup lang="ts" name="PropertyPanel">
+import { NodeName } from '../assets/lang/zh';
+import TaskPanel from './TaskPanel.vue';
+import ProcessPanel from './ProcessPanel.vue';
+import StartEndPanel from './StartEndPanel.vue';
+import GatewayPanel from './GatewayPanel.vue';
+import SequenceFlowPanel from './SequenceFlowPanel.vue';
+import { Modeler, ModdleElement } from 'bpmn';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+interface propsType {
+  modeler: Modeler;
+}
+const props = withDefaults(defineProps<propsType>(), {});
+
+const element = ref<ModdleElement>();
+const processElement = ref<ModdleElement>();
+
+const startEndType = ['bpmn:IntermediateThrowEvent', 'bpmn:StartEvent', 'bpmn:EndEvent'];
+const taskType = [
+  'bpmn:UserTask',
+  'bpmn:Task',
+  'bpmn:SendTask',
+  'bpmn:ReceiveTask',
+  'bpmn:ManualTask',
+  'bpmn:BusinessRuleTask',
+  'bpmn:ServiceTask',
+  'bpmn:ScriptTask'
+];
+const sequenceType = ['bpmn:SequenceFlow'];
+const gatewayType = ['bpmn:InclusiveGateway', 'bpmn:ExclusiveGateway', 'bpmn:ParallelGateway', 'bpmn:EventBasedGateway', 'bpmn:ComplexGateway'];
+const processType = ['bpmn:Process'];
+
+// 缁勪欢璁$畻
+const component = computed(() => {
+  if (!element.value) return null;
+  const type = element.value.type;
+  if (startEndType.includes(type)) return StartEndPanel;
+  if (taskType.includes(type)) return TaskPanel;
+  if (sequenceType.includes(type)) return SequenceFlowPanel;
+  if (gatewayType.includes(type)) return GatewayPanel;
+  if (processType.includes(type)) return ProcessPanel;
+  return proxy?.$modal.msgWarning('闈㈡澘寮�鍙戜腑....');
+});
+
+const nodeName = computed(() => {
+  if (element.value) {
+    const bizObj = element.value.businessObject;
+    const type = bizObj?.eventDefinitions && bizObj?.eventDefinitions.length > 0 ? bizObj.eventDefinitions[0].$type : bizObj.$type;
+    return NodeName[type] || type;
+  }
+});
+
+const handleModeler = () => {
+  props.modeler.on('root.added', (e: any) => {
+    element.value = null;
+    if (e.element.type === 'bpmn:Process') {
+      nextTick(() => {
+        element.value = e.element;
+        processElement.value = e.element;
+      });
+    }
+  });
+  props.modeler.on('element.click', (e: any) => {
+    if (e.element.type === 'bpmn:Process') {
+      nextTick(() => {
+        element.value = e.element;
+        processElement.value = e.element;
+      });
+    }
+  });
+  props.modeler.on('selection.changed', (e: any) => {
+    // 鍏堢粰null涓轰簡璁﹙ue鍒锋柊
+    element.value = null;
+    const newElement = e.newSelection[0];
+    if (newElement) {
+      nextTick(() => {
+        element.value = newElement;
+      });
+    } else {
+      nextTick(() => {
+        element.value = processElement.value;
+      });
+    }
+  });
+};
+
+onMounted(() => {
+  handleModeler();
+});
+</script>
+
+<style scoped lang="scss">
+.node-name {
+  font-size: 16px;
+  font-weight: bold;
+  padding: 10px;
+}
+</style>
diff --git a/src/components/BpmnDesign/panel/property/DueDate.vue b/src/components/BpmnDesign/panel/property/DueDate.vue
new file mode 100644
index 0000000..4825a1c
--- /dev/null
+++ b/src/components/BpmnDesign/panel/property/DueDate.vue
@@ -0,0 +1,252 @@
+<template>
+  <div>
+    <el-dialog v-model="visible" :title="title" width="600px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="灏忔椂">
+          <el-radio-group v-model="hourValue" @change="hourChange">
+            <el-radio-button label="4" />
+            <el-radio-button label="8" />
+            <el-radio-button label="12" />
+            <el-radio-button label="24" />
+            <el-radio-button label="鑷畾涔�" />
+            <el-input-number v-show="hourValue === '鑷畾涔�'" v-model="customHourValue" :min="1" @change="customHourValueChange"></el-input-number>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="澶�">
+          <el-radio-group v-model="dayValue" @change="dayChange">
+            <el-radio-button label="1" />
+            <el-radio-button label="2" />
+            <el-radio-button label="3" />
+            <el-radio-button label="4" />
+            <el-radio-button label="鑷畾涔�" />
+            <el-input-number v-show="dayValue === '鑷畾涔�'" v-model="customDayValue" :min="1" @change="customDayValueChange"></el-input-number>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="鍛�">
+          <el-radio-group v-model="weekValue" @change="weekChange">
+            <el-radio-button label="1" />
+            <el-radio-button label="2" />
+            <el-radio-button label="3" />
+            <el-radio-button label="4" />
+            <el-radio-button label="鑷畾涔�" />
+            <el-input-number v-show="weekValue === '鑷畾涔�'" v-model="customWeekValue" :min="1" @change="customWeekValueChange"></el-input-number>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="鏈�">
+          <el-radio-group v-model="monthValue" @change="monthChange">
+            <el-radio-button label="1" />
+            <el-radio-button label="2" />
+            <el-radio-button label="3" />
+            <el-radio-button label="4" />
+            <el-radio-button label="鑷畾涔�" />
+            <el-input-number v-show="monthValue === '鑷畾涔�'" v-model="customMonthValue" :min="1" @change="customMonthValueChange"></el-input-number>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div>
+          <el-button @click="closeDialog">鍙栨秷</el-button>
+          <el-button type="primary" @click="confirm">纭畾</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import useDialog from '@/hooks/useDialog';
+
+interface PropType {
+  modelValue?: string;
+  data?: string;
+}
+const prop = withDefaults(defineProps<PropType>(), {
+  modelValue: '',
+  data: ''
+});
+const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
+
+const { title, visible, openDialog, closeDialog } = useDialog({
+  title: '璁剧疆浠诲姟鍒版湡鏃堕棿'
+});
+const formValue = ref();
+const valueType = ref();
+
+const hourValue = ref('');
+const dayValue = ref('');
+const weekValue = ref('');
+const monthValue = ref('');
+
+const customHourValue = ref(1);
+const customDayValue = ref(1);
+const customWeekValue = ref(1);
+const customMonthValue = ref(1);
+
+const hourValueConst = ['4', '8', '12', '24'];
+const dayAndWeekAndMonthValueConst = ['1', '2', '3', '4'];
+
+const initValue = () => {
+  formValue.value = prop.data;
+  if (prop.data) {
+    const lastStr = prop.data.substring(prop.data.length - 1);
+    if (lastStr === 'H') {
+      const hourValueValue = prop.data.substring(2, prop.data.length - 1);
+      if (hourValueConst.includes(hourValueValue)) {
+        hourValue.value = hourValueValue;
+      } else {
+        hourValue.value = '鑷畾涔�';
+        customHourValue.value = Number(hourValueValue);
+      }
+    }
+    const dayAndWeekAndMonthValue = prop.data.substring(1, prop.data.length - 1);
+    if (lastStr === 'D') {
+      if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) {
+        dayValue.value = dayAndWeekAndMonthValue;
+      } else {
+        dayValue.value = '鑷畾涔�';
+        customDayValue.value = Number(dayAndWeekAndMonthValue);
+      }
+    }
+    if (lastStr === 'W') {
+      if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) {
+        weekValue.value = dayAndWeekAndMonthValue;
+      } else {
+        weekValue.value = '鑷畾涔�';
+        customWeekValue.value = Number(dayAndWeekAndMonthValue);
+      }
+    }
+    if (lastStr === 'M') {
+      if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) {
+        monthValue.value = dayAndWeekAndMonthValue;
+      } else {
+        monthValue.value = '鑷畾涔�';
+        customMonthValue.value = Number(dayAndWeekAndMonthValue);
+      }
+    }
+  }
+};
+
+const confirm = () => {
+  emit('update:modelValue', formValue.value);
+  emit('confirmCallBack', formValue.value);
+  closeDialog();
+};
+
+const customHourValueChange = (customHourValue) => {
+  formValue.value = `PT${customHourValue}H`;
+
+  dayValue.value = '';
+  weekValue.value = '';
+  monthValue.value = '';
+  customDayValue.value = 1;
+  customWeekValue.value = 1;
+  customMonthValue.value = 1;
+};
+const customDayValueChange = (customDayValue) => {
+  formValue.value = `P${customDayValue}D`;
+  hourValue.value = '';
+  weekValue.value = '';
+  monthValue.value = '';
+
+  customHourValue.value = 1;
+  customWeekValue.value = 1;
+  customMonthValue.value = 1;
+};
+
+const customWeekValueChange = (customWeekValue) => {
+  formValue.value = `P${customWeekValue}W`;
+  hourValue.value = '';
+  dayValue.value = '';
+  monthValue.value = '';
+
+  customHourValue.value = 1;
+  customDayValue.value = 1;
+  customMonthValue.value = 1;
+};
+
+const customMonthValueChange = (customMonthValue) => {
+  formValue.value = `P${customMonthValue}M`;
+  hourValue.value = '';
+  dayValue.value = '';
+  weekValue.value = '';
+
+  customHourValue.value = 1;
+  customDayValue.value = 1;
+  customWeekValue.value = 1;
+};
+
+const hourChange = (hourValue) => {
+  if (hourValue === '鑷畾涔�') {
+    formValue.value = `PT${customHourValue.value}H`;
+  } else {
+    formValue.value = `PT${hourValue}H`;
+  }
+
+  dayValue.value = '';
+  weekValue.value = '';
+  monthValue.value = '';
+  customDayValue.value = 1;
+  customWeekValue.value = 1;
+  customMonthValue.value = 1;
+};
+const dayChange = (dayValue) => {
+  if (dayValue === '鑷畾涔�') {
+    formValue.value = `P${customDayValue.value}D`;
+  } else {
+    formValue.value = `P${dayValue}D`;
+  }
+
+  hourValue.value = '';
+  weekValue.value = '';
+  monthValue.value = '';
+
+  customHourValue.value = 1;
+  customWeekValue.value = 1;
+  customMonthValue.value = 1;
+};
+const weekChange = (weekValue) => {
+  if (weekValue === '鑷畾涔�') {
+    formValue.value = `P${customWeekValue.value}W`;
+  } else {
+    formValue.value = `P${weekValue}W`;
+  }
+
+  hourValue.value = '';
+  dayValue.value = '';
+  monthValue.value = '';
+
+  customHourValue.value = 1;
+  customDayValue.value = 1;
+  customMonthValue.value = 1;
+};
+const monthChange = (monthValue) => {
+  if (monthValue === '鑷畾涔�') {
+    formValue.value = `P${customMonthValue.value}M`;
+  } else {
+    formValue.value = `P${monthValue}M`;
+  }
+
+  hourValue.value = '';
+  dayValue.value = '';
+  weekValue.value = '';
+
+  customHourValue.value = 1;
+  customDayValue.value = 1;
+  customWeekValue.value = 1;
+};
+
+watch(
+  () => visible.value,
+  () => {
+    if (visible.value) {
+      initValue();
+    }
+  }
+);
+
+defineExpose({
+  openDialog,
+  closeDialog
+});
+</script>
diff --git a/src/components/BpmnDesign/panel/property/ExecutionListener.vue b/src/components/BpmnDesign/panel/property/ExecutionListener.vue
new file mode 100644
index 0000000..ecd5034
--- /dev/null
+++ b/src/components/BpmnDesign/panel/property/ExecutionListener.vue
@@ -0,0 +1,305 @@
+<template>
+  <div>
+    <vxe-toolbar>
+      <template #buttons>
+        <el-button type="primary" link size="small" @click="insertEvent">鏂板</el-button>
+        <el-button type="primary" link size="small" @click="removeSelectRowEvent">鍒犻櫎</el-button>
+      </template>
+    </vxe-toolbar>
+    <vxe-table
+      ref="tableRef"
+      size="mini"
+      height="100px"
+      border
+      show-overflow
+      keep-source
+      :data="tableData"
+      :menu-config="menuConfig"
+      @cell-dblclick="cellDBLClickEvent"
+      @menu-click="contextMenuClickEvent"
+    >
+      <vxe-column type="checkbox" width="40"></vxe-column>
+      <vxe-column type="seq" width="40"></vxe-column>
+      <vxe-column field="event" title="浜嬩欢" min-width="100px">
+        <template #default="slotParams">
+          <span>{{ eventSelect.find((e) => e.value === slotParams.row.event)?.label }}</span>
+        </template>
+      </vxe-column>
+      <vxe-column field="type" title="绫诲瀷" min-width="100px">
+        <template #default="slotParams">
+          <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span>
+        </template>
+      </vxe-column>
+      <vxe-column field="className" title="Java 绫诲悕" min-width="100px"> </vxe-column>
+    </vxe-table>
+
+    <el-dialog
+      v-model="formDialog.visible.value"
+      :title="formDialog.title.value"
+      width="600px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+      append-to-body
+    >
+      <el-form ref="formRef" :model="formData" :rules="tableRules" label-width="90px">
+        <el-form-item label="浜嬩欢" prop="event">
+          <el-select v-model="formData.event">
+            <el-option v-for="item in eventSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="绫诲瀷" prop="type">
+          <template #label>
+            <span>
+              绫诲瀷
+              <el-tooltip placement="top">
+                <el-icon><QuestionFilled /></el-icon>
+                <template #content>
+                  绫伙細绀轰緥 com.company.MyCustomListener锛岃嚜瀹氫箟绫诲繀椤诲疄鐜� org.flowable.engine.delegate.TaskListener 鎺ュ彛<br />
+                  琛ㄨ揪寮忥細绀轰緥 ${myObject.callMethod(task, task.eventName)}<br />
+                  濮旀墭琛ㄨ揪寮忥細绀轰緥 ${myListenerSpringBean} 锛岃 springBean 闇�瑕佸疄鐜� org.flowable.engine.delegate.TaskListener 鎺ュ彛
+                </template>
+              </el-tooltip>
+            </span>
+          </template>
+          <el-select v-model="formData.type">
+            <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="Java 绫诲悕" prop="className">
+          <el-input v-model="formData.className" type="text"></el-input>
+        </el-form-item>
+      </el-form>
+      <el-tabs type="border-card">
+        <el-tab-pane label="鍙傛暟">
+          <ListenerParam ref="listenerParamRef" :table-data="formData.params" />
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="formDialog.closeDialog">鍙� 娑�</el-button>
+          <el-button type="primary" @click="submitEvent">纭� 瀹�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup lang="ts">
+import ListenerParam from './ListenerParam.vue';
+import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
+import { ExecutionListenerVO } from 'bpmnDesign';
+import { Moddle, Modeler, ModdleElement } from 'bpmn';
+
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useDialog from '@/hooks/useDialog';
+import useModelerStore from '@/store/modules/modeler';
+
+const emit = defineEmits(['close']);
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const selectRow = ref<ExecutionListenerVO | null>();
+const formDialog = useDialog({
+  title: selectRow.value ? '缂栬緫&淇濆瓨' : '鏂板&淇濆瓨'
+});
+
+const { showConfig, elementType, updateProperties } = usePanel({
+  element: toRaw(props.element)
+});
+const { getModdle } = useModelerStore();
+const moddle = getModdle();
+
+const listenerParamRef = ref<InstanceType<typeof ListenerParam>>();
+const tableRef = ref<VxeTableInstance<ExecutionListenerVO>>();
+const formRef = ref<ElFormInstance>();
+
+const initData: ExecutionListenerVO = {
+  event: '',
+  type: '',
+  className: '',
+  params: []
+};
+const formData = ref<ExecutionListenerVO>({ ...initData });
+const tableData = ref<ExecutionListenerVO[]>([]);
+const tableRules = ref<ElFormRules>({
+  event: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  type: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  className: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+
+const submitEvent = async () => {
+  const error = await listenerParamRef.value.validate();
+  await formRef.value.validate((validate) => {
+    if (validate && !error) {
+      const $table = tableRef.value;
+      if ($table) {
+        formData.value.params = listenerParamRef.value.getTableData();
+        if (selectRow.value) {
+          Object.assign(selectRow.value, formData.value);
+        } else {
+          $table.insertAt({ ...formData.value }, -1);
+        }
+        updateElement();
+        formDialog.closeDialog();
+      }
+    }
+  });
+};
+
+const removeSelectRowEvent = async () => {
+  const $table = tableRef.value;
+  if ($table) {
+    const selectCount = $table.getCheckboxRecords().length;
+    if (selectCount === 0) {
+      proxy?.$modal.msgWarning('璇烽�夋嫨琛�');
+    } else {
+      await $table.removeCheckboxRow();
+      updateElement();
+    }
+  }
+};
+const insertEvent = async () => {
+  Object.assign(formData.value, initData);
+  selectRow.value = null;
+  formDialog.openDialog();
+};
+
+const editEvent = (row: ExecutionListenerVO) => {
+  Object.assign(formData.value, row);
+  selectRow.value = row;
+  formDialog.openDialog();
+};
+
+const removeEvent = async (row: ExecutionListenerVO) => {
+  await proxy?.$modal.confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ユ暟鎹�?');
+  const $table = tableRef.value;
+  if ($table) {
+    await $table.remove(row);
+    updateElement();
+  }
+};
+const updateElement = () => {
+  const $table = tableRef.value;
+  const data = $table.getTableData().fullData;
+  if (data.length) {
+    let extensionElements = props.element.businessObject.get('extensionElements');
+    if (!extensionElements) {
+      extensionElements = moddle.create('bpmn:ExtensionElements');
+    }
+    // 娓呴櫎鏃у��
+    extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:ExecutionListener') ?? [];
+    data.forEach((item) => {
+      const executionListener = moddle.create('flowable:ExecutionListener');
+      executionListener['event'] = item.event;
+      executionListener[item.type] = item.className;
+      if (item.params && item.params.length) {
+        item.params.forEach((field) => {
+          const fieldElement = moddle.create('flowable:Field');
+          fieldElement['name'] = field.name;
+          fieldElement[field.type] = field.value;
+          executionListener.get('fields').push(fieldElement);
+        });
+      }
+      extensionElements.get('values').push(executionListener);
+    });
+    updateProperties({ extensionElements: extensionElements });
+  } else {
+    const extensionElements = props.element.businessObject[`extensionElements`];
+    if (extensionElements) {
+      extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:ExecutionListener') ?? [];
+    }
+  }
+};
+
+const cellDBLClickEvent: VxeTableEvents.CellDblclick<ExecutionListenerVO> = ({ row }) => {
+  editEvent(row);
+};
+
+const menuConfig = reactive<VxeTablePropTypes.MenuConfig<ExecutionListenerVO>>({
+  body: {
+    options: [
+      [
+        { code: 'edit', name: '缂栬緫', prefixIcon: 'vxe-icon-edit', disabled: false },
+        { code: 'remove', name: '鍒犻櫎', prefixIcon: 'vxe-icon-delete', disabled: false }
+      ]
+    ]
+  },
+  visibleMethod({ options, column }) {
+    const isDisabled = !column;
+    options.forEach((list) => {
+      list.forEach((item) => {
+        item.disabled = isDisabled;
+      });
+    });
+    return true;
+  }
+});
+const contextMenuClickEvent: VxeTableEvents.MenuClick<ExecutionListenerVO> = ({ menu, row, column }) => {
+  const $table = tableRef.value;
+  if ($table) {
+    switch (menu.code) {
+      case 'edit':
+        editEvent(row);
+        break;
+      case 'remove':
+        removeEvent(row);
+        break;
+    }
+  }
+};
+
+const initTableData = () => {
+  tableData.value =
+    props.element.businessObject.extensionElements?.values
+      .filter((item) => item.$type === 'flowable:ExecutionListener')
+      .map((item) => {
+        let type;
+        if ('class' in item) type = 'class';
+        if ('expression' in item) type = 'expression';
+        if ('delegateExpression' in item) type = 'delegateExpression';
+        return {
+          event: item.event,
+          type: type,
+          className: item[type],
+          params:
+            item.fields?.map((field) => {
+              let fieldType;
+              if ('stringValue' in field) fieldType = 'stringValue';
+              if ('expression' in field) fieldType = 'expression';
+              return {
+                name: field.name,
+                type: fieldType,
+                value: field[fieldType]
+              };
+            }) ?? []
+        };
+      }) ?? [];
+};
+
+onMounted(() => {
+  initTableData();
+});
+
+const typeSelect = [
+  { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '绫�', value: 'class' },
+  { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '琛ㄨ揪寮�', value: 'expression' },
+  { id: '4b8135ab-6bc3-4a0f-80be-22f58bc6c5fd', label: '濮旀墭琛ㄨ揪寮�', value: 'delegateExpression' }
+];
+const eventSelect = [
+  { id: 'e6e0a51a-2d5d-4dc4-b847-b5c14f43a6ab', label: 'start', value: 'start' },
+  { id: '6da97c1e-15fc-4445-8943-75d09f49778e', label: 'end', value: 'end' },
+  { id: '6a2cbcec-e026-4f11-bef7-fff0b5c871e2', label: 'take', value: 'take' }
+];
+</script>
+
+<style scoped lang="scss">
+.el-badge {
+  :deep(.el-badge__content) {
+    top: 10px;
+  }
+}
+</style>
diff --git a/src/components/BpmnDesign/panel/property/ListenerParam.vue b/src/components/BpmnDesign/panel/property/ListenerParam.vue
new file mode 100644
index 0000000..334249b
--- /dev/null
+++ b/src/components/BpmnDesign/panel/property/ListenerParam.vue
@@ -0,0 +1,121 @@
+<template>
+  <vxe-toolbar>
+    <template #buttons>
+      <el-button icon="Plus" @click="insertRow">鏂板</el-button>
+    </template>
+  </vxe-toolbar>
+  <vxe-table
+    ref="tableRef"
+    :height="height"
+    border
+    show-overflow
+    keep-source
+    :data="tableData"
+    :edit-rules="tableRules"
+    :edit-config="{ trigger: 'click', mode: 'row', showStatus: true }"
+  >
+    <vxe-column type="seq" width="40"></vxe-column>
+    <vxe-column field="type" title="绫诲瀷" :edit-render="{}">
+      <template #default="slotParams">
+        <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span>
+      </template>
+      <template #edit="slotParams">
+        <vxe-select v-model="slotParams.row.type">
+          <vxe-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></vxe-option>
+        </vxe-select>
+      </template>
+    </vxe-column>
+    <vxe-column field="name" title="鍚嶇О" :edit-render="{}">
+      <template #edit="slotParams">
+        <vxe-input v-model="slotParams.row.name" type="text"></vxe-input>
+      </template>
+    </vxe-column>
+    <vxe-column field="value" title="鍊�" :edit-render="{}">
+      <template #edit="slotParams">
+        <vxe-input v-model="slotParams.row.value" type="text"></vxe-input>
+      </template>
+    </vxe-column>
+    <vxe-column title="鎿嶄綔" width="100" show-overflow align="center">
+      <template #default="slotParams">
+        <el-tooltip content="鍒犻櫎" placement="top">
+          <el-button link type="danger" icon="Delete" @click="removeRow(slotParams.row)"></el-button>
+        </el-tooltip>
+      </template>
+    </vxe-column>
+  </vxe-table>
+</template>
+
+<script setup lang="ts">
+import { VXETable, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
+import { ParamVO } from 'bpmnDesign';
+import useDialog from '@/hooks/useDialog';
+
+interface PropType {
+  height?: string;
+  tableData?: ParamVO[];
+}
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const props = withDefaults(defineProps<PropType>(), {
+  height: '200px',
+  tableData: () => []
+});
+
+const tableRules = ref<VxeTablePropTypes.EditRules>({
+  type: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  value: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+
+const { title, visible, openDialog, closeDialog } = useDialog({
+  title: '鐩戝惉鍣ㄥ弬鏁�'
+});
+const typeSelect = [
+  { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '瀛楃涓�', value: 'stringValue' },
+  { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '琛ㄨ揪寮�', value: 'expression' }
+];
+
+const tableRef = ref<VxeTableInstance<ParamVO>>();
+
+const getTableData = () => {
+  const $table = tableRef.value;
+  if ($table) {
+    return $table.getTableData().fullData;
+  }
+  return [];
+};
+
+const insertRow = async () => {
+  const $table = tableRef.value;
+  if ($table) {
+    const { row: newRow } = await $table.insertAt({}, -1);
+    // 鎻掑叆涓�鏉℃暟鎹苟瑙﹀彂鏍¢獙
+    await $table.validate(newRow);
+  }
+};
+
+const removeRow = async (row: ParamVO) => {
+  await proxy?.$modal.confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ユ暟鎹�?');
+  const $table = tableRef.value;
+  if ($table) {
+    await $table.remove(row);
+  }
+};
+
+const validate = async () => {
+  const $table = tableRef.value;
+  if ($table) {
+    return await $table.validate(true);
+  }
+};
+
+defineExpose({
+  closeDialog,
+  openDialog,
+  validate,
+  getTableData
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/components/BpmnDesign/panel/property/TaskListener.vue b/src/components/BpmnDesign/panel/property/TaskListener.vue
new file mode 100644
index 0000000..f90c9a8
--- /dev/null
+++ b/src/components/BpmnDesign/panel/property/TaskListener.vue
@@ -0,0 +1,307 @@
+<template>
+  <div>
+    <vxe-toolbar>
+      <template #buttons>
+        <el-button type="primary" link size="small" @click="insertEvent">鏂板</el-button>
+        <el-button type="primary" link size="small" @click="removeSelectRowEvent">鍒犻櫎</el-button>
+      </template>
+    </vxe-toolbar>
+    <vxe-table
+      ref="tableRef"
+      size="mini"
+      height="100px"
+      border
+      show-overflow
+      keep-source
+      :data="tableData"
+      :menu-config="menuConfig"
+      @cell-dblclick="cellDBLClickEvent"
+      @menu-click="contextMenuClickEvent"
+    >
+      <vxe-column type="checkbox" width="40"></vxe-column>
+      <vxe-column type="seq" width="40"></vxe-column>
+      <vxe-column field="event" title="浜嬩欢" min-width="100px">
+        <template #default="slotParams">
+          <span>{{ eventSelect.find((e) => e.value === slotParams.row.event)?.label }}</span>
+        </template>
+      </vxe-column>
+      <vxe-column field="type" title="绫诲瀷" min-width="100px">
+        <template #default="slotParams">
+          <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span>
+        </template>
+      </vxe-column>
+      <vxe-column field="className" title="Java 绫诲悕" min-width="100px"> </vxe-column>
+    </vxe-table>
+
+    <el-dialog
+      v-model="formDialog.visible.value"
+      :title="formDialog.title.value"
+      width="600px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+      append-to-body
+    >
+      <el-form ref="formRef" :model="formData" :rules="tableRules" label-width="90px">
+        <el-form-item label="浜嬩欢" prop="event">
+          <template #label>
+            <span>
+              浜嬩欢
+              <el-tooltip placement="top">
+                <el-icon><QuestionFilled /></el-icon>
+                <template #content>
+                  create锛堝垱寤猴級锛氬綋浠诲姟宸茬粡鍒涘缓锛屽苟涓旀墍鏈変换鍔″弬鏁伴兘宸茬粡璁剧疆鏃惰Е鍙戙��<br />
+                  assignment锛堟寚娲撅級锛氬綋浠诲姟宸茬粡鎸囨淳缁欐煇浜烘椂瑙﹀彂銆傝娉ㄦ剰锛氬綋娴佺▼鎵ц鍒拌揪鐢ㄦ埛浠诲姟鏃讹紝鍦ㄨЕ鍙慶reate浜嬩欢涔嬪墠锛屼細棣栧厛瑙﹀彂assignment浜嬩欢銆�<br />
+                  complete锛堝畬鎴愶級锛氬綋浠诲姟宸茬粡瀹屾垚锛屼粠杩愯鏃舵暟鎹腑鍒犻櫎鍓嶈Е鍙戙��<br />
+                  delete锛堝垹闄わ級锛氬湪浠诲姟鍗冲皢琚垹闄ゅ墠瑙﹀彂銆傝娉ㄦ剰浠诲姟鐢眂ompleteTask姝e父瀹屾垚鏃朵篃浼氳Е鍙戙��
+                </template>
+              </el-tooltip>
+            </span>
+          </template>
+          <el-select v-model="formData.event">
+            <el-option v-for="item in eventSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="绫诲瀷" prop="type">
+          <el-select v-model="formData.type">
+            <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="Java 绫诲悕" prop="className">
+          <el-input v-model="formData.className" type="text"></el-input>
+        </el-form-item>
+      </el-form>
+      <el-tabs type="border-card">
+        <el-tab-pane label="鍙傛暟">
+          <ListenerParam ref="listenerParamRef" :table-data="formData.params" />
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="formDialog.closeDialog">鍙� 娑�</el-button>
+          <el-button type="primary" @click="submitEvent">纭� 瀹�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup lang="ts">
+import ListenerParam from './ListenerParam.vue';
+import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
+import { TaskListenerVO } from 'bpmnDesign';
+import { ModdleElement } from 'bpmn';
+
+import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useDialog from '@/hooks/useDialog';
+import useModelerStore from '@/store/modules/modeler';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+interface PropType {
+  element: ModdleElement;
+}
+const props = withDefaults(defineProps<PropType>(), {});
+
+const selectRow = ref<TaskListenerVO | null>();
+const formDialog = useDialog({
+  title: selectRow.value ? '缂栬緫&淇濆瓨' : '鏂板&淇濆瓨'
+});
+const { showConfig, elementType, updateProperties } = usePanel({
+  element: toRaw(props.element)
+});
+const { getModdle } = useModelerStore();
+const moddle = getModdle();
+
+const listenerParamRef = ref<InstanceType<typeof ListenerParam>>();
+const tableRef = ref<VxeTableInstance<TaskListenerVO>>();
+const formRef = ref<ElFormInstance>();
+
+const initData: TaskListenerVO = {
+  event: '',
+  type: '',
+  className: '',
+  name: '',
+  params: []
+};
+const formData = ref<TaskListenerVO>({ ...initData });
+const currentIndex = ref(0);
+const tableData = ref<TaskListenerVO[]>([]);
+const tableRules = ref<VxeTablePropTypes.EditRules>({
+  event: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  type: [{ required: true, message: '璇烽�夋嫨', trigger: 'blur' }],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+  className: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }]
+});
+
+const submitEvent = async () => {
+  const error = await listenerParamRef.value.validate();
+  await formRef.value.validate((validate) => {
+    if (validate && !error) {
+      const $table = tableRef.value;
+      if ($table) {
+        formData.value.params = listenerParamRef.value.getTableData();
+        if (selectRow.value) {
+          Object.assign(selectRow.value, formData.value);
+        } else {
+          $table.insertAt({ ...formData.value }, -1);
+        }
+        updateElement();
+        formDialog.closeDialog();
+      }
+    }
+  });
+};
+
+const insertEvent = async () => {
+  Object.assign(formData.value, initData);
+  selectRow.value = null;
+  formDialog.openDialog();
+};
+
+const editEvent = (row: TaskListenerVO) => {
+  Object.assign(formData.value, row);
+  selectRow.value = row;
+  formDialog.openDialog();
+};
+const removeEvent = async (row: TaskListenerVO) => {
+  await proxy?.$modal.confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ユ暟鎹�?');
+  const $table = tableRef.value;
+  if ($table) {
+    await $table.remove(row);
+    updateElement();
+  }
+};
+
+const removeSelectRowEvent = async () => {
+  const $table = tableRef.value;
+  if ($table) {
+    const selectCount = $table.getCheckboxRecords().length;
+    if (selectCount === 0) {
+      proxy?.$modal.msgWarning('璇烽�夋嫨琛�');
+    } else {
+      await $table.removeCheckboxRow();
+      updateElement();
+    }
+  }
+};
+const updateElement = () => {
+  const $table = tableRef.value;
+  const data = $table.getTableData().fullData;
+  if (data.length) {
+    let extensionElements = props.element.businessObject.get('extensionElements');
+    if (!extensionElements) {
+      extensionElements = moddle.create('bpmn:ExtensionElements');
+    }
+    // 娓呴櫎鏃у��
+    extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:TaskListener') ?? [];
+    data.forEach((item) => {
+      const taskListener = moddle.create('flowable:TaskListener');
+      taskListener['event'] = item.event;
+      taskListener[item.type] = item.className;
+      if (item.params && item.params.length) {
+        item.params.forEach((field) => {
+          const fieldElement = moddle.create('flowable:Field');
+          fieldElement['name'] = field.name;
+          fieldElement[field.type] = field.value;
+          taskListener.get('fields').push(fieldElement);
+        });
+      }
+      extensionElements.get('values').push(taskListener);
+    });
+    updateProperties({ extensionElements: extensionElements });
+  } else {
+    const extensionElements = props.element.businessObject[`extensionElements`];
+    if (extensionElements) {
+      extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:TaskListener') ?? [];
+    }
+  }
+};
+
+const cellDBLClickEvent: VxeTableEvents.CellDblclick<TaskListenerVO> = ({ row }) => {
+  editEvent(row);
+};
+
+const menuConfig = reactive<VxeTablePropTypes.MenuConfig<TaskListenerVO>>({
+  body: {
+    options: [
+      [
+        { code: 'edit', name: '缂栬緫', prefixIcon: 'vxe-icon-edit', disabled: false },
+        { code: 'remove', name: '鍒犻櫎', prefixIcon: 'vxe-icon-delete', disabled: false }
+      ]
+    ]
+  },
+  visibleMethod({ options, column }) {
+    const isDisabled = !column;
+    options.forEach((list) => {
+      list.forEach((item) => {
+        item.disabled = isDisabled;
+      });
+    });
+    return true;
+  }
+});
+const contextMenuClickEvent: VxeTableEvents.MenuClick<TaskListenerVO> = ({ menu, row, column }) => {
+  const $table = tableRef.value;
+  if ($table) {
+    switch (menu.code) {
+      case 'edit':
+        editEvent(row);
+        break;
+      case 'remove':
+        removeEvent(row);
+        break;
+    }
+  }
+};
+const initTableData = () => {
+  tableData.value =
+    props.element.businessObject.extensionElements?.values
+      .filter((item) => item.$type === 'flowable:TaskListener')
+      .map((item) => {
+        let type;
+        if ('class' in item) type = 'class';
+        if ('expression' in item) type = 'expression';
+        if ('delegateExpression' in item) type = 'delegateExpression';
+        return {
+          event: item.event,
+          type: type,
+          className: item[type],
+          params:
+            item.fields?.map((field) => {
+              let fieldType;
+              if ('stringValue' in field) fieldType = 'stringValue';
+              if ('expression' in field) fieldType = 'expression';
+              return {
+                name: field.name,
+                type: fieldType,
+                value: field[fieldType]
+              };
+            }) ?? []
+        };
+      }) ?? [];
+};
+
+onMounted(() => {
+  initTableData();
+});
+
+const typeSelect = [
+  { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '绫�', value: 'class' },
+  { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '琛ㄨ揪寮�', value: 'expression' },
+  { id: '4b8135ab-6bc3-4a0f-80be-22f58bc6c5fd', label: '濮旀墭琛ㄨ揪寮�', value: 'delegateExpression' }
+];
+const eventSelect = [
+  { id: 'e6e0a51a-2d5d-4dc4-b847-b5c14f43a6ab', label: '鍒涘缓', value: 'create' },
+  { id: '6da97c1e-15fc-4445-8943-75d09f49778e', label: '鎸囨淳', value: 'assignment' },
+  { id: '6a2cbcec-e026-4f11-bef7-fff0b5c871e2', label: '瀹屾垚', value: 'complete' },
+  { id: '68801972-85f1-482f-bd86-1fad015c26ed', label: '鍒犻櫎', value: 'delete' }
+];
+</script>
+
+<style scoped lang="scss">
+.el-badge {
+  :deep(.el-badge__content) {
+    top: 10px;
+  }
+}
+</style>
diff --git a/src/components/Process/approvalRecord.vue b/src/components/Process/approvalRecord.vue
new file mode 100644
index 0000000..d06e7ef
--- /dev/null
+++ b/src/components/Process/approvalRecord.vue
@@ -0,0 +1,162 @@
+<template>
+  <el-dialog v-model="visible" draggable title="瀹℃壒璁板綍" :width="props.width" :height="props.height" append-to-body
+    :close-on-click-modal="false">
+    <div v-loading="loading">
+      <div style="width: 100%;height: 300px;overflow: auto;position: relative;">
+        <div v-for="(graphic, index) in graphicInfoVos" :key="index" :style="{
+          position: 'absolute',
+          left: `${graphic.x}px`,
+          top: `${graphic.y}px`,
+          width: `${graphic.width}px`,
+          height: `${graphic.height}px`,
+          cursor: 'pointer',
+          zIndex: 99
+        }" @mouseover="handleMouseOver(graphic)" @mouseleave="handleMouseLeave()"></div>
+        <!-- 寮瑰嚭鐨� div 鍏冪礌 -->
+        <div v-show="popupVisible" class="triangle" :style="{
+          position: 'absolute',
+          left: `${graphicX}px`,
+          top: `${graphicY}px`,
+          backgroundColor: '#fff',
+          padding: '10px',
+          zIndex: 100
+        }">
+          <p>瀹℃壒浜哄憳: {{ nodeInfo.nickName }}</p>
+          <p>鑺傜偣鐘舵�侊細{{ nodeInfo.status }}</p>
+          <p>寮�濮嬫椂闂达細{{ nodeInfo.startTime }}</p>
+          <p>缁撴潫鏃堕棿锛歿{ nodeInfo.endTime }}</p>
+          <p>瀹℃壒鑰楁椂锛歿{ nodeInfo.runDuration }}</p>
+        </div>
+        <el-image :src="src" />
+      </div>
+      <div>
+        <el-table :data="historyList" style="width: 100%" border fit max-height="570">
+          <el-table-column label="娴佺▼瀹℃壒鍘嗗彶璁板綍" align="center">
+            <el-table-column type="index" label="搴忓彿" align="center" width="50"></el-table-column>
+            <el-table-column prop="name" label="浠诲姟鍚嶇О" sortable align="center"></el-table-column>
+            <el-table-column prop="nickName" label="鍔炵悊浜�" sortable align="center"></el-table-column>
+            <el-table-column label="鐘舵��" sortable align="center">
+              <template #default="scope">
+                <el-tag type="success">{{ scope.row.statusName }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="comment" label="瀹℃壒鎰忚" sortable align="center"></el-table-column>
+            <el-table-column prop="attachmentList" label="闄勪欢" sortable align="center">
+              <template #default="scope">
+                <el-popover placement="right"  v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" :width="310" trigger="click">
+                    <template #reference>
+                      <el-button style="margin-right: 16px">闄勪欢</el-button>
+                    </template>
+                    <el-table border :data="scope.row.attachmentList">
+                      <el-table-column prop="name" width="202" :show-overflow-tooltip="true" label="闄勪欢鍚嶇О"></el-table-column>
+                      <el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="鎿嶄綔">
+                        <template #default="tool">
+                          <el-button type="text" @click="handleDownload(tool.row.contentId)">涓嬭浇</el-button>
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                    
+                  </el-popover>
+              </template>
+            </el-table-column>
+            <el-table-column prop="startTime" label="寮�濮嬫椂闂�" sortable align="center"></el-table-column>
+            <el-table-column prop="endTime" label="缁撴潫鏃堕棿" sortable align="center"></el-table-column>
+            <el-table-column prop="runDuration" label="杩愯鏃堕暱" sortable align="center"></el-table-column>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import { getHistoryProcessImage, getHistoryRecord } from '@/api/workflow/processInstance';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+import { ref } from 'vue';
+const props = defineProps({
+  width: {
+    type: String,
+    default: '70%'
+  },
+  height: {
+    type: String,
+    default: '100%'
+  }
+});
+const loading = ref(false);
+const src = ref('');
+const visible = ref(false);
+const historyList = ref<Array<any>>([]);
+const deleteReason = ref<string>('');
+const graphicInfoVos = ref<Array<any>>([]);
+const nodeListInfo = ref<Array<any>>([]);
+const popupVisible = ref(false);
+const nodeInfo = ref<any>({});
+const graphicX = ref<number | string>(0);
+const graphicY = ref<number | string>(0);
+//鍒濆鍖栨煡璇㈠鎵硅褰�
+const init = async (processInstanceId: string) => {
+  visible.value = true;
+  loading.value = true;
+  historyList.value = [];
+  graphicInfoVos.value = [];
+  getHistoryProcessImage(processInstanceId).then((res) => {
+    src.value = 'data:image/png;base64,' + res.data
+  });
+  getHistoryRecord(processInstanceId).then((response) => {
+    historyList.value = response.data.historyRecordList;
+    graphicInfoVos.value = response.data.graphicInfoVos;
+    nodeListInfo.value = response.data.nodeListInfo;
+    deleteReason.value = response.data.deleteReason;
+    loading.value = false;
+  });
+};
+//鎮诞浜嬩欢
+const handleMouseOver = async (graphic: any) => {
+  graphicX.value = graphic.x + graphic.width + 10;
+  graphicY.value = graphic.y - graphic.height + -10;
+  nodeInfo.value = {};
+  if (nodeListInfo.value && nodeListInfo.value.length > 0) {
+    let info = nodeListInfo.value.find((e: any) => e.taskDefinitionKey == graphic.nodeId);
+    if (info) {
+      nodeInfo.value = {
+        nickName: info.nickName,
+        status: info.status,
+        startTime: info.startTime,
+        endTime: info.endTime,
+        runDuration: info.runDuration
+      };
+      popupVisible.value = true;
+    }
+  }
+};
+//鍏抽棴
+const handleMouseLeave = async () => {
+  popupVisible.value = false;
+};
+
+/** 涓嬭浇鎸夐挳鎿嶄綔 */
+const handleDownload = (ossId: string) => {
+  proxy?.$download.oss(ossId);
+};
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  init
+});
+</script>
+<style scoped>
+.triangle {
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+  border-radius: 6px;
+}
+
+.triangle::after {
+  content: ' ';
+  position: absolute;
+  top: 8em;
+  right: 215px;
+  border: 15px solid;
+  border-color: transparent #fff transparent transparent;
+}
+</style>
diff --git a/src/components/Process/multiInstance-user.vue b/src/components/Process/multiInstance-user.vue
new file mode 100644
index 0000000..96fa21f
--- /dev/null
+++ b/src/components/Process/multiInstance-user.vue
@@ -0,0 +1,362 @@
+<template>
+  <el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body
+    :close-on-click-modal="false">
+    <div class="p-2" v-if="multiInstance === 'add'">
+      <el-row :gutter="20">
+        <!-- 閮ㄩ棬鏍� -->
+        <el-col :lg="4" :xs="24" style="">
+          <el-card shadow="hover">
+            <el-input v-model="deptName" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" prefix-icon="Search" clearable />
+            <el-tree class="mt-2" ref="deptTreeRef" node-key="id" :data="deptOptions"
+              :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false"
+              :filter-node-method="filterNode" highlight-current default-expand-all
+              @node-click="handleNodeClick"></el-tree>
+          </el-card>
+        </el-col>
+        <el-col :lg="20" :xs="24">
+          <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+            :leave-active-class="proxy?.animate.searchAnimate.leave">
+            <div class="search" v-show="showSearch">
+              <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+                <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+                  <el-input v-model="queryParams.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" clearable style="width: 240px"
+                    @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
+                  <el-input v-model="queryParams.phonenumber" placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�" clearable style="width: 240px"
+                    @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" @click="handleQuery" icon="Search">鎼滅储</el-button>
+                  <el-button @click="resetQuery" icon="Refresh">閲嶇疆</el-button>
+                </el-form-item>
+              </el-form>
+            </div>
+          </transition>
+
+          <el-card shadow="hover">
+            <template #header>
+              <el-row :gutter="10">
+                <right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery" :search="true"></right-toolbar>
+              </el-row>
+            </template>
+
+            <el-table v-loading="loading" :data="userList" ref="multipleTableRef" row-key="userId"
+              @selection-change="handleSelectionChange">
+              <el-table-column type="selection" width="50" align="center" />
+              <el-table-column label="鐢ㄦ埛缂栧彿" align="center" key="userId" prop="userId" />
+              <el-table-column label="鐢ㄦ埛鍚嶇О" align="center" key="userName" prop="userName" :show-overflow-tooltip="true" />
+              <el-table-column label="鐢ㄦ埛鏄电О" align="center" key="nickName" prop="nickName" :show-overflow-tooltip="true" />
+              <el-table-column label="鎵嬫満鍙风爜" align="center" key="phonenumber" prop="phonenumber" width="120" />
+              <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="160">
+                <template #default="scope">
+                  <span>{{ scope.row.createTime }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+              v-model:limit="queryParams.pageSize" @pagination="handleQuery" />
+          </el-card>
+          <el-card shadow="hover">
+            <el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin:2px" closable
+              @close="handleCloseTag(user, index)">{{ user.userName }}
+            </el-tag>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+    <div class="p-2" v-if="multiInstance === 'delete'">
+      <el-table v-loading="loading" :data="taskList" @selection-change="handleTaskSelection">
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="name" label="浠诲姟鍚嶇О" />
+        <el-table-column prop="assigneeName" label="鍔炵悊浜�" />
+      </el-table>
+    </div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+        <el-button @click="visible = false">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup name="User" lang="ts">
+import { deptTreeSelect } from '@/api/system/user';
+import { getWorkflowAddMultiListByPage, getWorkflowDeleteMultiInstanceList, getUserListByIds } from '@/api/workflow/workflowUser';
+import { addMultiInstanceExecution, deleteMultiInstanceExecution } from '@/api/workflow/task';
+import { UserVO } from '@/api/system/user/types';
+import { DeptVO } from '@/api/system/dept/types';
+import { ComponentInternalInstance } from 'vue';
+import { ElTree, ElTable } from 'element-plus';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const props = defineProps({
+  // 瀹�
+  width: {
+    type: String,
+    default: '70%'
+  },
+  // 楂�
+  height: {
+    type: String,
+    default: '100%'
+  },
+  // 鏍囬
+  title: {
+    type: String,
+    default: '鍔犵浜哄憳'
+  },
+  //鏄惁澶氶��
+  multiple: {
+    type: Boolean,
+    default: true
+  },
+  //鍥炴樉鐢ㄦ埛id
+  userIdList: {
+    type: Array,
+    default: []
+  }
+});
+const deptTreeRef = ref(ElTree);
+const multipleTableRef = ref(ElTable);
+
+const userList = ref<UserVO[]>();
+const taskList = ref<Array<any>[]>();
+const loading = ref(true);
+const showSearch = ref(true);
+const selectionTask = ref<Array<any>[]>();
+const visible = ref(false);
+const total = ref(0);
+const deptName = ref('');
+const deptOptions = ref<DeptVO[]>([]);
+const chooseUserList = ref(ref<UserVO[]>());
+const userIds = ref<Array<number | string>>([]);
+//鍔犵鎴栬�呭噺绛�
+const multiInstance = ref('');
+const queryParams = ref<Record<string, any>>({
+  pageNum: 1,
+  pageSize: 10,
+  userName: '',
+  nickName: '',
+  taskId: ''
+});
+/** 鏌ヨ鐢ㄦ埛鍒楄〃 */
+const getAddMultiInstanceList = async (taskId: string, userIdList: Array<number | string>) => {
+  deptOptions.value = [];
+  getTreeSelect();
+  multiInstance.value = 'add';
+  userIds.value = userIdList;
+  visible.value = true;
+  queryParams.value.taskId = taskId;
+  loading.value = true;
+  const res = await getWorkflowAddMultiListByPage(queryParams.value);
+  loading.value = false;
+  userList.value = res.rows;
+  total.value = res.total;
+  if (userList.value && userIds.value.length > 0) {
+    const data = await getUserListByIds(userIds.value);
+    if (data.data && data.data.length > 0) {
+      chooseUserList.value = data.data;
+      data.data.forEach((user: UserVO) => {
+        multipleTableRef.value!.toggleRowSelection(
+          userList.value.find((item) => {
+            return item.userId == user.userId;
+          }),
+          true
+        );
+      });
+    }
+  }
+};
+
+const getList = async () => {
+  loading.value = true;
+  const res = await getWorkflowAddMultiListByPage(queryParams.value);
+  loading.value = false;
+  userList.value = res.rows;
+  total.value = res.total;
+  if (userList.value && userIds.value.length > 0) {
+    const data = await getUserListByIds(userIds.value);
+    if (data.data && data.data.length > 0) {
+      chooseUserList.value = data.data;
+      data.data.forEach((user: UserVO) => {
+        multipleTableRef.value!.toggleRowSelection(
+          userList.value.find((item) => {
+            return item.userId == user.userId;
+          }),
+          true
+        );
+      });
+    }
+  }
+};
+
+const getDeleteMultiInstanceList = async (taskId: string) => {
+  deptOptions.value = [];
+  loading.value = true;
+  queryParams.value.taskId = taskId;
+  multiInstance.value = 'delete';
+  visible.value = true;
+  const res = await getWorkflowDeleteMultiInstanceList(taskId);
+  taskList.value = res.data;
+  loading.value = false;
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getAddMultiInstanceList(queryParams.value.taskId, userIds.value);
+};
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryParams.value.pageNum = 1;
+  queryParams.value.deptId = undefined;
+  queryParams.value.userName = undefined;
+  queryParams.value.nickName = undefined;
+  deptTreeRef.value.setCurrentKey(null);
+  handleQuery();
+};
+
+/** 閫夋嫨鏉℃暟  */
+const handleSelectionChange = (selection: UserVO[]) => {
+  if (props.multiple) {
+    chooseUserList.value = selection.filter((element, index, self) => {
+      return self.findIndex((x) => x.userId === element.userId) === index;
+    });
+    selection.forEach((u) => {
+      if (chooseUserList.value && !chooseUserList.value.includes(u)) {
+        multipleTableRef.value!.toggleRowSelection(u, undefined);
+      }
+    });
+    userIds.value = chooseUserList.value.map((item) => {
+      return item.userId;
+    });
+  } else {
+    chooseUserList.value = selection;
+    if (selection.length > 1) {
+      let delRow = selection.shift();
+      multipleTableRef.value!.toggleRowSelection(delRow, undefined);
+    }
+    if (selection.length === 0) {
+      chooseUserList.value = [];
+    }
+  }
+};
+/** 閫夋嫨鏉℃暟  */
+const handleTaskSelection = (selection: any) => {
+  selectionTask.value = selection;
+};
+
+/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */
+const getTreeSelect = async () => {
+  const res = await deptTreeSelect();
+  deptOptions.value = res.data;
+};
+
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watchEffect(
+  () => {
+    if (visible.value && deptOptions.value && deptOptions.value.length > 0) {
+      deptTreeRef.value.filter(deptName.value);
+    }
+  },
+  {
+    flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛�
+  }
+);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+const handleNodeClick = (data: DeptVO) => {
+  queryParams.value.deptId = data.id;
+  getList();
+};
+//鍒犻櫎tag
+const handleCloseTag = (user: UserVO, index: any) => {
+  if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) {
+    multipleTableRef.value.selection.forEach((u: UserVO, i: Number) => {
+      if (user.userId === u.userId) {
+        multipleTableRef.value.selection.splice(i, 1);
+      }
+    });
+  }
+  if (chooseUserList.value && chooseUserList.value.length > 0) {
+    chooseUserList.value.splice(index, 1);
+  }
+  multipleTableRef.value.toggleRowSelection(user, undefined);
+
+  if (userIds.value && userIds.value.length > 0) {
+    userIds.value.forEach((userId, i) => {
+      if (userId === user.userId) {
+        userIds.value.splice(i, 1);
+      }
+    });
+  }
+};
+const submitFileForm = async () => {
+  if (multiInstance.value === 'add') {
+    if (chooseUserList.value && chooseUserList.value.length > 0) {
+      loading.value = true;
+      let userIds = chooseUserList.value.map((item) => {
+        return item.userId;
+      });
+      let nickNames = chooseUserList.value.map((item) => {
+        return item.nickName;
+      });
+      let params = {
+        taskId: queryParams.value.taskId,
+        assignees: userIds,
+        assigneeNames: nickNames
+      };
+      await addMultiInstanceExecution(params);
+      emits('submitCallback');
+      loading.value = false;
+      proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+      visible.value = false;
+    }
+  } else {
+    if (selectionTask.value && selectionTask.value.length > 0) {
+      loading.value = true;
+      let taskIds = selectionTask.value.map((item: any) => {
+        return item.id;
+      });
+      let executionIds = selectionTask.value.map((item: any) => {
+        return item.executionId;
+      });
+      let assigneeIds = selectionTask.value.map((item: any) => {
+        return item.assignee;
+      });
+      let assigneeNames = selectionTask.value.map((item: any) => {
+        return item.assigneeName;
+      });
+      let params = {
+        taskId: queryParams.value.taskId,
+        taskIds: taskIds,
+        executionIds: executionIds,
+        assigneeIds: assigneeIds,
+        assigneeNames: assigneeNames
+      };
+      await deleteMultiInstanceExecution(params);
+      emits('submitCallback');
+      loading.value = false;
+      proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+      visible.value = false;
+    }
+  }
+};
+//浜嬩欢
+const emits = defineEmits(['submitCallback']);
+
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  getAddMultiInstanceList,
+  getDeleteMultiInstanceList
+});
+</script>
\ No newline at end of file
diff --git a/src/components/Process/submitVerify.vue b/src/components/Process/submitVerify.vue
new file mode 100644
index 0000000..b52854f
--- /dev/null
+++ b/src/components/Process/submitVerify.vue
@@ -0,0 +1,165 @@
+<template>
+  <el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" :close-on-click-modal="false">
+    <el-form v-loading="loading" :model="form" label-width="120px">
+      <el-form-item label="娑堟伅鎻愰啋">
+        <el-checkbox-group v-model="form.messageType">
+          <el-checkbox label="1" name="type" disabled>绔欏唴淇�</el-checkbox>
+          <el-checkbox label="2" name="type">閭欢</el-checkbox>
+          <el-checkbox label="3" name="type">鐭俊</el-checkbox>
+        </el-checkbox-group>
+      </el-form-item>
+      <el-form-item label="闄勪欢">
+        <fileUpload v-model="form.fileId" :fileType="['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'zip']" :fileSize="'20'"/>
+      </el-form-item>
+      <el-form-item label="鎶勯��">
+        <el-button type="primary" @click="openUserSelectCopy" icon="Plus" circle />
+          <el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
+            {{ user.userName }}
+          </el-tag>
+      </el-form-item>
+      <el-form-item label="瀹℃壒鎰忚" v-if="businessStatus === 'waiting'">
+        <el-input v-model="form.message" type="textarea" resize="none" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button v-loading="buttonLoading" @click="cancel">鍙栨秷</el-button>
+        <el-button v-loading="buttonLoading" type="primary" @click="handleCompleteTask"> 鎻愪氦 </el-button>
+        <el-button v-if="businessStatus === 'waiting'" v-loading="buttonLoading" type="danger" @click="handleBackProcess"> 閫�鍥� </el-button>
+      </span>
+    </template>
+    <UserSelect ref="userSelectCopyRef" :data="selectCopyUserIds" @confirm-call-back="userSelectCopyCallBack"></UserSelect>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { ComponentInternalInstance } from 'vue';
+import { ElForm } from 'element-plus';
+import { completeTask, backProcess, getBusinessStatus } from '@/api/workflow/task';
+import UserSelect from '@/components/UserSelect';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+import { UserVO } from '@/api/system/user/types';
+const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
+
+const props = defineProps({
+  taskVariables: {
+    type: Object as () => Record<string, any>,
+    default: {}
+  }
+});
+//閬僵灞�
+const loading = ref(true);
+//鎸夐挳
+const buttonLoading = ref(true);
+//娴佺▼鐘舵��
+const businessStatus = ref<string>('');
+//浠诲姟id
+const taskId = ref<string>('');
+//鎶勯�佷汉
+const selectCopyUserList = ref<UserVO[]>([]);
+//鎶勯�佷汉id
+const selectCopyUserIds = ref<string>('');
+  
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: '鎻愮ず'
+});
+
+const form = ref<Record<string, any>>({
+  taskId: undefined,
+  message: undefined,
+  variables: {},
+  messageType: ['1'],
+  wfCopyList: []
+});
+//鎵撳紑寮圭獥
+const openDialog = (id?: string) => {
+  selectCopyUserIds.value = ''
+  selectCopyUserList.value = []
+  form.value.fileId = undefined
+  taskId.value = id;
+  form.value.message = undefined;
+  dialog.visible = true;
+  loading.value = true;
+  buttonLoading.value = true;
+  nextTick(() => {
+    getBusinessStatus(taskId.value).then((response) => {
+      businessStatus.value = response.data;
+      loading.value = false;
+      buttonLoading.value = false;
+    });
+  });
+};
+
+onMounted(() => {});
+const emits = defineEmits(['submitCallback', 'cancelCallback']);
+
+/** 鍔炵悊娴佺▼ */
+const handleCompleteTask = async () => {
+  form.value.taskId = taskId.value;
+  form.value.taskVariables = props.taskVariables;
+  if(selectCopyUserList && selectCopyUserList.value.length > 0){
+    let wfCopyList = []
+    selectCopyUserList.value.forEach( e=> {
+      let copyUser = {
+        userId: e.userId,
+        userName: e.nickName
+      }
+      wfCopyList.push(copyUser)
+    })
+    form.value.wfCopyList = wfCopyList
+  }
+  await proxy?.$modal.confirm('鏄惁纭鎻愪氦锛�');
+  loading.value = true;
+  buttonLoading.value = true;
+  await completeTask(form.value).finally(() => (loading.value = false));
+  dialog.visible = false;
+  emits('submitCallback');
+  proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+};
+
+/** 椹冲洖娴佺▼ */
+const handleBackProcess = async () => {
+  form.value.taskId = taskId.value;
+  await proxy?.$modal.confirm('鏄惁纭椹冲洖鍒扮敵璇蜂汉锛�');
+  loading.value = true;
+  buttonLoading.value = true;
+  await backProcess(form.value).finally(() => (loading.value = false));
+  dialog.visible = false;
+  emits('submitCallback');
+  proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+};
+//鍙栨秷
+const cancel = async () => {
+  dialog.visible = false;
+  buttonLoading.value = false;
+  emits('cancelCallback');
+};
+//鎵撳紑鎶勯�佷汉鍛�
+const openUserSelectCopy = () => {
+  userSelectCopyRef.value.open();
+};
+//纭鎶勯�佷汉鍛�
+const userSelectCopyCallBack = (data: UserVO[]) => {
+  if(data && data.length > 0){
+    selectCopyUserList.value = data
+    selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
+  }
+}
+//鍒犻櫎鎶勯�佷汉鍛�
+const handleCopyCloseTag = (user: UserVO) => {
+  const userId = user.userId;
+  // 浣跨敤split鍒犻櫎鐢ㄦ埛
+  const index = selectCopyUserList.value.findIndex((item) => item.userId === userId);
+  selectCopyUserList.value.splice(index, 1);
+  selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
+};
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  openDialog
+});
+</script>
diff --git a/src/components/Process/sys-user.vue b/src/components/Process/sys-user.vue
new file mode 100644
index 0000000..dc100f3
--- /dev/null
+++ b/src/components/Process/sys-user.vue
@@ -0,0 +1,301 @@
+<template>
+  <el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body :close-on-click-modal="false">
+    <div class="p-2">
+      <el-row :gutter="20">
+        <!-- 閮ㄩ棬鏍� -->
+        <el-col :lg="4" :xs="24" style="">
+          <el-card shadow="hover">
+            <el-input v-model="deptName" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" prefix-icon="Search" clearable />
+            <el-tree
+              ref="deptTreeRef"
+              class="mt-2"
+              node-key="id"
+              :data="deptOptions"
+              :props="{ label: 'label', children: 'children' }"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              highlight-current
+              default-expand-all
+              @node-click="handleNodeClick"
+            ></el-tree>
+          </el-card>
+        </el-col>
+        <el-col :lg="20" :xs="24">
+          <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+            <div v-show="showSearch" class="search">
+              <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+                <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+                  <el-input v-model="queryParams.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" clearable style="width: 240px" @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName">
+                  <el-input v-model="queryParams.nickName" placeholder="璇疯緭鍏ョ敤鎴锋樀绉�" clearable style="width: 240px" @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+              </el-form>
+            </div>
+          </transition>
+
+          <el-card shadow="hover">
+            <template #header>
+              <el-row :gutter="10">
+                <right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="getUserList"></right-toolbar>
+              </el-row>
+            </template>
+
+            <el-table ref="multipleTableRef" v-loading="loading" :data="userList" row-key="userId" @selection-change="handleSelectionChange">
+              <el-table-column type="selection" width="50" align="center" :reserve-selection="true" />
+              <el-table-column key="userId" label="鐢ㄦ埛缂栧彿" align="center" prop="userId" />
+              <el-table-column key="userName" label="鐢ㄦ埛鍚嶇О" align="center" prop="userName" :show-overflow-tooltip="true" />
+              <el-table-column key="nickName" label="鐢ㄦ埛鏄电О" align="center" prop="nickName" :show-overflow-tooltip="true" />
+              <el-table-column key="phonenumber" label="鎵嬫満鍙风爜" align="center" prop="phonenumber" width="120" />
+              <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="160">
+                <template #default="scope">
+                  <span>{{ scope.row.createTime }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              v-model:page="queryParams.pageNum"
+              v-model:limit="queryParams.pageSize"
+              :total="total"
+              @pagination="getUserList"
+            />
+          </el-card>
+          <el-card shadow="hover">
+            <el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin: 2px" closable @close="handleCloseTag(user, index)"
+              >{{ user.userName }}
+            </el-tag>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+        <el-button @click="visible = false">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup name="User" lang="ts">
+import { deptTreeSelect } from '@/api/system/user';
+import { getUserListByPage, getUserListByIds } from '@/api/workflow/workflowUser';
+import { UserVO } from '@/api/system/user/types';
+import { DeptVO } from '@/api/system/dept/types';
+import { ComponentInternalInstance } from 'vue';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const props = defineProps({
+  // 瀹�
+  width: {
+    type: String,
+    default: '70%'
+  },
+  // 楂�
+  height: {
+    type: String,
+    default: '100%'
+  },
+  // 鏍囬
+  title: {
+    type: String,
+    default: '鐢ㄦ埛'
+  },
+  //鏄惁澶氶��
+  multiple: {
+    type: Boolean,
+    default: true
+  },
+  //鍥炴樉鐢ㄦ埛id
+  userIdList: {
+    type: Array,
+    default: () => []
+  }
+});
+const deptTreeRef = ref<ElTreeInstance>();
+const multipleTableRef = ref<ElTableInstance>();
+
+const userList = ref<UserVO[]>();
+const loading = ref(true);
+const showSearch = ref(true);
+const visible = ref(false);
+const total = ref(0);
+const deptName = ref('');
+const deptOptions = ref<DeptVO[]>([]);
+const chooseUserList = ref(ref<UserVO[]>());
+const userIds = ref<Array<number | string>>([]);
+//鏌ヨ鍙傛暟
+const queryParams = ref<Record<string, any>>({
+  pageNum: 1,
+  pageSize: 10,
+  userName: undefined,
+  nickName: undefined,
+  deptId: undefined
+});
+/** 鏌ヨ鐢ㄦ埛鍒楄〃 */
+const getUserList = async (userIdList: Array<number | string>) => {
+  deptOptions.value = [];
+  getTreeSelect();
+  userIds.value = userIdList;
+  visible.value = true;
+  loading.value = true;
+  const res = await getUserListByPage(queryParams.value);
+  loading.value = false;
+  userList.value = res.rows;
+  total.value = res.total;
+  if (userList.value && userIds.value.length > 0) {
+    const data = await getUserListByIds(userIds.value);
+    if (data.data && data.data.length > 0) {
+      chooseUserList.value = data.data;
+      data.data.forEach((user: UserVO) => {
+        multipleTableRef.value!.toggleRowSelection(
+          userList.value.find((item) => {
+            return item.userId == user.userId;
+          }),
+          true
+        );
+      });
+    }
+  }
+};
+
+const getList = async () => {
+  loading.value = true;
+  const res = await getUserListByPage(queryParams.value);
+  loading.value = false;
+  userList.value = res.rows;
+  total.value = res.total;
+
+  if (userList.value && userIds.value.length > 0) {
+    const data = await getUserListByIds(userIds.value);
+    if (data.data && data.data.length > 0) {
+      chooseUserList.value = data.data;
+      data.data.forEach((user: UserVO) => {
+        multipleTableRef.value!.toggleRowSelection(
+          userList.value.find((item) => {
+            return item.userId == user.userId;
+          }),
+          true
+        );
+      });
+    }
+  }
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getUserList(userIds.value);
+};
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryParams.value.pageNum = 1;
+  queryParams.value.deptId = undefined;
+  queryParams.value.userName = undefined;
+  queryParams.value.nickName = undefined;
+  deptTreeRef.value.setCurrentKey(null);
+  handleQuery();
+};
+
+/** 閫夋嫨鏉℃暟  */
+const handleSelectionChange = (selection: UserVO[]) => {
+  console.log(selection);
+  if (props.multiple) {
+    chooseUserList.value = selection.filter((element, index, self) => {
+      return self.findIndex((x) => x.userId === element.userId) === index;
+    });
+    selection.forEach((u) => {
+      if (chooseUserList.value && !chooseUserList.value.includes(u)) {
+        multipleTableRef.value!.toggleRowSelection(u, undefined);
+      }
+    });
+    userIds.value = chooseUserList.value.map((item) => {
+      return item.userId;
+    });
+  } else {
+    chooseUserList.value = selection;
+    if (selection.length > 1) {
+      let delRow = selection.shift();
+      multipleTableRef.value!.toggleRowSelection(delRow, undefined);
+    }
+    if (selection.length === 0) {
+      chooseUserList.value = [];
+    }
+  }
+};
+/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */
+const getTreeSelect = async () => {
+  const res = await deptTreeSelect();
+  deptOptions.value = res.data;
+};
+
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watchEffect(
+  () => {
+    if (visible.value && deptOptions.value && deptOptions.value.length > 0) {
+      deptTreeRef.value.filter(deptName.value);
+    }
+  },
+  {
+    flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛�
+  }
+);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+const handleNodeClick = (data: DeptVO) => {
+  queryParams.value.deptId = data.id;
+  getList();
+};
+//鍒犻櫎tag
+const handleCloseTag = (user: UserVO, index: any) => {
+  if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) {
+    multipleTableRef.value.selection.forEach((u: UserVO, i: number) => {
+      if (user.userId === u.userId) {
+        multipleTableRef.value.selection.splice(i, 1);
+      }
+    });
+  }
+  if (chooseUserList.value && chooseUserList.value.length > 0) {
+    chooseUserList.value.splice(index, 1);
+  }
+  multipleTableRef.value.toggleRowSelection(user, undefined);
+
+  if (userIds.value && userIds.value.length > 0) {
+    userIds.value.forEach((userId, i) => {
+      if (userId === user.userId) {
+        userIds.value.splice(i, 1);
+      }
+    });
+  }
+};
+const submitFileForm = async () => {
+  loading.value = true;
+  emits('submitCallback', chooseUserList);
+};
+const close = async () => {
+  visible.value = false;
+  loading.value = false;
+  emits('close');
+};
+//浜嬩欢
+const emits = defineEmits(['submitCallback', 'close']);
+
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  getUserList,
+  close
+});
+</script>
diff --git a/src/components/RoleSelect/index.vue b/src/components/RoleSelect/index.vue
index a9a07d3..ec5bcc7 100644
--- a/src/components/RoleSelect/index.vue
+++ b/src/components/RoleSelect/index.vue
@@ -196,4 +196,4 @@
 onMounted(() => {
   getList();
 });
-</script>
+</script>
\ No newline at end of file
diff --git a/src/components/UserSelect/index.vue b/src/components/UserSelect/index.vue
index 6a68f92..f2ef014 100644
--- a/src/components/UserSelect/index.vue
+++ b/src/components/UserSelect/index.vue
@@ -258,4 +258,4 @@
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped></style>
\ No newline at end of file
diff --git a/src/directive/common/copyText.ts b/src/directive/common/copyText.ts
index b6805f8..0e605d3 100644
--- a/src/directive/common/copyText.ts
+++ b/src/directive/common/copyText.ts
@@ -2,9 +2,10 @@
  * v-copyText 澶嶅埗鏂囨湰鍐呭
  * Copyright (c) 2022 ruoyi
  */
+import { DirectiveBinding } from 'vue';
 
 export default {
-  beforeMount(el: any, { value, arg }: any) {
+  beforeMount(el: any, { value, arg }: DirectiveBinding) {
     if (arg === 'callback') {
       el.$copyCallback = value;
     } else {
diff --git a/src/enums/bpmn/IndexEnums.ts b/src/enums/bpmn/IndexEnums.ts
new file mode 100644
index 0000000..8c39823
--- /dev/null
+++ b/src/enums/bpmn/IndexEnums.ts
@@ -0,0 +1,17 @@
+export enum AllocationTypeEnum {
+  USER = 'user',
+  CANDIDATE = 'candidate',
+  YOURSELF = 'yourself',
+  SPECIFY = 'specify'
+}
+
+export enum SpecifyDescEnum {
+  SPECIFY_MULTIPLE = 'specifyMultiple',
+  SPECIFY_SINGLE = 'specifySingle'
+}
+
+export enum MultiInstanceTypeEnum {
+  SERIAL = 'serial',
+  PARALLEL = 'parallel',
+  NONE = 'none'
+}
diff --git a/src/hooks/useDialog.ts b/src/hooks/useDialog.ts
index 547f199..68440bf 100644
--- a/src/hooks/useDialog.ts
+++ b/src/hooks/useDialog.ts
@@ -28,4 +28,4 @@
     openDialog,
     closeDialog
   };
-};
+};
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index 29a5ee5..592de58 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -15,6 +15,12 @@
 // 娉ㄥ唽鎻掍欢
 import plugins from './plugins/index'; // plugins
 
+// 楂樹寒缁勪欢
+// import 'highlight.js/styles/a11y-light.css';
+import 'highlight.js/styles/atom-one-dark.css';
+import 'highlight.js/lib/common';
+import HighLight from '@highlightjs/vue-plugin';
+
 // svg鍥炬爣
 import 'virtual:svg-icons-register';
 import ElementIcons from '@/plugins/svgicon';
@@ -38,6 +44,7 @@
 
 const app = createApp(App);
 
+app.use(HighLight);
 app.use(ElementIcons);
 app.use(router);
 app.use(store);
diff --git a/src/store/modules/modeler.ts b/src/store/modules/modeler.ts
new file mode 100644
index 0000000..7b19c1a
--- /dev/null
+++ b/src/store/modules/modeler.ts
@@ -0,0 +1,76 @@
+import { Modeler, Modeling, Canvas, ElementRegistry, Moddle, BpmnFactory } from 'bpmn';
+
+type ModelerStore = {
+  modeler: Modeler | undefined;
+  moddle: Moddle | undefined;
+  modeling: Modeling | undefined;
+  canvas: Canvas | undefined;
+  elementRegistry: ElementRegistry | undefined;
+  bpmnFactory: BpmnFactory | undefined;
+  // 娴佺▼瀹氫箟鏍硅妭鐐逛俊鎭�
+  procDefId: string | undefined;
+  procDefName: string | undefined;
+};
+
+const defaultState: ModelerStore = {
+  modeler: undefined,
+  moddle: undefined,
+  modeling: undefined,
+  canvas: undefined,
+  elementRegistry: undefined,
+  bpmnFactory: undefined,
+  procDefId: undefined,
+  procDefName: undefined
+};
+export const useModelerStore = defineStore('modeler', () => {
+  let modeler = defaultState.modeler;
+  let moddle = defaultState.moddle;
+  let modeling = defaultState.modeling;
+  let canvas = defaultState.canvas;
+  let elementRegistry = defaultState.elementRegistry;
+  let bpmnFactory = defaultState.bpmnFactory;
+  const procDefId = ref(defaultState.procDefId);
+  const procDefName = ref(defaultState.procDefName);
+
+  const getModeler = () => modeler;
+  const getModdle = () => moddle;
+  const getModeling = (): Modeling | undefined => modeling;
+  const getCanvas = (): Canvas | undefined => canvas;
+  const getElRegistry = (): ElementRegistry | undefined => elementRegistry;
+  const getBpmnFactory = (): BpmnFactory | undefined => bpmnFactory;
+  const getProcDefId = (): string | undefined => procDefId.value;
+  const getProcDefName = (): string | undefined => procDefName.value;
+
+  // 璁剧疆鏍硅妭鐐�
+  const setModeler = (modelers: Modeler | undefined) => {
+    if (modelers) {
+      modeler = modelers;
+      modeling = modelers.get<Modeling>('modeling');
+      moddle = modelers.get<Moddle>('moddle');
+      canvas = modelers.get<Canvas>('canvas');
+      bpmnFactory = modelers.get<BpmnFactory>('bpmnFactory');
+      elementRegistry = modelers.get<ElementRegistry>('elementRegistry');
+    } else {
+      modeling = moddle = canvas = elementRegistry = bpmnFactory = undefined;
+    }
+  };
+  // 璁剧疆娴佺▼瀹氫箟鏍硅妭鐐逛俊鎭�
+  const setProcDef = (modeler: Modeler | undefined) => {
+    procDefId.value = modeler.get<Canvas>('canvas').getRootElement().businessObject.get('id');
+    procDefName.value = modeler.get<Canvas>('canvas').getRootElement().businessObject.get('name');
+  };
+
+  return {
+    getModeler,
+    getModdle,
+    getModeling,
+    getCanvas,
+    getElRegistry,
+    getBpmnFactory,
+    getProcDefId,
+    getProcDefName,
+    setModeler,
+    setProcDef
+  };
+});
+export default useModelerStore;
diff --git a/src/types/bpmn/editor/global.d.ts b/src/types/bpmn/editor/global.d.ts
new file mode 100644
index 0000000..1b9a634
--- /dev/null
+++ b/src/types/bpmn/editor/global.d.ts
@@ -0,0 +1,13 @@
+import { MessageApiInjection } from 'naive-ui/lib/message/src/MessageProvider';
+
+declare global {
+  interface Window {
+    bpmnInstances: any;
+    __messageBox: MessageApiInjection;
+    URL: any;
+  }
+}
+
+declare interface Window {
+  bpmnInstances: any;
+}
diff --git a/src/types/bpmn/index.d.ts b/src/types/bpmn/index.d.ts
new file mode 100644
index 0000000..f8e8d15
--- /dev/null
+++ b/src/types/bpmn/index.d.ts
@@ -0,0 +1,15 @@
+declare module 'bpmn' {
+  import type modeler from 'bpmn-js/lib/Modeler';
+  import type modeling from 'bpmn-js/lib/features/modeling/Modeling';
+  import type canvas from 'diagram-js/lib/core/Canvas';
+  import type elementRegistry from 'diagram-js/lib/core/ElementRegistry';
+  import type bpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory';
+
+  export type Modeler = modeler;
+  export type Modeling = modeling;
+  export type Canvas = canvas;
+  export type ElementRegistry = elementRegistry;
+  export type Moddle = import('moddle').Moddle;
+  export type ModdleElement = import('moddle').ModdleElement;
+  export type BpmnFactory = bpmnFactory;
+}
diff --git a/src/types/bpmn/moddle.d.ts b/src/types/bpmn/moddle.d.ts
new file mode 100644
index 0000000..1ed7933
--- /dev/null
+++ b/src/types/bpmn/moddle.d.ts
@@ -0,0 +1,37 @@
+declare module 'moddle' {
+  import type { Element as element } from 'bpmn-js/lib/model/Types';
+
+  export type Element = {
+    get<T>(name: string): T;
+
+    set(name: string, value: any): void;
+  } & element;
+
+  export interface ModdleElement extends Element {
+    $model: Moddle;
+    readonly $type: string;
+    $attrs: object | {};
+    $parent: any;
+    businessObject: ModdleElement;
+    type: string;
+
+    [field: string]: any;
+
+    hasType(element: ModdleElement, type?: string): boolean;
+  }
+
+  export interface Package {
+    name: string;
+    prefix: string;
+  }
+
+  export interface Moddle {
+    typeCache: Record<string, ModdleElement>;
+
+    getPackage: typeof Registry.prototype.getPackage;
+
+    getPackages: typeof Registry.prototype.getPackages;
+
+    create(type: string, attrs?: any): ModdleElement;
+  }
+}
diff --git a/src/types/bpmn/panel.d.ts b/src/types/bpmn/panel.d.ts
new file mode 100644
index 0000000..0d0cac4
--- /dev/null
+++ b/src/types/bpmn/panel.d.ts
@@ -0,0 +1,75 @@
+declare module 'bpmnDesign' {
+  import { AllocationTypeEnum, SpecifyDescEnum, MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
+
+  export interface ParamVO {
+    type: string;
+    name: string;
+    value: string;
+  }
+
+  export interface TaskListenerVO {
+    event: string;
+    type: string;
+    name: string;
+    className: string;
+    params: ParamVO[];
+  }
+
+  export interface ExecutionListenerVO {
+    event: string;
+    type: string;
+    className: string;
+    params: ParamVO[];
+  }
+
+  interface BasePanel {
+    id: string;
+    name: string;
+  }
+  export interface ProcessPanel extends BasePanel {}
+
+  export interface TaskPanel extends BasePanel {
+    allocationType: AllocationTypeEnum;
+    specifyDesc: SpecifyDescEnum;
+    multiInstanceType: MultiInstanceTypeEnum;
+    async?: boolean;
+    priority?: number;
+    skipExpression?: string;
+    isForCompensation?: boolean;
+    triggerServiceTask?: boolean;
+    autoStoreVariables?: boolean;
+    ruleVariablesInput?: string;
+    excludeTaskListener?: boolean;
+    exclude?: boolean;
+    class?: string;
+    dueDate?: string;
+    fixedAssignee?: string;
+
+    candidateUsers?: string;
+    assignee?: string;
+    candidateGroups?: string;
+    collection?: string;
+    elementVariable?: string;
+    completionCondition?: string;
+    isSequential?: boolean;
+
+    loopCharacteristics?: {
+      collection: string;
+      elementVariable: string;
+      isSequential: boolean;
+      completionCondition: {
+        body: string;
+      };
+    };
+  }
+
+  export interface StartEndPanel extends BasePanel {}
+  export interface GatewayPanel extends BasePanel {}
+  export interface SequenceFlowPanel extends BasePanel {
+    conditionExpression: {
+      body: string;
+    };
+    conditionExpressionValue: string;
+    skipExpression: string;
+  }
+}
diff --git a/src/views/workflow/category/index.vue b/src/views/workflow/category/index.vue
new file mode 100644
index 0000000..3fa4307
--- /dev/null
+++ b/src/views/workflow/category/index.vue
@@ -0,0 +1,263 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="search">
+        <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+          <el-form-item label="鍒嗙被鍚嶇О" prop="categoryName">
+            <el-input v-model="queryParams.categoryName" placeholder="璇疯緭鍏ュ垎绫诲悕绉�" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item label="鍒嗙被缂栫爜" prop="categoryCode">
+            <el-input v-model="queryParams.categoryCode" placeholder="璇疯緭鍏ュ垎绫荤紪鐮�" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button v-hasPermi="['workflow:category:add']" type="primary" plain icon="Plus" @click="handleAdd()">鏂板</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">灞曞紑/鎶樺彔</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
+        </el-row>
+      </template>
+      <el-table
+        ref="categoryTableRef"
+        v-loading="loading"
+        :data="categoryList"
+        row-key="id"
+        :default-expand-all="isExpandAll"
+        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+      >
+        <el-table-column label="鍒嗙被鍚嶇О" prop="categoryName" />
+        <el-table-column label="鍒嗙被缂栫爜" align="center" prop="categoryCode" />
+        <el-table-column label="鎺掑簭" align="center" prop="sortNum" />
+        <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="淇敼" placement="top">
+              <el-button v-hasPermi="['workflow:category:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
+            </el-tooltip>
+            <el-tooltip content="鏂板" placement="top">
+              <el-button v-hasPermi="['workflow:category:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
+            </el-tooltip>
+            <el-tooltip content="鍒犻櫎" placement="top">
+              <el-button v-hasPermi="['workflow:category:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+    <!-- 娣诲姞鎴栦慨鏀规祦绋嬪垎绫诲璇濇 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+      <el-form ref="categoryFormRef" v-loading="loading" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="鐖剁骇鍒嗙被" prop="parentId">
+          <el-tree-select
+            v-model="form.parentId"
+            :data="categoryOptions"
+            :props="{ value: 'id', label: 'categoryName', children: 'children' }"
+            value-key="id"
+            placeholder="璇烽�夋嫨鐖剁骇id"
+            check-strictly
+          />
+        </el-form-item>
+        <el-form-item label="鍒嗙被鍚嶇О" prop="categoryName">
+          <el-input v-model="form.categoryName" placeholder="璇疯緭鍏ュ垎绫诲悕绉�" />
+        </el-form-item>
+        <el-form-item label="鍒嗙被缂栫爜" prop="categoryCode">
+          <el-input v-model="form.categoryCode" placeholder="璇疯緭鍏ュ垎绫荤紪鐮�" />
+        </el-form-item>
+        <el-form-item label="鎺掑簭" prop="sortNum">
+          <el-input-number v-model="form.sortNum" placeholder="璇疯緭鍏ユ帓搴�" controls-position="right" :min="0" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Category" lang="ts">
+import { listCategory, getCategory, delCategory, addCategory, updateCategory } from '@/api/workflow/category';
+import { CategoryVO, CategoryQuery, CategoryForm } from '@/api/workflow/category/types';
+
+type CategoryOption = {
+  id: number;
+  categoryName: string;
+  children?: CategoryOption[];
+};
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const categoryList = ref<CategoryVO[]>([]);
+const categoryOptions = ref<CategoryOption[]>([]);
+const buttonLoading = ref(false);
+const showSearch = ref(true);
+const isExpandAll = ref(true);
+const loading = ref(false);
+
+const queryFormRef = ref<ElFormInstance>();
+const categoryFormRef = ref<ElFormInstance>();
+const categoryTableRef = ref<ElTableInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: CategoryForm = {
+  id: undefined,
+  categoryName: undefined,
+  categoryCode: undefined,
+  parentId: undefined,
+  sortNum: 0
+};
+
+const data = reactive<PageData<CategoryForm, CategoryQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    categoryName: undefined,
+    categoryCode: undefined
+  },
+  rules: {
+    id: [{ required: true, message: '涓婚敭涓嶈兘涓虹┖', trigger: 'blur' }],
+    categoryName: [{ required: true, message: '鍒嗙被鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' }],
+    categoryCode: [{ required: true, message: '鍒嗙被缂栫爜涓嶈兘涓虹┖', trigger: 'blur' }],
+    parentId: [{ required: true, message: '鐖剁骇id涓嶈兘涓虹┖', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ娴佺▼鍒嗙被鍒楄〃 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listCategory(queryParams.value);
+  const data = proxy?.handleTree<CategoryVO>(res.data, 'id', 'parentId');
+  if (data) {
+    categoryList.value = data;
+    loading.value = false;
+  }
+};
+
+/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */
+const getTreeselect = async () => {
+  const res = await listCategory();
+  categoryOptions.value = [];
+  const data: CategoryOption = { id: 0, categoryName: '椤剁骇鑺傜偣', children: [] };
+  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
+  categoryOptions.value.push(data);
+};
+
+// 鍙栨秷鎸夐挳
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+// 琛ㄥ崟閲嶇疆
+const reset = () => {
+  form.value = { ...initFormData };
+  categoryFormRef.value?.resetFields();
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getList();
+};
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+const handleAdd = (row?: CategoryVO) => {
+  dialog.visible = true;
+  dialog.title = '娣诲姞娴佺▼鍒嗙被';
+  nextTick(() => {
+    reset();
+    getTreeselect();
+    if (row != null && row.id) {
+      form.value.parentId = row.id;
+    } else {
+      form.value.parentId = 0;
+    }
+  });
+};
+
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+const handleToggleExpandAll = () => {
+  isExpandAll.value = !isExpandAll.value;
+  toggleExpandAll(categoryList.value, isExpandAll.value);
+};
+
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+const toggleExpandAll = (data: CategoryVO[], status: boolean) => {
+  data.forEach((item) => {
+    categoryTableRef.value?.toggleRowExpansion(item, status);
+    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
+  });
+};
+
+/** 淇敼鎸夐挳鎿嶄綔 */
+const handleUpdate = (row: CategoryVO) => {
+  loading.value = true;
+  dialog.visible = true;
+  dialog.title = '淇敼娴佺▼鍒嗙被';
+  nextTick(async () => {
+    reset();
+    await getTreeselect();
+    if (row != null) {
+      form.value.parentId = row.id;
+    }
+    const res = await getCategory(row.id);
+    loading.value = false;
+    Object.assign(form.value, res.data);
+  });
+};
+
+/** 鎻愪氦鎸夐挳 */
+const submitForm = () => {
+  categoryFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateCategory(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addCategory(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row: CategoryVO) => {
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎娴佺▼鍒嗙被缂栧彿涓�"' + row.id + '"鐨勬暟鎹」锛�');
+  loading.value = true;
+  await delCategory(row.id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
diff --git a/src/views/workflow/leave/index.vue b/src/views/workflow/leave/index.vue
new file mode 100644
index 0000000..c7550b2
--- /dev/null
+++ b/src/views/workflow/leave/index.vue
@@ -0,0 +1,381 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="search">
+        <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+          <el-form-item label="璇峰亣澶╂暟" prop="startLeaveDays">
+            <el-input v-model="queryParams.startLeaveDays" placeholder="璇疯緭鍏ヨ鍋囧ぉ鏁�" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item prop="endLeaveDays"> 鑷� </el-form-item>
+          <el-form-item prop="endLeaveDays">
+            <el-input v-model="queryParams.endLeaveDays" placeholder="璇疯緭鍏ヨ鍋囧ぉ鏁�" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button v-hasPermi="['demo:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">鏂板</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-hasPermi="['demo:leave:export']" type="warning" plain icon="Download" @click="handleExport">瀵煎嚭</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" :data="leaveList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column v-if="false" label="涓婚敭" align="center" prop="id" />
+        <el-table-column label="璇峰亣绫诲瀷" align="center">
+          <template #default="scope">
+            <el-tag>{{ options.find((e) => e.value === scope.row.leaveType)?.label }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="寮�濮嬫椂闂�" align="center" prop="startDate">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="缁撴潫鏃堕棿" align="center" prop="endDate">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="璇峰亣澶╂暟" align="center" prop="leaveDays" />
+        <el-table-column label="璇峰亣鍘熷洜" align="center" prop="remark" />
+        <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70">
+          <template #default="scope">
+            <el-tag type="success">{{ scope.row.processInstanceVo.businessStatusName }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip
+              v-if="
+                scope.row.processInstanceVo.businessStatus === 'draft' ||
+                scope.row.processInstanceVo.businessStatus === 'cancel' ||
+                scope.row.processInstanceVo.businessStatus === 'back'
+              "
+              content="淇敼"
+              placement="top"
+            >
+              <el-button v-hasPermi="['demo:leave:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
+            </el-tooltip>
+            <el-tooltip
+              v-if="
+                scope.row.processInstanceVo.businessStatus === 'draft' ||
+                scope.row.processInstanceVo.businessStatus === 'cancel' ||
+                scope.row.processInstanceVo.businessStatus === 'back'
+              "
+              content="鍒犻櫎"
+              placement="top"
+            >
+              <el-button v-hasPermi="['demo:leave:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
+            </el-tooltip>
+            <el-tooltip v-if="scope.row.processInstanceVo.businessStatus === 'waiting'" content="鎾ら攢" placement="top">
+              <el-button link type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.processInstanceVo.id)"></el-button>
+            </el-tooltip>
+            <el-tooltip v-if="scope.row.processInstanceVo.businessStatus === 'waiting'" content="瀹℃壒璁板綍" placement="top">
+              <el-button link type="primary" icon="Document" @click="handleApprovalRecord(scope.row.processInstanceVo.id)"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+    </el-card>
+    <!-- 娣诲姞鎴栦慨鏀硅鍋囧璇濇 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body>
+      <el-form ref="leaveFormRef" v-loading="loading" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="璇峰亣绫诲瀷" prop="leaveType">
+          <el-select v-model="form.leaveType" placeholder="璇烽�夋嫨璇峰亣绫诲瀷" style="width: 100%">
+            <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="璇峰亣鏃堕棿">
+          <el-date-picker
+            v-model="leaveTime"
+            type="daterange"
+            range-separator="To"
+            start-placeholder="寮�濮嬫椂闂�"
+            end-placeholder="缁撴潫鏃堕棿"
+            @change="changeLeaveTime()"
+          />
+        </el-form-item>
+        <el-form-item label="璇峰亣澶╂暟" prop="leaveDays">
+          <el-input v-model="form.leaveDays" disabled type="number" placeholder="璇疯緭鍏ヨ鍋囧ぉ鏁�" />
+        </el-form-item>
+        <el-form-item label="璇峰亣鍘熷洜" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ鍋囧師鍥�" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="info" @click="submitForm('draft')">鏆� 瀛�</el-button>
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm('submit')">鎻� 浜�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 鎻愪氦缁勪欢 -->
+    <submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+  </div>
+</template>
+
+<script setup name="Leave" lang="ts">
+import { addLeave, delLeave, getLeave, listLeave, updateLeave } from '@/api/workflow/leave';
+import { cancelProcessApply } from '@/api/workflow/processInstance';
+import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
+import { startWorkFlow } from '@/api/workflow/task';
+import SubmitVerify from '@/components/Process/submitVerify.vue';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import { AxiosResponse } from 'axios';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const leaveList = ref<LeaveVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const leaveTime = ref<Array<string>>([]);
+const options = [
+  {
+    value: '1',
+    label: '浜嬪亣'
+  },
+  {
+    value: '2',
+    label: '璋冧紤'
+  },
+  {
+    value: '3',
+    label: '鐥呭亣'
+  },
+  {
+    value: '4',
+    label: '濠氬亣'
+  }
+];
+//鎻愪氦缁勪欢
+const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+
+const queryFormRef = ref<ElFormInstance>();
+const leaveFormRef = ref<ElFormInstance>();
+
+const submitFormData = ref<Record<string, any>>({
+  businessKey: '',
+  processKey: '',
+  variables: {}
+});
+const taskVariables = ref<Record<string, any>>({});
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: LeaveForm = {
+  id: undefined,
+  leaveType: undefined,
+  startDate: undefined,
+  endDate: undefined,
+  leaveDays: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<LeaveForm, LeaveQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    startLeaveDays: undefined,
+    endLeaveDays: undefined
+  },
+  rules: {
+    id: [{ required: true, message: '涓婚敭涓嶈兘涓虹┖', trigger: 'blur' }],
+    leaveType: [{ required: true, message: '璇峰亣绫诲瀷涓嶈兘涓虹┖', trigger: 'blur' }],
+    leaveTime: [{ required: true, message: '璇峰亣鏃堕棿涓嶈兘涓虹┖', trigger: 'blur' }],
+    leaveDays: [{ required: true, message: '璇峰亣澶╂暟涓嶈兘涓虹┖', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ璇峰亣鍒楄〃 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listLeave(queryParams.value);
+  leaveList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 鍙栨秷鎸夐挳 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 琛ㄥ崟閲嶇疆 */
+const reset = () => {
+  form.value = { ...initFormData };
+  leaveTime.value = [];
+  leaveFormRef.value?.resetFields();
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 澶氶�夋閫変腑鏁版嵁 */
+const handleSelectionChange = (selection: LeaveVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+const handleAdd = () => {
+  dialog.visible = true;
+  dialog.title = '娣诲姞璇峰亣鐢宠';
+  nextTick(() => {
+    reset();
+  });
+};
+
+const changeLeaveTime = () => {
+  const startDate = new Date(leaveTime.value[0]).getTime();
+  const endDate = new Date(leaveTime.value[1]).getTime();
+  const diffInMilliseconds = endDate - startDate;
+  form.value.leaveDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24));
+};
+/** 淇敼鎸夐挳鎿嶄綔 */
+const handleUpdate = (row?: LeaveVO) => {
+  buttonLoading.value = false;
+  dialog.visible = true;
+  dialog.title = '淇敼璇峰亣鐢宠';
+  nextTick(async () => {
+    reset();
+    const _id = row?.id || ids.value[0];
+    const res = await getLeave(_id);
+    Object.assign(form.value, res.data);
+    leaveTime.value = [];
+    leaveTime.value.push(form.value.startDate);
+    leaveTime.value.push(form.value.endDate);
+  });
+};
+
+/** 鎻愪氦鎸夐挳 */
+const submitForm = (status: string) => {
+  if (leaveTime.value.length === 0) {
+    proxy?.$modal.msgError('璇峰亣鏃堕棿涓嶈兘涓虹┖');
+    return;
+  }
+  leaveFormRef.value?.validate(async (valid: boolean) => {
+    form.value.startDate = leaveTime.value[0];
+    form.value.endDate = leaveTime.value[1];
+    if (valid) {
+      buttonLoading.value = true;
+      let res: AxiosResponse<LeaveVO>;
+      if (form.value.id) {
+        res = await updateLeave(form.value);
+      } else {
+        res = await addLeave(form.value);
+      }
+      form.value = res.data;
+      if (status === 'draft') {
+        buttonLoading.value = false;
+        proxy?.$modal.msgSuccess('鏆傚瓨鎴愬姛');
+        dialog.visible = false;
+        await getList();
+      } else {
+        await handleStartWorkFlow(res.data);
+      }
+    }
+  });
+};
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row?: LeaveVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎璇峰亣缂栧彿涓�"' + _ids + '"鐨勬暟鎹」锛�').finally(() => (loading.value = false));
+  await delLeave(_ids);
+  proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+  await getList();
+};
+
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+const handleExport = () => {
+  proxy?.download(
+    'demo/leave/export',
+    {
+      ...queryParams.value
+    },
+    `leave_${new Date().getTime()}.xlsx`
+  );
+};
+
+//鎻愪氦鐢宠
+const handleStartWorkFlow = async (data: LeaveVO) => {
+  submitFormData.value.processKey = 'leave7';
+  submitFormData.value.businessKey = data.id;
+  //娴佺▼鍙橀噺
+  taskVariables.value = {
+    entity: data,
+    leaveDays: data.leaveDays,
+    userList: [1, 2],
+    userList2: [1, 2]
+  };
+  submitFormData.value.variables = taskVariables.value;
+  const resp = await startWorkFlow(submitFormData.value);
+  if (submitVerifyRef.value) {
+    buttonLoading.value = false;
+    submitVerifyRef.value.openDialog(resp.data.taskId);
+  }
+};
+//瀹℃壒璁板綍
+const handleApprovalRecord = (id: string) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(id);
+  }
+};
+//鎻愪氦鍥炶皟
+const submitCallback = async () => {
+  dialog.visible = false;
+  handleQuery();
+};
+/** 鎾ら攢鎸夐挳鎿嶄綔 */
+const handleCancelProcessApply = async (id: string) => {
+  await proxy?.$modal.confirm('鏄惁纭鎾ら攢褰撳墠鍗曟嵁锛�');
+  loading.value = true;
+  await cancelProcessApply(id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('鎾ら攢鎴愬姛');
+};
+onMounted(() => {
+  getList();
+});
+</script>
diff --git a/src/views/workflow/model/design.vue b/src/views/workflow/model/design.vue
new file mode 100644
index 0000000..0f6b118
--- /dev/null
+++ b/src/views/workflow/model/design.vue
@@ -0,0 +1,65 @@
+<template>
+  <div class="design">
+    <el-dialog v-model="visible" width="100%" fullscreen :title="title">
+      <div class="modeler">
+        <bpmn-design ref="bpmnDesignRef" @save-call-back="saveCallBack"></bpmn-design>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="Design">
+import { getInfo, editModelXml } from '@/api/workflow/model';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+import { ModelForm } from '@/api/workflow/model/types';
+import BpmnDesign from '@/components/BpmnDesign';
+import useDialog from '@/hooks/useDialog';
+const bpmnDesignRef = ref<InstanceType<typeof BpmnDesign>>();
+const modelForm = ref<ModelForm>();
+const emit = defineEmits(['closeCallBack']);
+const { visible, title } = useDialog({
+  title: '缂栬緫娴佺▼'
+});
+const modelId = ref('');
+const open = async (id) => {
+  visible.value = true;
+  modelId.value = id;
+  const { data } = await getInfo(id);
+  modelForm.value = data;
+  bpmnDesignRef.value.initDiagram(modelForm.value.xml);
+};
+//淇濆瓨妯″瀷
+const saveCallBack = async (data) => {
+  await proxy?.$modal.confirm('鏄惁纭淇濆瓨锛�');
+  modelForm.value.id = modelId.value;
+  modelForm.value.xml = data.xml;
+  modelForm.value.svg = data.svg;
+  modelForm.value.key = data.key;
+  modelForm.value.name = data.name;
+  editModelXml(modelForm.value).then((res) => {
+    if (res.code === 200) {
+      visible.value = false;
+      proxy?.$modal.msgSuccess('淇濆瓨鎴愬姛');
+      emit('closeCallBack', data);
+    }
+  });
+};
+
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  open
+});
+</script>
+
+<style lang="scss" scoped>
+.design {
+  :deep(.el-dialog .el-dialog__body) {
+    max-height: 100% !important;
+    min-height: calc(100vh - 50px);
+  }
+}
+</style>
diff --git a/src/views/workflow/model/index.vue b/src/views/workflow/model/index.vue
new file mode 100644
index 0000000..93cd675
--- /dev/null
+++ b/src/views/workflow/model/index.vue
@@ -0,0 +1,360 @@
+<template>
+  <div class="p-2">
+    <el-row :gutter="20">
+      <!-- 娴佺▼鍒嗙被鏍� -->
+      <el-col :lg="4" :xs="24" style="">
+        <el-card shadow="hover">
+          <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable />
+          <el-tree
+            ref="categoryTreeRef"
+            class="mt-2"
+            node-key="id"
+            :data="categoryOptions"
+            :props="{ label: 'categoryName', children: 'children' }"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            highlight-current
+            default-expand-all
+            @node-click="handleNodeClick"
+          ></el-tree>
+        </el-card>
+      </el-col>
+      <el-col :lg="20" :xs="24">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+          <div v-show="showSearch" class="mb-[10px]">
+            <el-card shadow="hover">
+              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="80px">
+                <el-form-item label="妯″瀷鍚嶇О" prop="name">
+                  <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユā鍨嬪悕绉�" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="妯″瀷KEY" prop="key">
+                  <el-input v-model="queryParams.key" placeholder="璇疯緭鍏ユā鍨婯EY" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+              </el-form>
+            </el-card>
+          </div>
+        </transition>
+        <el-card shadow="hover">
+          <template #header>
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">鏂板</el-button>
+              </el-col>
+              <el-col :span="1.5">
+                <el-button type="success" plain icon="Edit" :disabled="multiple" @click="handleUpdate()">淇敼</el-button>
+              </el-col>
+              <el-col :span="1.5">
+                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">鍒犻櫎</el-button>
+              </el-col>
+              <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
+            </el-row>
+          </template>
+
+          <el-table v-loading="loading" :data="modelList" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+            <el-table-column fixed align="center" prop="name" label="妯″瀷鍚嶇О"></el-table-column>
+            <el-table-column align="center" prop="key" label="妯″瀷KEY"></el-table-column>
+            <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90">
+              <template #default="scope"> v{{ scope.row.version }}.0</template>
+            </el-table-column>
+            <el-table-column align="center" prop="metaInfo" label="澶囨敞璇存槑" min-width="130"></el-table-column>
+            <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column>
+            <el-table-column align="center" prop="lastUpdateTime" label="鏇存柊鏃堕棿" width="160"></el-table-column>
+            <el-table-column fixed="right" label="鎿嶄綔" align="center" width="180" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Pointer" @click="clickDesign(scope.row.id)">璁捐娴佺▼</el-button>
+                  </el-col>
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Download" @click="clickExportZip(scope.row)">瀵煎嚭</el-button>
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="ScaleToOriginal" @click="clickDeploy(scope.row.id, scope.row.key)">
+                      娴佺▼閮ㄧ讲
+                    </el-button>
+                  </el-col>
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+          />
+        </el-card>
+      </el-col>
+    </el-row>
+    <!-- 璁捐娴佺▼寮�濮� -->
+    <Design ref="designRef" @close-call-back="handleQuery"></Design>
+    <!-- 璁捐娴佺▼缁撴潫 -->
+    <!-- 娣诲姞妯″瀷瀵硅瘽妗� -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="650px" append-to-body :close-on-click-modal="false">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="妯″瀷鍚嶇О锛�" prop="name">
+          <el-input v-model="form.name" :disabled="ids && ids.length > 0" maxlength="20" show-word-limit />
+        </el-form-item>
+        <el-form-item label="妯″瀷KEY锛�" prop="key">
+          <el-input v-model="form.key" :disabled="ids && ids.length > 0" maxlength="20" show-word-limit />
+        </el-form-item>
+        <el-form-item label="娴佺▼鍒嗙被" prop="categoryCode">
+          <el-tree-select
+            v-model="form.categoryCode"
+            :data="categoryOptions"
+            :props="{ value: 'categoryCode', label: 'categoryName', children: 'children' }"
+            value-key="categoryCode"
+            placeholder="璇烽�夋嫨娴佺▼鍒嗙被"
+            check-strictly
+          />
+        </el-form-item>
+        <el-form-item label="澶囨敞锛�" prop="description">
+          <el-input v-model="form.description" type="textarea" maxlength="200" show-word-limit></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="Model">
+import Design from './design.vue';
+import { listModel, addModel, delModel, modelDeploy, getInfo, update } from '@/api/workflow/model';
+import { ModelQuery, ModelForm, ModelVO } from '@/api/workflow/model/types';
+import { listCategory } from '@/api/workflow/category';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const formRef = ref<ElFormInstance>();
+const queryFormRef = ref<ElFormInstance>();
+const categoryTreeRef = ref<ElTreeInstance>();
+const designRef = ref<InstanceType<typeof Design>>();
+
+type CategoryOption = {
+  categoryCode: string;
+  categoryName: string;
+  children?: CategoryOption[];
+};
+
+const buttonLoading = ref(false);
+const loading = ref(true);
+const ids = ref<string[]>([]);
+const single = ref(true);
+const multiple = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+const modelList = ref<ModelVO[]>([]);
+const categoryOptions = ref<CategoryOption[]>([]);
+const categoryName = ref('');
+const modelId = ref<string>('');
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ModelForm = {
+  id: '',
+  name: '',
+  key: '',
+  categoryCode: '',
+  xml: '',
+  svg: '',
+  description: ''
+};
+const data = reactive<PageData<ModelForm, ModelQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: '',
+    key: '',
+    categoryCode: ''
+  },
+  rules: {
+    name: [{ required: true, message: '妯″瀷涓嶈兘涓虹┖', trigger: 'blur' }],
+    key: [{ required: true, message: '妯″瀷KEY涓嶈兘涓虹┖', trigger: 'blur' }],
+    categoryCode: [{ required: true, message: '娴佺▼鍒嗙被涓嶈兘涓虹┖', trigger: 'blur' }]
+  }
+});
+const { queryParams, form, rules } = toRefs(data);
+
+onMounted(() => {
+  getList();
+  getTreeselect();
+});
+
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+const handleNodeClick = (data: ModelForm) => {
+  queryParams.value.categoryCode = data.categoryCode;
+  if (data.categoryCode === 'ALL') {
+    queryParams.value.categoryCode = '';
+  }
+  handleQuery();
+};
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.categoryName.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watchEffect(
+  () => {
+    categoryTreeRef.value?.filter(categoryName.value);
+  },
+  {
+    flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛�
+  }
+);
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.categoryCode = '';
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: ModelVO[]) => {
+  ids.value = selection.map((item: ModelVO) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+//鍒嗛〉
+const getList = async () => {
+  loading.value = true;
+  const resp = await listModel(queryParams.value);
+  modelList.value = resp.rows;
+  total.value = resp.total;
+  loading.value = false;
+};
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row?: ModelVO) => {
+  const id = row?.id || ids.value;
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎妯″瀷id涓恒��' + id + '銆戠殑鏁版嵁椤癸紵');
+  loading.value = true;
+  await delModel(id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+};
+// 娴佺▼閮ㄧ讲
+const clickDeploy = async (id: string, key: string) => {
+  await proxy?.$modal.confirm('鏄惁閮ㄧ讲妯″瀷key涓恒��' + key + '銆戞祦绋嬶紵');
+  loading.value = true;
+  await modelDeploy(id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('閮ㄧ讲鎴愬姛');
+};
+const handleAdd = () => {
+  ids.value = [];
+  getTreeselect();
+  form.value = { ...initFormData };
+  dialog.visible = true;
+  dialog.title = '鏂板妯″瀷';
+};
+const handleUpdate = () => {
+  dialog.title = '淇敼妯″瀷';
+  nextTick(async () => {
+    await getTreeselect();
+    const _id = ids.value[0];
+    const res = await getInfo(_id);
+    Object.assign(form.value, res.data);
+    dialog.visible = true;
+  });
+};
+
+/** 鎻愪氦鎸夐挳 */
+const submitForm = () => {
+  formRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (ids.value && ids.value.length > 0) {
+        form.value.id = ids.value[0];
+        await update(form.value);
+        proxy?.$modal.msgSuccess('鏂板鎴愬姛');
+      } else {
+        initXml(form.value.key, form.value.name);
+        form.value.xml = xml.value;
+        await addModel(form.value);
+        proxy?.$modal.msgSuccess('鏂板鎴愬姛');
+      }
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 鍙栨秷鎸夐挳 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 琛ㄥ崟閲嶇疆 */
+const reset = () => {
+  form.value = { ...initFormData };
+  formRef.value.resetFields();
+};
+
+// 鎵撳紑璁捐娴佺▼
+const clickDesign = async (id: string) => {
+  await designRef.value.open(id);
+};
+// 瀵煎嚭娴佺▼妯″瀷
+const clickExportZip = (data: any) => {
+  proxy?.$download.zip('/workflow/model/export/zip/' + data.id, data.name + '-' + data.key);
+};
+/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */
+const getTreeselect = async () => {
+  const res = await listCategory();
+  categoryOptions.value = [];
+  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] };
+  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
+  categoryOptions.value.push(data);
+};
+
+const xml = ref<string>('');
+
+const initXml = async (key: string, name: string) => {
+  xml.value = `<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef">
+  <process id="${key}" name="${name}">
+    <startEvent id="startNode1" name="寮�濮�" />
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_flow">
+    <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c">
+      <bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke="">
+        <omgdc:Bounds x="240" y="200" width="30" height="30" />
+        <bpmndi:BPMNLabel>
+          <omgdc:Bounds x="242" y="237" width="23" height="14" />
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNShape>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>`;
+  return xml;
+};
+</script>
diff --git a/src/views/workflow/processDefinition/components/processPreview.vue b/src/views/workflow/processDefinition/components/processPreview.vue
new file mode 100644
index 0000000..817552a
--- /dev/null
+++ b/src/views/workflow/processDefinition/components/processPreview.vue
@@ -0,0 +1,57 @@
+<template>
+  <el-dialog v-model="data.visible" title="棰勮" width="70%" append-to-body>
+    <div v-if="data.type === 'png'" style="align: center">
+      <el-image v-if="data.type === 'png'" :src="data.url[0]">
+        <div>娴佺▼鍥惧姞杞戒腑 <i class="el-icon-loading"></i></div>
+      </el-image>
+    </div>
+    <div v-if="data.type === 'xml'" class="xml-data">
+      <div v-for="(xml, index) in data.url" :key="index">
+        <pre class="font">{{ xml }}</pre>
+      </div>
+    </div>
+    <template #footer>
+      <span v-if="data.type === 'xml'" class="dialog-footer"> </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+const data = reactive({
+  visible: false,
+  url: new Array<string>(),
+  type: ''
+});
+//鎵撳紑
+const openDialog = (url: string[], type: string) => {
+  data.visible = true;
+  data.url = url;
+  data.type = type;
+};
+/**
+ * 瀵瑰鏆撮湶瀛愮粍浠舵柟娉�
+ */
+defineExpose({
+  openDialog
+});
+</script>
+<style>
+.xml-data {
+  background-color: #2b2b2b;
+  border-radius: 5px;
+  color: #c6c6c6;
+  word-break: break-all;
+  overflow-y: scroll;
+  overflow-x: hidden;
+  box-sizing: border-box;
+  padding: 8px 0px;
+  height: 500px;
+  width: inherit;
+  line-height: 1px;
+  overflow: auto;
+}
+.font {
+  font-family: '骞煎渾';
+  font-weight: 500;
+}
+</style>
diff --git a/src/views/workflow/processDefinition/index.vue b/src/views/workflow/processDefinition/index.vue
new file mode 100644
index 0000000..3843573
--- /dev/null
+++ b/src/views/workflow/processDefinition/index.vue
@@ -0,0 +1,403 @@
+<template>
+  <div class="p-2">
+    <el-row :gutter="20">
+      <!-- 娴佺▼鍒嗙被鏍� -->
+      <el-col :lg="4" :xs="24" style="">
+        <el-card shadow="hover">
+          <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable />
+          <el-tree
+            ref="categoryTreeRef"
+            class="mt-2"
+            node-key="id"
+            :data="categoryOptions"
+            :props="{ label: 'categoryName', children: 'children' }"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            highlight-current
+            default-expand-all
+            @node-click="handleNodeClick"
+          ></el-tree>
+        </el-card>
+      </el-col>
+      <el-col :lg="20" :xs="24">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+          <div v-show="showSearch" class="mb-[10px]">
+            <el-card shadow="hover">
+              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+                <el-form-item label="娴佺▼瀹氫箟鍚嶇О" prop="name">
+                  <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="娴佺▼瀹氫箟KEY" prop="key">
+                  <el-input v-model="queryParams.key" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+              </el-form>
+            </el-card>
+          </div>
+        </transition>
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+          <div v-show="showSearch" class="mb-[10px]">
+            <el-card shadow="hover">
+              <el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">閮ㄧ讲娴佺▼鏂囦欢</el-button>
+            </el-card>
+          </div>
+        </transition>
+        <el-card shadow="hover">
+          <template #header>
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5"> </el-col>
+              <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
+            </el-row>
+          </template>
+
+          <el-table v-loading="loading" :data="processDefinitionList" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+            <el-table-column fixed align="center" prop="name" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+            <el-table-column align="center" prop="key" label="鏍囪瘑Key"></el-table-column>
+            <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90">
+              <template #default="scope"> v{{ scope.row.version }}.0</template>
+            </el-table-column>
+            <el-table-column align="center" prop="resourceName" label="娴佺▼XML" min-width="80" :show-overflow-tooltip="true">
+              <template #default="scope">
+                <el-link type="primary" @click="clickPreviewXML(scope.row.id)">{{ scope.row.resourceName }}</el-link>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="diagramResourceName" label="娴佺▼鍥剧墖" min-width="80" :show-overflow-tooltip="true">
+              <template #default="scope">
+                <el-link type="primary" @click="clickPreviewImg(scope.row.id)">{{ scope.row.diagramResourceName }}</el-link>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="suspensionState" label="鐘舵��" min-width="70">
+              <template #default="scope">
+                <el-tag v-if="scope.row.suspensionState == 1" type="success">婵�娲�</el-tag>
+                <el-tag v-else type="danger">鎸傝捣</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="deploymentTime" label="閮ㄧ讲鏃堕棿" :show-overflow-tooltip="true"></el-table-column>
+            <el-table-column fixed="right" label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button
+                      link
+                      type="primary"
+                      size="small"
+                      :icon="scope.row.suspensionState === 1 ? 'Lock' : 'Unlock'"
+                      @click="handleProcessDefState(scope.row)"
+                    >
+                      {{ scope.row.suspensionState === 1 ? '鎸傝捣娴佺▼' : '婵�娲绘祦绋�' }}
+                    </el-button>
+                  </el-col>
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Sort" @click="handleConvertToModel(scope.row)"> 杞崲妯″瀷 </el-button>
+                  </el-col>
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Document" @click="getProcessDefinitionHitoryList(scope.row.id, scope.row.key)">
+                      鍘嗗彶鐗堟湰
+                    </el-button>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+          />
+        </el-card>
+      </el-col>
+    </el-row>
+    <!-- 棰勮鍥剧墖鎴杧ml -->
+    <process-preview ref="previewRef" />
+
+    <!-- 閮ㄧ讲鏂囦欢 -->
+    <el-dialog v-if="uploadDialog.visible" v-model="uploadDialog.visible" :title="uploadDialog.title" width="30%">
+      <div v-loading="uploadDialogLoading">
+        <el-upload class="upload-demo" drag accept="application/zip,application/xml,.bpmn" :http-request="handerDeployProcessFile">
+          <el-icon class="UploadFilled"><upload-filled /></el-icon>
+          <div class="el-upload__text"><em>鐐瑰嚮涓婁紶锛岄�夋嫨BPMN娴佺▼鏂囦欢</em></div>
+          <div class="el-upload__text">浠呮敮鎸� .zip銆�.bpmn20.xml銆乥pmn 鏍煎紡鏂囦欢</div>
+          <div class="el-upload__text">PS:濡傝嫢閮ㄧ讲璇烽儴缃蹭粠鏈」鐩ā鍨嬬鐞嗗鍑虹殑鏁版嵁</div>
+      </el-upload>
+      </div>
+    </el-dialog>
+
+    <!-- 鍘嗗彶鐗堟湰 -->
+    <el-dialog v-if="processDefinitionDialog.visible" v-model="processDefinitionDialog.visible" :title="processDefinitionDialog.title" width="70%">
+      <el-table v-loading="loading" :data="processDefinitionHistoryList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+        <el-table-column fixed align="center" prop="name" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+        <el-table-column align="center" prop="key" label="鏍囪瘑Key"></el-table-column>
+        <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90">
+          <template #default="scope"> v{{ scope.row.version }}.0</template>
+        </el-table-column>
+        <el-table-column align="center" prop="resourceName" label="娴佺▼XML" min-width="80" :show-overflow-tooltip="true">
+          <template #default="scope">
+            <el-link type="primary" @click="clickPreviewXML(scope.row.id)">{{ scope.row.resourceName }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="diagramResourceName" label="娴佺▼鍥剧墖" min-width="80" :show-overflow-tooltip="true">
+          <template #default="scope">
+            <el-link type="primary" @click="clickPreviewImg(scope.row.id)">{{ scope.row.diagramResourceName }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="suspensionState" label="鐘舵��" min-width="70">
+          <template #default="scope">
+            <el-tag v-if="scope.row.suspensionState == 1" type="success">婵�娲�</el-tag>
+            <el-tag v-else type="danger">鎸傝捣</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="deploymentTime" label="閮ㄧ讲鏃堕棿" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column fixed="right" label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button
+                  link
+                  type="primary"
+                  size="small"
+                  :icon="scope.row.suspensionState === 1 ? 'Lock' : 'Unlock'"
+                  @click="handleProcessDefState(scope.row)"
+                >
+                  {{ scope.row.suspensionState === 1 ? '鎸傝捣娴佺▼' : '婵�娲绘祦绋�' }}
+                </el-button>
+              </el-col>
+              <el-col :span="1.5">
+                <el-button link type="primary" icon="Delete" size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+              </el-col>
+            </el-row>
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button link type="primary" icon="Sort" size="small" @click="handleConvertToModel(scope.row)"> 杞崲妯″瀷 </el-button>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="processDefinition">
+import {
+  listProcessDefinition,
+  processDefinitionImage,
+  processDefinitionXml,
+  deleteProcessDefinition,
+  updateProcessDefState,
+  convertToModel,
+  deployProcessFile,
+  getProcessDefinitionListByKey
+} from '@/api/workflow/processDefinition';
+import ProcessPreview from './components/processPreview.vue';
+import { listCategory } from '@/api/workflow/category';
+import { CategoryVO } from '@/api/workflow/category/types';
+import { ProcessDefinitionQuery, ProcessDefinitionVO } from '@/api/workflow/processDefinition/types';
+import { UploadRequestOptions } from 'element-plus';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const previewRef = ref<InstanceType<typeof ProcessPreview>>();
+const queryFormRef = ref<ElFormInstance>();
+const categoryTreeRef = ref<ElTreeInstance>();
+
+type CategoryOption = {
+  categoryCode: string;
+  categoryName: string;
+  children?: CategoryOption[];
+};
+
+const loading = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+const uploadDialogLoading = ref(false);
+const processDefinitionList = ref<ProcessDefinitionVO[]>([]);
+const processDefinitionHistoryList = ref<ProcessDefinitionVO[]>([]);
+const url = ref<string[]>([]);
+const categoryOptions = ref<CategoryOption[]>([]);
+const categoryName = ref('');
+
+const uploadDialog = reactive<DialogOption>({
+  visible: false,
+  title: '閮ㄧ讲娴佺▼鏂囦欢'
+});
+
+const processDefinitionDialog = reactive<DialogOption>({
+  visible: false,
+  title: '鍘嗗彶鐗堟湰'
+});
+
+// 鏌ヨ鍙傛暟
+const queryParams = ref<ProcessDefinitionQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  key: undefined,
+  categoryCode: undefined
+});
+
+onMounted(() => {
+  getList();
+  getTreeselect();
+});
+
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+const handleNodeClick = (data: CategoryVO) => {
+  queryParams.value.categoryCode = data.categoryCode;
+  if (data.categoryCode === 'ALL') {
+    queryParams.value.categoryCode = '';
+  }
+  handleQuery();
+};
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.categoryName.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watchEffect(
+  () => {
+    categoryTreeRef.value.filter(categoryName.value);
+  },
+  {
+    flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛�
+  }
+);
+
+/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */
+const getTreeselect = async () => {
+  const res = await listCategory();
+  categoryOptions.value = [];
+  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] };
+  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
+  categoryOptions.value.push(data);
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.categoryCode = '';
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: any) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+//鍒嗛〉
+const getList = async () => {
+  loading.value = true;
+  const resp = await listProcessDefinition(queryParams.value);
+  processDefinitionList.value = resp.rows;
+  total.value = resp.total;
+  loading.value = false;
+};
+//鑾峰彇鍘嗗彶娴佺▼瀹氫箟
+const getProcessDefinitionHitoryList = async (id: string, key: string) => {
+  processDefinitionDialog.visible = true;
+  loading.value = true;
+  const resp = await getProcessDefinitionListByKey(key);
+  if (resp.data && resp.data.length > 0) {
+    processDefinitionHistoryList.value = resp.data.filter((item: any) => item.id !== id);
+  }
+  loading.value = false;
+};
+
+//棰勮鍥剧墖
+const clickPreviewImg = async (id: string) => {
+  loading.value = true;
+  const resp = await processDefinitionImage(id);
+  if (previewRef.value) {
+    url.value = [];
+    url.value.push('data:image/png;base64,' + resp.data);
+    loading.value = false;
+    previewRef.value.openDialog(url.value, 'png');
+  }
+};
+//棰勮xml
+const clickPreviewXML = async (id: string) => {
+  loading.value = true;
+  const resp = await processDefinitionXml(id);
+  if (previewRef.value) {
+    url.value = [];
+    url.value = resp.data.xml;
+    loading.value = false;
+    previewRef.value.openDialog(url.value, 'xml');
+  }
+};
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row: ProcessDefinitionVO) => {
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎娴佺▼瀹氫箟key涓恒��' + row.key + '銆戠殑鏁版嵁椤癸紵');
+  loading.value = true;
+  await deleteProcessDefinition(row.deploymentId, row.id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+};
+/** 鎸傝捣/婵�娲� */
+const handleProcessDefState = async (row: ProcessDefinitionVO) => {
+  let msg: string;
+  if (row.suspensionState === 1) {
+    msg = `鏆傚仠鍚庯紝姝ゆ祦绋嬩笅鐨勬墍鏈変换鍔¢兘涓嶅厑璁稿線鍚庢祦杞紝鎮ㄧ‘瀹氭寕璧枫��${row.name || row.key}銆戝悧锛焋;
+  } else {
+    msg = `鍚姩鍚庯紝姝ゆ祦绋嬩笅鐨勬墍鏈変换鍔¢兘鍏佽寰�鍚庢祦杞紝鎮ㄧ‘瀹氭縺娲汇��${row.name || row.key}銆戝悧锛焋;
+  }
+  await proxy?.$modal.confirm(msg);
+  loading.value = true;
+  await updateProcessDefState(row.id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+};
+/** 娴佺▼瀹氫箟杞崲涓烘ā鍨� */
+const handleConvertToModel = async (row: ProcessDefinitionVO) => {
+  await proxy?.$modal.confirm('鏄惁纭杞崲娴佺▼瀹氫箟key涓恒��' + row.key + '銆戠殑鏁版嵁椤癸紵');
+  await convertToModel(row.id).finally(() => (loading.value = false));
+  getList();
+  proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+};
+
+//閮ㄧ讲鏂囦欢
+const handerDeployProcessFile = (data: UploadRequestOptions): XMLHttpRequest => {
+  let formData = new FormData();
+  if (queryParams.value.categoryCode === 'ALL') {
+    proxy?.$modal.msgError('椤剁骇鑺傜偣涓嶅彲浣滀负鍒嗙被锛�');
+    return;
+  }
+  if (!queryParams.value.categoryCode) {
+    proxy?.$modal.msgError('璇烽�夋嫨宸︿晶瑕佷笂浼犵殑鍒嗙被锛�');
+    return;
+  }
+  uploadDialogLoading.value = true
+  formData.append('file', data.file);
+  formData.append('categoryCode', queryParams.value.categoryCode);
+  deployProcessFile(formData).then(() => {
+    uploadDialog.visible = false;
+    proxy?.$modal.msgSuccess('閮ㄧ讲鎴愬姛');
+    uploadDialogLoading.value = false
+    handleQuery();
+  });
+};
+</script>
diff --git a/src/views/workflow/processInstance/index.vue b/src/views/workflow/processInstance/index.vue
new file mode 100644
index 0000000..9830366
--- /dev/null
+++ b/src/views/workflow/processInstance/index.vue
@@ -0,0 +1,363 @@
+<template>
+  <div class="p-2">
+    <el-row :gutter="20">
+      <!-- 娴佺▼鍒嗙被鏍� -->
+      <el-col :lg="4" :xs="24" style="">
+        <el-card shadow="hover">
+          <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable />
+          <el-tree
+            ref="categoryTreeRef"
+            class="mt-2"
+            node-key="id"
+            :data="categoryOptions"
+            :props="{ label: 'categoryName', children: 'children' }"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            highlight-current
+            default-expand-all
+            @node-click="handleNodeClick"
+          ></el-tree>
+        </el-card>
+      </el-col>
+      <el-col :lg="20" :xs="24">
+        <div class="mb-[10px]">
+          <el-card shadow="hover" class="text-center">
+            <el-radio-group v-model="tab" @change="changeTab(tab)">
+              <el-radio-button label="running">杩愯涓�</el-radio-button>
+              <el-radio-button label="finish">宸插畬鎴�</el-radio-button>
+            </el-radio-group>
+          </el-card>
+        </div>
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+          <div v-show="showSearch" class="mb-[10px]">
+            <el-card shadow="hover">
+              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+                <el-form-item label="娴佺▼瀹氫箟鍚嶇О" prop="name">
+                  <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="娴佺▼瀹氫箟KEY" prop="key">
+                  <el-input v-model="queryParams.key" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+              </el-form>
+            </el-card>
+          </div>
+        </transition>
+        <el-card shadow="hover">
+          <template #header>
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">鍒犻櫎</el-button>
+              </el-col>
+              <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
+            </el-row>
+          </template>
+
+          <el-table v-loading="loading" :data="processInstanceList" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+            <el-table-column v-if="false" fixed align="center" prop="id" label="id"></el-table-column>
+            <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+            <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column>
+            <el-table-column align="center" prop="processDefinitionVersion" label="鐗堟湰鍙�" width="90">
+              <template #default="scope"> v{{ scope.row.processDefinitionVersion }}.0</template>
+            </el-table-column>
+            <el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="鐘舵��" min-width="70">
+              <template #default="scope">
+                <el-tag v-if="!scope.row.isSuspended" type="success">婵�娲�</el-tag>
+                <el-tag v-else type="danger">鎸傝捣</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70">
+              <template #default="scope">
+                <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="startTime" label="鍚姩鏃堕棿" width="160"></el-table-column>
+            <el-table-column v-if="tab === 'finish'" align="center" prop="endTime" label="缁撴潫鏃堕棿" width="160"></el-table-column>
+            <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button>
+                  </el-col>
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+                  </el-col>
+                </el-row>
+                <el-row v-if="tab === 'running'" :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button
+                      link
+                      type="primary"
+                      size="small"
+                      icon="Sort"
+                      @click="getProcessDefinitionHitoryList(scope.row.processDefinitionId, scope.row.processDefinitionKey)"
+                      >鍒囨崲鐗堟湰</el-button
+                    >
+                  </el-col>
+                  <el-col :span="1.5">
+                    <el-popover :ref="`popoverRef${scope.$index}`" trigger="click" placement="left" :width="300">
+                      <el-input v-model="deleteReason" resize="none" :rows="3" type="textarea" placeholder="璇疯緭鍏ヤ綔搴熷師鍥�" />
+                      <div style="text-align: right; margin: 5px 0px 0px 0px">
+                        <el-button size="small" text @click="cancelPopover(scope.$index)">鍙栨秷</el-button>
+                        <el-button size="small" type="primary" @click="handleInvalid(scope.row)">纭</el-button>
+                      </div>
+                      <template #reference>
+                        <el-button link type="primary" size="small" icon="CircleClose">浣滃簾</el-button>
+                      </template>
+                    </el-popover>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="handleQuery"
+          />
+        </el-card>
+      </el-col>
+    </el-row>
+    <el-dialog v-if="processDefinitionDialog.visible" v-model="processDefinitionDialog.visible" :title="processDefinitionDialog.title" width="70%">
+      <el-table v-loading="loading" :data="processDefinitionHistoryList">
+        <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+        <el-table-column fixed align="center" prop="name" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+        <el-table-column align="center" prop="key" label="鏍囪瘑Key"></el-table-column>
+        <el-table-column align="center" prop="version" label="鐗堟湰鍙�" width="90">
+          <template #default="scope"> v{{ scope.row.version }}.0</template>
+        </el-table-column>
+        <el-table-column align="center" prop="suspensionState" label="鐘舵��" min-width="70">
+          <template #default="scope">
+            <el-tag v-if="scope.row.suspensionState == 1" type="success">婵�娲�</el-tag>
+            <el-tag v-else type="danger">鎸傝捣</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="deploymentTime" label="閮ㄧ讲鏃堕棿" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column fixed="right" label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" size="small" icon="Sort" @click="handleChange(scope.row.id)">鍒囨崲</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  getProcessInstanceRunningByPage,
+  getProcessInstanceFinishByPage,
+  deleteRuntimeProcessAndHisInst,
+  deleteFinishProcessAndHisInst,
+  deleteRuntimeProcessInst
+} from '@/api/workflow/processInstance';
+import { getProcessDefinitionListByKey, migrationProcessDefinition } from '@/api/workflow/processDefinition';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import { listCategory } from '@/api/workflow/category';
+import { CategoryVO } from '@/api/workflow/category/types';
+import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types';
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const queryFormRef = ref<ElFormInstance>();
+const categoryTreeRef = ref<ElTreeInstance>();
+
+// 閬僵灞�
+const loading = ref(true);
+// 閫変腑鏁扮粍
+const ids = ref<Array<any>>([]);
+// 闈炲崟涓鐢�
+const single = ref(true);
+// 闈炲涓鐢�
+const multiple = ref(true);
+// 鏄剧ず鎼滅储鏉′欢
+const showSearch = ref(true);
+// 鎬绘潯鏁�
+const total = ref(0);
+// 娴佺▼瀹氫箟id
+const processDefinitionId = ref<string>('');
+// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁
+const processInstanceList = ref<ProcessInstanceVO[]>([]);
+const processDefinitionHistoryList = ref<Array<any>>([]);
+const categoryOptions = ref<CategoryOption[]>([]);
+const categoryName = ref('');
+
+const processDefinitionDialog = reactive<DialogOption>({
+  visible: false,
+  title: '娴佺▼瀹氫箟'
+});
+
+type CategoryOption = {
+  categoryCode: string;
+  categoryName: string;
+  children?: CategoryOption[];
+};
+
+const tab = ref('running');
+// 浣滃簾鍘熷洜
+const deleteReason = ref('');
+// 鏌ヨ鍙傛暟
+const queryParams = ref<ProcessInstanceQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  key: undefined,
+  categoryCode: undefined
+});
+
+onMounted(() => {
+  getProcessInstanceRunningList();
+  getTreeselect();
+});
+
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+const handleNodeClick = (data: CategoryVO) => {
+  queryParams.value.categoryCode = data.categoryCode;
+  if (data.categoryCode === 'ALL') {
+    queryParams.value.categoryCode = '';
+  }
+  handleQuery();
+};
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.categoryName.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watchEffect(
+  () => {
+    categoryTreeRef.value.filter(categoryName.value);
+  },
+  {
+    flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛�
+  }
+);
+
+/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */
+const getTreeselect = async () => {
+  const res = await listCategory();
+  categoryOptions.value = [];
+  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] };
+  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
+  categoryOptions.value.push(data);
+};
+
+//瀹℃壒璁板綍
+const handleApprovalRecord = (row: any) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(row.id);
+  }
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  if ('running' === tab.value) {
+    getProcessInstanceRunningList();
+  } else {
+    getProcessInstanceFinishList();
+  }
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.categoryCode = '';
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: ProcessInstanceVO[]) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+//鍒嗛〉
+const getProcessInstanceRunningList = () => {
+  loading.value = true;
+  getProcessInstanceRunningByPage(queryParams.value).then((resp) => {
+    processInstanceList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+//鍒嗛〉
+const getProcessInstanceFinishList = () => {
+  loading.value = true;
+  getProcessInstanceFinishByPage(queryParams.value).then((resp) => {
+    processInstanceList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row: any) => {
+  const id = row.id || ids.value;
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎id涓恒��' + id + '銆戠殑鏁版嵁椤癸紵');
+  loading.value = true;
+  if ('running' === tab.value) {
+    await deleteRuntimeProcessAndHisInst(id).finally(() => (loading.value = false));
+    getProcessInstanceRunningList();
+  } else {
+    await deleteFinishProcessAndHisInst(id).finally(() => (loading.value = false));
+    getProcessInstanceFinishList();
+  }
+  proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+};
+const changeTab = async (data: string) => {
+  queryParams.value.pageNum = 1;
+  if ('running' === data) {
+    getProcessInstanceRunningList();
+  } else {
+    getProcessInstanceFinishList();
+  }
+};
+/** 浣滃簾鎸夐挳鎿嶄綔 */
+const handleInvalid = async (row: ProcessInstanceVO) => {
+  await proxy?.$modal.confirm('鏄惁纭浣滃簾涓氬姟id涓恒��' + row.businessKey + '銆戠殑鏁版嵁椤癸紵');
+  loading.value = true;
+  if ('running' === tab.value) {
+    let param = {
+      processInstanceId: row.id,
+      deleteReason: deleteReason.value
+    };
+    await deleteRuntimeProcessInst(param).finally(() => (loading.value = false));
+    getProcessInstanceRunningList();
+    proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+  }
+};
+const cancelPopover = async (index: any) => {
+  (proxy?.$refs[`popoverRef${index}`] as any).hide(); //鍏抽棴寮圭獥
+};
+//鑾峰彇娴佺▼瀹氫箟
+const getProcessDefinitionHitoryList = (id: string, key: string) => {
+  processDefinitionDialog.visible = true;
+  processDefinitionId.value = id;
+  loading.value = true;
+  getProcessDefinitionListByKey(key).then((resp) => {
+    if (resp.data && resp.data.length > 0) {
+      processDefinitionHistoryList.value = resp.data.filter((item: any) => item.id !== id);
+    }
+    loading.value = false;
+  });
+};
+//鍒囨崲娴佺▼鐗堟湰
+const handleChange = async (id: string) => {
+  await proxy?.$modal.confirm('鏄惁纭鍒囨崲锛�');
+  loading.value = true;
+  migrationProcessDefinition(processDefinitionId.value, id).then((resp) => {
+    proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+    getProcessInstanceRunningList();
+    processDefinitionDialog.visible = false;
+    loading.value = false;
+  });
+};
+</script>
diff --git a/src/views/workflow/task/allTaskWaiting.vue b/src/views/workflow/task/allTaskWaiting.vue
new file mode 100644
index 0000000..ef1c45e
--- /dev/null
+++ b/src/views/workflow/task/allTaskWaiting.vue
@@ -0,0 +1,237 @@
+<template>
+  <div class="p-2">
+    <div class="mb-[10px]">
+      <el-card shadow="hover" class="text-center">
+        <el-radio-group v-model="tab" @change="changeTab(tab)">
+          <el-radio-button label="waiting">寰呭姙浠诲姟</el-radio-button>
+          <el-radio-button label="finish">宸插姙浠诲姟</el-radio-button>
+        </el-radio-group>
+      </el-card>
+    </div>
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="浠诲姟鍚嶇О" prop="name">
+              <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName">
+              <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey">
+              <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Edit" @click="handleUpdate">淇敼鍔炵悊浜�</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column>
+        <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�">
+          <template v-if="tab === 'waiting'" #default="scope">
+            <template v-if="scope.row.participantVo && scope.row.assignee === null">
+              <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success">
+                {{ item }}
+              </el-tag>
+            </template>
+            <template v-else>
+              <el-tag type="success">
+                {{ scope.row.assigneeName }}
+              </el-tag>
+            </template>
+          </template>
+          <template v-else-if="tab === 'finish'" #default="scope">
+            <el-tag type="success">
+              {{ scope.row.assigneeName }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70">
+          <template #default="scope">
+            <el-tag v-if="tab === 'waiting'" type="success">{{ scope.row.businessStatusName }}</el-tag>
+            <el-tag v-else type="success">宸插畬鎴�</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column>
+        <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button>
+              </el-col>
+              <el-col v-if="scope.row.multiInstance" :span="1.5">
+                <el-button link type="primary" size="small" icon="CirclePlus" @click="addMultiInstanceUser(scope.row)">鍔犵</el-button>
+              </el-col>
+              <el-col v-if="scope.row.multiInstance" :span="1.5">
+                <el-button link type="primary" size="small" icon="Remove" @click="deleteMultiInstanceUser(scope.row)">鍑忕</el-button>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="handleQuery"
+      />
+    </el-card>
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+    <!-- 鎻愪氦缁勪欢 -->
+    <submitVerify ref="submitVerifyRef" :task-id="taskId" @submit-callback="handleQuery" />
+    <!-- 鍔犵缁勪欢 -->
+    <multiInstanceUser ref="multiInstanceUserRef" :title="title" @submit-callback="handleQuery" />
+    <!-- 鍔犵缁勪欢 -->
+    <SysUser ref="sysUserRef" :multiple="true" @submit-callback="submitCallback" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getAllTaskWaitByPage, getAllTaskFinishByPage, updateAssignee } from '@/api/workflow/task';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import SubmitVerify from '@/components/Process/submitVerify.vue';
+import MultiInstanceUser from '@/components/Process/multiInstance-user.vue';
+import SysUser from '@/components/Process/sys-user.vue';
+import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
+//鎻愪氦缁勪欢
+const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+//鍔犵缁勪欢
+const multiInstanceUserRef = ref<InstanceType<typeof MultiInstanceUser>>();
+//閫変汉缁勪欢
+const sysUserRef = ref<InstanceType<typeof SysUser>>();
+
+const queryFormRef = ref<ElFormInstance>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+// 閬僵灞�
+const loading = ref(true);
+// 閫変腑鏁扮粍
+const ids = ref<Array<any>>([]);
+// 闈炲崟涓鐢�
+const single = ref(true);
+// 闈炲涓鐢�
+const multiple = ref(true);
+// 鏄剧ず鎼滅储鏉′欢
+const showSearch = ref(true);
+// 鎬绘潯鏁�
+const total = ref(0);
+// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁
+const taskList = ref([]);
+// 浠诲姟id
+const taskId = ref('');
+const title = ref('');
+// 鏌ヨ鍙傛暟
+const queryParams = ref<TaskQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  processDefinitionName: undefined,
+  processDefinitionKey: undefined
+});
+const tab = ref('waiting');
+onMounted(() => {
+  getWaitingList();
+});
+//瀹℃壒璁板綍
+const handleApprovalRecord = (row: TaskVO) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(row.processInstanceId);
+  }
+};
+//鍔犵
+const addMultiInstanceUser = (row: TaskVO) => {
+  if (multiInstanceUserRef.value) {
+    title.value = '鍔犵浜哄憳';
+    multiInstanceUserRef.value.getAddMultiInstanceList(row.id, []);
+  }
+};
+//鍑忕
+const deleteMultiInstanceUser = (row: TaskVO) => {
+  if (multiInstanceUserRef.value) {
+    title.value = '鍑忕浜哄憳';
+    multiInstanceUserRef.value.getDeleteMultiInstanceList(row.id);
+  }
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  if ('waiting' === tab.value) {
+    getWaitingList();
+  } else {
+    getFinishList();
+  }
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: any) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+const changeTab = async (data: string) => {
+  queryParams.value.pageNum = 1;
+  if ('waiting' === data) {
+    getWaitingList();
+  } else {
+    getFinishList();
+  }
+};
+//鍒嗛〉
+const getWaitingList = () => {
+  loading.value = true;
+  getAllTaskWaitByPage(queryParams.value).then((resp) => {
+    taskList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+const getFinishList = () => {
+  loading.value = true;
+  getAllTaskFinishByPage(queryParams.value).then((resp) => {
+    taskList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+const handleUpdate = () => {
+  if (sysUserRef.value) {
+    sysUserRef.value.getUserList([]);
+  }
+};
+//淇敼鍔炵悊浜�
+const submitCallback = (data) => {
+  if (data && data.value.length > 0) {
+    updateAssignee(ids.value, data.value[0].userId).then((resp) => {
+      sysUserRef.value.close();
+      proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+      handleQuery();
+    });
+  }
+};
+</script>
diff --git a/src/views/workflow/task/myDocument.vue b/src/views/workflow/task/myDocument.vue
new file mode 100644
index 0000000..1e1d2a0
--- /dev/null
+++ b/src/views/workflow/task/myDocument.vue
@@ -0,0 +1,256 @@
+<template>
+  <div class="p-2">
+    <el-row :gutter="20">
+      <!-- 娴佺▼鍒嗙被鏍� -->
+      <el-col :lg="4" :xs="24" style="">
+        <el-card shadow="hover">
+          <el-input v-model="categoryName" placeholder="璇疯緭鍏ユ祦绋嬪垎绫诲悕" prefix-icon="Search" clearable />
+          <el-tree
+            ref="categoryTreeRef"
+            class="mt-2"
+            node-key="id"
+            :data="categoryOptions"
+            :props="{ label: 'categoryName', children: 'children' }"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            highlight-current
+            default-expand-all
+            @node-click="handleNodeClick"
+          ></el-tree>
+        </el-card>
+      </el-col>
+      <el-col :lg="20" :xs="24">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+          <div v-show="showSearch" class="mb-[10px]">
+            <el-card shadow="hover">
+              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+                <el-form-item label="娴佺▼瀹氫箟鍚嶇О" prop="name">
+                  <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+              </el-form>
+            </el-card>
+          </div>
+        </transition>
+        <el-card shadow="hover">
+          <template #header>
+            <el-row :gutter="10" class="mb8">
+              <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
+            </el-row>
+          </template>
+
+          <el-table v-loading="loading" :data="processInstanceList" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+            <el-table-column v-if="false" fixed align="center" prop="id" label="id"></el-table-column>
+            <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+            <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column>
+            <el-table-column align="center" prop="processDefinitionVersion" label="鐗堟湰鍙�" width="90">
+              <template #default="scope"> v{{ scope.row.processDefinitionVersion }}.0</template>
+            </el-table-column>
+            <el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="鐘舵��" min-width="70">
+              <template #default="scope">
+                <el-tag v-if="!scope.row.isSuspended" type="success">婵�娲�</el-tag>
+                <el-tag v-else type="danger">鎸傝捣</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70">
+              <template #default="scope">
+                <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" prop="startTime" label="鍚姩鏃堕棿" width="160"></el-table-column>
+            <el-table-column v-if="tab === 'finish'" align="center" prop="endTime" label="缁撴潫鏃堕棿" width="160"></el-table-column>
+            <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="1.5">
+                    <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row.id)">瀹℃壒璁板綍</el-button>
+                  </el-col>
+                  <el-col
+                    v-if="scope.row.businessStatus === 'draft' || scope.row.businessStatus === 'cancel' || scope.row.businessStatus === 'back'"
+                    :span="1.5"
+                  >
+                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+                  </el-col>
+                  <el-col v-if="scope.row.businessStatus === 'waiting'" :span="1.5">
+                    <el-button link type="primary" size="small" icon="Notification" @click="handleCancelProcessApply(scope.row.id)">鎾ら攢</el-button>
+                  </el-col>
+                  <el-col
+                    v-if="scope.row.businessStatus === 'draft' || scope.row.businessStatus === 'cancel' || scope.row.businessStatus === 'back'"
+                    :span="1.5"
+                  >
+                    <el-button link type="primary" size="small" icon="Edit" @click="submitVerifyOpen(scope.row.taskVoList[0].id)">鎻愪氦</el-button>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+          />
+        </el-card>
+      </el-col>
+    </el-row>
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+    <!-- 鎻愪氦缁勪欢 -->
+    <submitVerify ref="submitVerifyRef" @submit-callback="getList" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getCurrentSubmitByPage, deleteRuntimeProcessAndHisInst, cancelProcessApply } from '@/api/workflow/processInstance';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import SubmitVerify from '@/components/Process/submitVerify.vue';
+import { listCategory } from '@/api/workflow/category';
+import { CategoryVO } from '@/api/workflow/category/types';
+import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types';
+//鎻愪氦缁勪欢
+const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const queryFormRef = ref<ElFormInstance>();
+const categoryTreeRef = ref<ElTreeInstance>();
+
+// 閬僵灞�
+const loading = ref(true);
+// 閫変腑鏁扮粍
+const ids = ref<Array<any>>([]);
+// 闈炲崟涓鐢�
+const single = ref(true);
+// 闈炲涓鐢�
+const multiple = ref(true);
+// 鏄剧ず鎼滅储鏉′欢
+const showSearch = ref(true);
+// 鎬绘潯鏁�
+const total = ref(0);
+// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁
+const processInstanceList = ref<ProcessInstanceVO[]>([]);
+
+const categoryOptions = ref<CategoryOption[]>([]);
+const categoryName = ref('');
+
+interface CategoryOption {
+  categoryCode: string;
+  categoryName: string;
+  children?: CategoryOption[];
+}
+
+const tab = ref('running');
+// 鏌ヨ鍙傛暟
+const queryParams = ref<ProcessInstanceQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  categoryCode: undefined
+});
+
+onMounted(() => {
+  getList();
+  getTreeselect();
+});
+
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+const handleNodeClick = (data: CategoryVO) => {
+  queryParams.value.categoryCode = data.categoryCode;
+  if (data.categoryCode === 'ALL') {
+    queryParams.value.categoryCode = '';
+  }
+  handleQuery();
+};
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.categoryName.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watchEffect(
+  () => {
+    categoryTreeRef.value.filter(categoryName.value);
+  },
+  {
+    flush: 'post' // watchEffect浼氬湪DOM鎸傝浇鎴栬�呮洿鏂颁箣鍓嶅氨浼氳Е鍙戯紝姝ゅ睘鎬ф帶鍒跺湪DOM鍏冪礌鏇存柊鍚庤繍琛�
+  }
+);
+
+/** 鏌ヨ娴佺▼鍒嗙被涓嬫媺鏍戠粨鏋� */
+const getTreeselect = async () => {
+  const res = await listCategory();
+  categoryOptions.value = [];
+  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '椤剁骇鑺傜偣', children: [] };
+  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
+  categoryOptions.value.push(data);
+};
+
+//瀹℃壒璁板綍
+const handleApprovalRecord = (processInstanceId: string) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(processInstanceId);
+  }
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.categoryCode = '';
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: ProcessInstanceVO[]) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+//鍒嗛〉
+const getList = () => {
+  loading.value = true;
+  getCurrentSubmitByPage(queryParams.value).then((resp) => {
+    processInstanceList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row: ProcessInstanceVO) => {
+  const id = row.id || ids.value;
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎id涓恒��' + id + '銆戠殑鏁版嵁椤癸紵');
+  loading.value = true;
+  if ('running' === tab.value) {
+    await deleteRuntimeProcessAndHisInst(id).finally(() => (loading.value = false));
+    getList();
+  }
+  proxy?.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+};
+
+/** 鎾ら攢鎸夐挳鎿嶄綔 */
+const handleCancelProcessApply = async (processInstanceId: string) => {
+  await proxy?.$modal.confirm('鏄惁纭鎾ら攢褰撳墠鍗曟嵁锛�');
+  loading.value = true;
+  if ('running' === tab.value) {
+    await cancelProcessApply(processInstanceId).finally(() => (loading.value = false));
+    getList();
+  }
+  proxy?.$modal.msgSuccess('鎾ら攢鎴愬姛');
+};
+//鎻愪氦
+const submitVerifyOpen = async (id: string) => {
+  if (submitVerifyRef.value) {
+    submitVerifyRef.value.openDialog(id);
+  }
+};
+</script>
diff --git a/src/views/workflow/task/taskCopyList.vue b/src/views/workflow/task/taskCopyList.vue
new file mode 100644
index 0000000..6348024
--- /dev/null
+++ b/src/views/workflow/task/taskCopyList.vue
@@ -0,0 +1,141 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="浠诲姟鍚嶇О" prop="name">
+              <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName">
+              <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey">
+              <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column>
+        <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�">
+          <template #default="scope">
+            <template v-if="scope.row.participantVo && scope.row.assignee === null">
+              <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success">
+                {{ item }}
+              </el-tag>
+            </template>
+            <template v-else>
+              <el-tag type="success">
+                {{ scope.row.assigneeName }}
+              </el-tag>
+            </template>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70">
+          <template #default="scope">
+            <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="handleQuery"
+      />
+    </el-card>
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getTaskCopyByPage} from '@/api/workflow/task';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+const queryFormRef = ref<ElFormInstance>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+// 閬僵灞�
+const loading = ref(true);
+// 閫変腑鏁扮粍
+const ids = ref<Array<any>>([]);
+// 闈炲崟涓鐢�
+const single = ref(true);
+// 闈炲涓鐢�
+const multiple = ref(true);
+// 鏄剧ず鎼滅储鏉′欢
+const showSearch = ref(true);
+// 鎬绘潯鏁�
+const total = ref(0);
+// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁
+const taskList = ref([]);
+// 鏌ヨ鍙傛暟
+const queryParams = ref<TaskQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  processDefinitionName: undefined,
+  processDefinitionKey: undefined
+});
+onMounted(() => {
+  getTaskCopyList();
+});
+//瀹℃壒璁板綍
+const handleApprovalRecord = (row: TaskVO) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(row.processInstanceId);
+  }
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getTaskCopyList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: any) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+//鍒嗛〉
+const getTaskCopyList = () => {
+  loading.value = true;
+  getTaskCopyByPage(queryParams.value).then((resp) => {
+    taskList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+
+</script>
diff --git a/src/views/workflow/task/taskFinish.vue b/src/views/workflow/task/taskFinish.vue
new file mode 100644
index 0000000..3e8adba
--- /dev/null
+++ b/src/views/workflow/task/taskFinish.vue
@@ -0,0 +1,128 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="浠诲姟鍚嶇О" prop="name">
+              <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName">
+              <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey">
+              <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column>
+        <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�">
+          <template #default="scope">
+            <el-tag type="success">
+              {{ scope.row.assigneeName }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column>
+        <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="handleQuery"
+      />
+    </el-card>
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getTaskFinishByPage } from '@/api/workflow/task';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+const queryFormRef = ref<ElFormInstance>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+// 閬僵灞�
+const loading = ref(true);
+// 閫変腑鏁扮粍
+const ids = ref<Array<any>>([]);
+// 闈炲崟涓鐢�
+const single = ref(true);
+// 闈炲涓鐢�
+const multiple = ref(true);
+// 鏄剧ず鎼滅储鏉′欢
+const showSearch = ref(true);
+// 鎬绘潯鏁�
+const total = ref(0);
+// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁
+const taskList = ref([]);
+// 鏌ヨ鍙傛暟
+const queryParams = ref<TaskQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  processDefinitionName: undefined,
+  processDefinitionKey: undefined
+});
+onMounted(() => {
+  getFinishList();
+});
+//瀹℃壒璁板綍
+const handleApprovalRecord = (row: TaskVO) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(row.processInstanceId);
+  }
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getFinishList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: any) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+const getFinishList = () => {
+  loading.value = true;
+  getTaskFinishByPage(queryParams.value).then((resp) => {
+    taskList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+</script>
diff --git a/src/views/workflow/task/taskWaiting.vue b/src/views/workflow/task/taskWaiting.vue
new file mode 100644
index 0000000..c3f34aa
--- /dev/null
+++ b/src/views/workflow/task/taskWaiting.vue
@@ -0,0 +1,181 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="浠诲姟鍚嶇О" prop="name">
+              <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟鍚嶇О" label-width="100" prop="processDefinitionName">
+              <el-input v-model="queryParams.processDefinitionName" placeholder="璇疯緭鍏ユ祦绋嬪畾涔夊悕绉�" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="娴佺▼瀹氫箟KEY" label-width="100" prop="processDefinitionKey">
+              <el-input v-model="queryParams.processDefinitionKey" placeholder="璇疯緭鍏ユ祦绋嬪畾涔塊EY" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column fixed align="center" type="index" label="搴忓彿" width="50"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionName" label="娴佺▼瀹氫箟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="processDefinitionKey" label="娴佺▼瀹氫箟KEY"></el-table-column>
+        <el-table-column fixed align="center" prop="name" label="浠诲姟鍚嶇О"></el-table-column>
+        <el-table-column fixed align="center" prop="assigneeName" label="鍔炵悊浜�">
+          <template #default="scope">
+            <template v-if="scope.row.participantVo && scope.row.assignee === null">
+              <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success">
+                {{ item }}
+              </el-tag>
+            </template>
+            <template v-else>
+              <el-tag type="success">
+                {{ scope.row.assigneeName }}
+              </el-tag>
+            </template>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="businessStatusName" label="娴佺▼鐘舵��" min-width="70">
+          <template #default="scope">
+            <el-tag type="success">{{ scope.row.businessStatusName }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="createTime" label="鍒涘缓鏃堕棿" width="160"></el-table-column>
+        <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button link type="primary" size="small" icon="Document" @click="handleApprovalRecord(scope.row)">瀹℃壒璁板綍</el-button>
+              </el-col>
+              <el-col v-if="scope.row.participantVo && (scope.row.participantVo.claim === null || scope.row.participantVo.claim === true)" :span="1.5">
+                <el-button link type="primary" size="small" icon="Edit" @click="submitVerifyOpen(scope.row.id)">鍔炵悊</el-button>
+              </el-col>
+              <el-col v-if="scope.row.participantVo && scope.row.participantVo.claim === true" :span="1.5">
+                <el-button link type="primary" size="small" icon="Document" @click="handleReturnTask(scope.row.id)">褰掕繕</el-button>
+              </el-col>
+              <el-col v-if="scope.row.participantVo && scope.row.participantVo.claim === false" :span="1.5">
+                <el-button link type="primary" size="small" icon="Document" @click="handleClaimTask(scope.row.id)">璁ら</el-button>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="handleQuery"
+      />
+    </el-card>
+    <!-- 瀹℃壒璁板綍 -->
+    <approvalRecord ref="approvalRecordRef" />
+    <!-- 鎻愪氦缁勪欢 -->
+    <submitVerify ref="submitVerifyRef" @submit-callback="handleQuery" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getTaskWaitByPage, claim, returnTask } from '@/api/workflow/task';
+import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import SubmitVerify from '@/components/Process/submitVerify.vue';
+import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
+//鎻愪氦缁勪欢
+const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
+//瀹℃壒璁板綍缁勪欢
+const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+const queryFormRef = ref<ElFormInstance>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+// 閬僵灞�
+const loading = ref(true);
+// 閫変腑鏁扮粍
+const ids = ref<Array<any>>([]);
+// 闈炲崟涓鐢�
+const single = ref(true);
+// 闈炲涓鐢�
+const multiple = ref(true);
+// 鏄剧ず鎼滅储鏉′欢
+const showSearch = ref(true);
+// 鎬绘潯鏁�
+const total = ref(0);
+// 妯″瀷瀹氫箟琛ㄦ牸鏁版嵁
+const taskList = ref([]);
+// 鏌ヨ鍙傛暟
+const queryParams = ref<TaskQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: undefined,
+  processDefinitionName: undefined,
+  processDefinitionKey: undefined
+});
+onMounted(() => {
+  getWaitingList();
+});
+//瀹℃壒璁板綍
+const handleApprovalRecord = (row: TaskVO) => {
+  if (approvalRecordRef.value) {
+    approvalRecordRef.value.init(row.processInstanceId);
+  }
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getWaitingList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  handleQuery();
+};
+// 澶氶�夋閫変腑鏁版嵁
+const handleSelectionChange = (selection: any) => {
+  ids.value = selection.map((item: any) => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+//鍒嗛〉
+const getWaitingList = () => {
+  loading.value = true;
+  getTaskWaitByPage(queryParams.value).then((resp) => {
+    taskList.value = resp.rows;
+    total.value = resp.total;
+    loading.value = false;
+  });
+};
+//鎻愪氦
+const submitVerifyOpen = async (id: string) => {
+  if (submitVerifyRef.value) {
+    submitVerifyRef.value.openDialog(id);
+  }
+};
+
+/** 璁ら浠诲姟 */
+const handleClaimTask = async (taskId: string) => {
+  loading.value = true;
+  await claim(taskId).finally(() => (loading.value = false));
+  getWaitingList();
+  proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+};
+
+/** 褰掕繕浠诲姟 */
+const handleReturnTask = async (taskId: string) => {
+  loading.value = true;
+  await returnTask(taskId).finally(() => (loading.value = false));
+  getWaitingList();
+  proxy?.$modal.msgSuccess('鎿嶄綔鎴愬姛');
+};
+</script>
diff --git a/vite.config.ts b/vite.config.ts
index 01da668..99ea8e8 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -65,7 +65,24 @@
         'echarts',
         'vue-i18n',
         '@vueup/vue-quill',
+        'bpmn-js/lib/Modeler.js',
+        'bpmn-js-properties-panel',
+        'min-dash',
+        'bpmn-js/lib/features/palette/PaletteProvider',
+        'bpmn-js/lib/features/context-pad/ContextPadProvider',
+        'diagram-js/lib/draw/BaseRenderer',
+        'tiny-svg',
 
+        'element-plus/es/components/collapse-item/style/css',
+        'element-plus/es/components/collapse/style/css',
+        'element-plus/es/components/space/style/css',
+        'element-plus/es/components/container/style/css',
+        'element-plus/es/components/aside/style/css',
+        'element-plus/es/components/main/style/css',
+        'element-plus/es/components/header/style/css',
+        'element-plus/es/components/button-group/style/css',
+        'element-plus/es/components/radio-button/style/css',
+        'element-plus/es/components/checkbox-group/style/css',
         'element-plus/es/components/form/style/css',
         'element-plus/es/components/form-item/style/css',
         'element-plus/es/components/button/style/css',

--
Gitblit v1.9.3