办学质量监测教学评价系统
du
9 小时以前 6350384ee189b076372b6570331a50abbc2a053c
流程定义
已修改4个文件
已添加75个文件
14399 ■■■■■ 文件已修改
ruoyi-ui/apps/web-antd/package.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/category/index.ts 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/category/index1.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/deploy/index.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/form/index.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/identity/index.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/listener/index.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/model/index.ts 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/work/process.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/workflow/work/task.ts 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/bootstrap.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/components/ProcessDesigner/index.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/components/ProcessViewer/index.vue 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/modules/auto-place/CustomAutoPlace.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/modules/auto-place/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/modules/custom-renderer/CustomRenderer.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/modules/custom-renderer/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/modules/rules/CustomRules.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/modules/rules/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/Log.js 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/ProcessDesigner.vue 519 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/content-pad/contentPadProvider.js 390 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/content-pad/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/defaultEmpty.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/descriptor/activitiDescriptor.json 1071 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/descriptor/camundaDescriptor.json 1087 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/descriptor/flowableDescriptor.json 1230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/activiti/activitiExtension.js 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/activiti/index.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/camunda/extension.js 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/camunda/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/flowable/flowableExtension.js 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/flowable/index.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/palette/CustomPalette.js 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/palette/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/palette/paletteProvider.js 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/translate/customTranslate.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/designer/plugins/translate/zh.js 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/index.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/palette/ProcessPalette.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/palette/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/PropertiesPanel.vue 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/base/ElementBaseInfo.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/flow-condition/FlowCondition.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/form/ElementForm.vue 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/listeners/ElementListeners.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/listeners/UserTaskListeners.vue 335 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/listeners/template.js 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/listeners/utilSelf.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/multi-instance/ElementMultiInstance.vue 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/multi-instance/utilSelf.js 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/other/ElementOtherConfig.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/properties/ElementProperties.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/signal-message/SignalAndMessage.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/task/ElementTask.vue 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/task/task-components/ReceiveTask.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/task/task-components/ScriptTask.vue 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/penal/task/task-components/UserTask.vue 684 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/theme/element-variables.scss 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/theme/index.scss 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/theme/process-designer.scss 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/theme/process-panel.scss 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/package/utils.js 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/utils/min-dash.js 694 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/category/index.vue 335 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/deploy/index.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/form/index.vue 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/listener/index.vue 469 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/model/index.vue 438 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/claim.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/copy.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/detail.vue 545 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/finished.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/index.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/own.vue 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/start.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/workflow/work/todo.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/package.json
@@ -27,6 +27,7 @@
  },
  "dependencies": {
    "@ant-design/icons-vue": "^7.0.1",
    "@element-plus/icons-vue": "^2.3.1",
    "@form-create/designer": "^3.2.11",
    "@form-create/element-ui": "^3.2.25",
    "@tinymce/tinymce-vue": "^6.0.1",
@@ -48,20 +49,27 @@
    "ant-design-vue": "catalog:",
    "axios": "^1.10.0",
    "bpmn-js": "^18.6.2",
    "bpmn-js-token-simulation": "^0.38.1",
    "codemirror": "5.65.0",
    "codemirror-editor-vue3": "^2.8.0",
    "cropperjs": "^1.6.2",
    "crypto-js": "^4.2.0",
    "dayjs": "catalog:",
    "diagram-js": "^15.3.0",
    "diagram-js-minimap": "^5.2.0",
    "echarts": "^5.5.1",
    "element-plus": "^2.10.2",
    "jsencrypt": "^3.3.2",
    "lodash": "^4.17.21",
    "lodash-es": "^4.17.21",
    "min-dash": "^4.2.3",
    "pinia": "catalog:",
    "qs": "^6.13.1",
    "tinymce": "^7.3.0",
    "unplugin-vue-components": "^0.27.3",
    "vue": "catalog:",
    "vue-router": "catalog:"
    "vue-router": "catalog:",
    "x2js": "^3.4.4"
  },
  "devDependencies": {
    "@types/crypto-js": "^4.2.2",
ruoyi-ui/apps/web-antd/src/api/workflow/category/index.ts
@@ -1,63 +1,29 @@
import type {
  CategoryForm,
  CategoryQuery,
  CategoryTree,
  CategoryVO,
} from './model';
import type { ID, IDS } from '#/api/common';
// import request from '#/utils/request';
import { requestClient } from '#/api/request';
/**
 * èŽ·å–æµç¨‹åˆ†ç±»æ ‘åˆ—è¡¨
 * @returns tree
 */
export function categoryTree() {
  return requestClient.get<CategoryTree[]>('/workflow/category/categoryTree');
// æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
export function listCategory(query) {
  return requestClient.get('/category/list', { params: query });
}
/**
 * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
 * @param params
 * @returns æµç¨‹åˆ†ç±»åˆ—表
 */
export function categoryList(params?: CategoryQuery) {
  return requestClient.get<CategoryVO[]>(`/workflow/category/list`, { params });
// æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
export function listAllCategory(query) {
  return requestClient.get('/category/listAll', { params: query });
}
// æŸ¥è¯¢æµç¨‹åˆ†ç±»è¯¦ç»†
export function getCategory(categoryId) {
  return requestClient.get(`/category/${categoryId}`);
}
// æ–°å¢žæµç¨‹åˆ†ç±»
export function addCategory(data) {
  return requestClient.post('/category', { data });
}
/**
 * æŸ¥è¯¢æµç¨‹åˆ†ç±»è¯¦æƒ…
 * @param id id
 * @returns æµç¨‹åˆ†ç±»è¯¦æƒ…
 */
export function categoryInfo(id: ID) {
  return requestClient.get<CategoryVO>(`/workflow/category/${id}`);
// ä¿®æ”¹æµç¨‹åˆ†ç±»
export function updateCategory(data) {
  return requestClient.put('/category', { data });
}
/**
 * æ–°å¢žæµç¨‹åˆ†ç±»
 * @param data
 * @returns void
 */
export function categoryAdd(data: CategoryForm) {
  return requestClient.postWithMsg<void>('/workflow/category', data);
}
/**
 * æ›´æ–°æµç¨‹åˆ†ç±»
 * @param data
 * @returns void
 */
export function categoryUpdate(data: CategoryForm) {
  return requestClient.putWithMsg<void>('/workflow/category', data);
}
/**
 * åˆ é™¤æµç¨‹åˆ†ç±»
 * @param id id
 * @returns void
 */
export function categoryRemove(id: ID | IDS) {
  return requestClient.deleteWithMsg<void>(`/workflow/category/${id}`);
// åˆ é™¤æµç¨‹åˆ†ç±»
export function delCategory(categoryIds) {
  return requestClient.delete(`/category/${categoryIds}`);
}
ruoyi-ui/apps/web-antd/src/api/workflow/category/index1.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
import type {
  CategoryForm,
  CategoryQuery,
  CategoryTree,
  CategoryVO,
} from './model';
import type { ID, IDS } from '#/api/common';
import { requestClient } from '#/api/request';
/**
 * èŽ·å–æµç¨‹åˆ†ç±»æ ‘åˆ—è¡¨
 * @returns tree
 */
export function categoryTree() {
  return requestClient.get<CategoryTree[]>('/workflow/category/categoryTree');
}
/**
 * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
 * @param params
 * @returns æµç¨‹åˆ†ç±»åˆ—表
 */
export function categoryList(params?: CategoryQuery) {
  return requestClient.get<CategoryVO[]>(`/workflow/category/list`, { params });
}
/**
 * æŸ¥è¯¢æµç¨‹åˆ†ç±»è¯¦æƒ…
 * @param id id
 * @returns æµç¨‹åˆ†ç±»è¯¦æƒ…
 */
export function categoryInfo(id: ID) {
  return requestClient.get<CategoryVO>(`/workflow/category/${id}`);
}
/**
 * æ–°å¢žæµç¨‹åˆ†ç±»
 * @param data
 * @returns void
 */
export function categoryAdd(data: CategoryForm) {
  return requestClient.postWithMsg<void>('/workflow/category', data);
}
/**
 * æ›´æ–°æµç¨‹åˆ†ç±»
 * @param data
 * @returns void
 */
export function categoryUpdate(data: CategoryForm) {
  return requestClient.putWithMsg<void>('/workflow/category', data);
}
/**
 * åˆ é™¤æµç¨‹åˆ†ç±»
 * @param id id
 * @returns void
 */
export function categoryRemove(id: ID | IDS) {
  return requestClient.deleteWithMsg<void>(`/workflow/category/${id}`);
}
ruoyi-ui/apps/web-antd/src/api/workflow/deploy/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
// import request from '#/utils/request';
import { requestClient } from '#/api/request';
// æŸ¥è¯¢æµç¨‹éƒ¨ç½²åˆ—表
export function listDeploy(query) {
  return requestClient.get('/deploy/list', { params: query });
}
export function listPublish(query) {
  return requestClient.get('/deploy/publishList', { params: query });
}
// èŽ·å–æµç¨‹æ¨¡åž‹æµç¨‹å›¾
export function getBpmnXml(definitionId) {
  return requestClient.get(`/deploy/bpmnXml/${definitionId}`);
}
// ä¿®æ”¹æµç¨‹çŠ¶æ€
export function changeState(params) {
  return requestClient.put('/model',  { params: params });
}
// åˆ é™¤æµç¨‹éƒ¨ç½²
export function delDeploy(deployIds) {
  return requestClient.delete(`/deploy/${deployIds}`);
}
ruoyi-ui/apps/web-antd/src/api/workflow/form/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
// import request from '#/utils/request';
import { requestClient } from '#/api/request';
// æŸ¥è¯¢æµç¨‹è¡¨å•列表
export function listForm(query) {
  return requestClient.get('/flowable/form/list', { params: query });
}
// æŸ¥è¯¢æµç¨‹è¡¨å•详细
export function getForm(formId) {
  return requestClient.get(`/form/${formId}`);
}
// æ–°å¢žæµç¨‹è¡¨å•
export function addForm(data) {
  return requestClient.post('/form', { data });
}
// ä¿®æ”¹æµç¨‹è¡¨å•
export function updateForm(data) {
  return requestClient.put('/form', { data });
}
// åˆ é™¤æµç¨‹è¡¨å•
export function delForm(formId) {
  return requestClient.delete(`/form/${formId}`);
}
ruoyi-ui/apps/web-antd/src/api/workflow/identity/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// import request from '#/utils/request';
import { requestClient } from '#/api/request';
// æŸ¥è¯¢æµç¨‹æ¨¡åž‹ä¿¡æ¯
export function selectUser(query) {
  return requestClient.get('/system/user/list', { params: query });
}
export function deptTreeSelect() {
  return requestClient.get('/system/user/deptTree');
}
ruoyi-ui/apps/web-antd/src/api/workflow/listener/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
// import request from '#/utils/request';
import { requestClient } from '#/api/request';
// åˆ†é¡µæŸ¥è¯¢æµç¨‹ç›‘听器
export function queryListenerPage(query) {
  return requestClient.get('/flowable/listener/queryPage', { params: query });
}
// åˆ—表查询流程监听器
export function queryListenerList(query) {
  return requestClient.get('/flowable/listener/queryList', { params: query });
}
// è¯¦ç»†æŸ¥è¯¢æµç¨‹ç›‘听器
export function getListener(formId) {
  return requestClient.get(`/flowable/listener/query/${formId}`);
}
// æ–°å¢žæµç¨‹ç›‘听器
export function addListener(data) {
  return requestClient.post('/flowable/listener/insert', { data });
}
// ä¿®æ”¹æµç¨‹ç›‘听器
export function updateListener(data) {
  return requestClient.post('/flowable/listener/update', { data });
}
// åˆ é™¤æµç¨‹ç›‘听器
export function delListener(listenerId) {
  return requestClient.post(`/listener/delete/${listenerId}`);
}
// æ–°å¢žæµç¨‹ç›‘听器字段
export function insertListenerFieldAPI(data) {
  return requestClient.post('/listener/insertField', { data });
}
// ä¿®æ”¹æµç¨‹ç›‘听器字段
export function updateListenerFieldAPI(data) {
  return requestClient.post('/listener/updateField', { data });
}
// åˆ é™¤æµç¨‹ç›‘听器字段
export function deleteListenerFieldAPI(fieldIds) {
  return requestClient.post(`/listener/deleteField/${fieldIds}`);
}
ruoyi-ui/apps/web-antd/src/api/workflow/model/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
import { requestClient } from '#/api/request';
// æŸ¥è¯¢æµç¨‹æ¨¡åž‹ä¿¡æ¯
export function listModel(query) {
  return requestClient.get('/model/list', { params: query });
}
// æŸ¥è¯¢åŽ†å²æµç¨‹æ¨¡åž‹ä¿¡æ¯
export function historyModel(query) {
  return requestClient.get('/model/historyList', { params: query });
}
export function getModel(modelId) {
  return requestClient.get(`/model/${modelId}`);
}
// æ–°å¢žæ¨¡åž‹ä¿¡æ¯
export function addModel(data) {
  return requestClient.post('/model', { data });
}
// ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
export function updateModel(data) {
  return requestClient.put('/model', { data });
}
// ä¿å­˜æµç¨‹æ¨¡åž‹
export function saveModel(data) {
  return requestClient.post('/model/save', { data });
}
export function latestModel(params) {
  return requestClient.post('/model/latest', { params });
}
export function delModel(modelIds) {
  return requestClient.delete(`/model/${modelIds}`);
}
export function deployModel(params) {
  return requestClient.post('/model/deploy', { params });
}
// èŽ·å–æµç¨‹æ¨¡åž‹æµç¨‹å›¾
export function getBpmnXml(modelId) {
  return requestClient.get(`/model/bpmnXml/${modelId}`);
}
ruoyi-ui/apps/web-antd/src/api/workflow/work/process.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
import { requestClient } from '#/api/request';
// æŸ¥è¯¢æµç¨‹åˆ—表
export function listProcess(query) {
  return requestClient.get('/process/list', { params: query });
}
// æŸ¥è¯¢æµç¨‹åˆ—表
export function getProcessForm(query) {
  return requestClient.get('/process/getProcessForm', { params: query });
}
// éƒ¨ç½²æµç¨‹å®žä¾‹
export function startProcess(processDefId,data) {
  return requestClient.post(`/process/start/${processDefId}`, { data });
}
// åˆ é™¤æµç¨‹å®žä¾‹
export function delProcess(ids) {
  return requestClient.delete(`/process/instance/${ids}`);
}
// èŽ·å–æµç¨‹å›¾
export function getBpmnXml(processDefId) {
  return requestClient.get(`/process/bpmnXml/${processDefId}`);
}
export function detailProcess(query) {
  return requestClient.get('/process/detail', { params: query });
}
// æˆ‘的发起的流程
export function listOwnProcess(query) {
  return requestClient.get('/process/ownList', { params: query });
}
// æˆ‘待办的流程
export function listTodoProcess(query) {
  return requestClient.get('/process/todoList', { params: query });
}
// æˆ‘待签的流程
export function listClaimProcess(query) {
  return requestClient.get('/process/claimList', { params: query });
}
// æˆ‘已办的流程
export function listFinishedProcess(query) {
  return requestClient({
    url: '/process/finishedList',
    method: 'get',
    params: query
  });
}
export function listFinishedProcess(query) {
  return requestClient.get('/process/finishedList', { params: query });
}
// æŸ¥è¯¢æµç¨‹æŠ„送列表
export function listCopyProcess(query) {
  return requestClient.get('/process/copyList', { params: query });
}
// å–消申请
export function stopProcess(data) {
  return requestClient.post('/task/stopProcess', { data });
}
ruoyi-ui/apps/web-antd/src/api/workflow/work/task.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
// import request from '#/utils/request';
import { requestClient } from '#/api/request';
// å®Œæˆä»»åŠ¡
export function complete(data) {
  return requestClient.post('/task/complete', { data });
}
// å§”派任务
export function delegate(data) {
  return requestClient.post('/task/delegate', { data });
}
// è½¬åŠžä»»åŠ¡
export function transfer(data) {
  return requestClient.post('/task/transfer', { data });
}
// é€€å›žä»»åŠ¡
export function returnTask(data) {
  return requestClient.post('/task/return', { data });
}
// æ‹’绝任务
export function rejectTask(data) {
  return requestClient.post('/task/reject', { data });
}
// ç­¾æ”¶ä»»åŠ¡
export function claimTask(data) {
  return requestClient.post('/task/claim', { data });
}
// å¯é€€å›žä»»åŠ¡åˆ—è¡¨
export function returnList(data) {
  return requestClient.post('/task/returnList', { data });
}
// æ’¤å›žä»»åŠ¡
export function revokeProcess(data) {
  return requestClient.post('/task/revokeProcess', { data });
}
ruoyi-ui/apps/web-antd/src/bootstrap.ts
@@ -7,7 +7,7 @@
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import { useTitle } from '@vueuse/core';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'; // æ ·å¼æ–‡ä»¶
@@ -66,6 +66,9 @@
  app.use(formCreate);
  app.use(FcDesigner);
  app.use(Antd);
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component);
  }
  // åŠ¨æ€æ›´æ–°æ ‡é¢˜
  watchEffect(() => {
    if (preferences.app.dynamicTitle) {
ruoyi-ui/apps/web-antd/src/components/ProcessDesigner/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,214 @@
<template>
  <div class="process-design" :style="'display: flex; height:' + height">
    <my-process-designer
      v-model="xmlString"
      v-bind="controlForm"
      keyboard
      ref="processDesigner"
      :events="[
        'element.click',
        'connection.added',
        'connection.removed',
        'connection.changed'
      ]"
      @element-click="elementClick"
      @init-finished="initModeler"
      @element-contextmenu="elementContextmenu"
      @save="onSaveProcess"
    />
    <my-process-penal :bpmn-modeler="modeler" :prefix="controlForm.prefix" class="process-panel" />
    <!-- demo config -->
<!--    <div class="demo-control-bar">-->
<!--      <div class="open-model-button" @click="controlDrawerVisible = true"><el-icon><setting /></el-icon></div>-->
<!--    </div>-->
<!--    <el-drawer v-model="controlDrawerVisible" size="400px" title="偏好设置" append-to-body destroy-on-close>-->
<!--      <el-form :model="controlForm" size="small" label-width="100px" class="control-form" @submit.prevent>-->
<!--        <el-form-item label="流程ID">-->
<!--          <el-input v-model="controlForm.processId" @change="reloadProcessDesigner(true)" />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="流程名称">-->
<!--          <el-input v-model="controlForm.processName" @change="reloadProcessDesigner(true)" />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="流转模拟">-->
<!--          <el-switch v-model="controlForm.simulation" inactive-text="停用" active-text="启用" @change="reloadProcessDesigner()" />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="禁用双击">-->
<!--          <el-switch v-model="controlForm.labelEditing" inactive-text="停用" active-text="启用" @change="changeLabelEditingStatus" />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="自定义渲染">-->
<!--          <el-switch v-model="controlForm.labelVisible" inactive-text="停用" active-text="启用" @change="changeLabelVisibleStatus" />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="流程引擎">-->
<!--          <el-radio-group v-model="controlForm.prefix" @change="reloadProcessDesigner()">-->
<!--            <el-radio label="camunda">camunda</el-radio>-->
<!--            <el-radio label="flowable">flowable</el-radio>-->
<!--            <el-radio label="activiti">activiti</el-radio>-->
<!--          </el-radio-group>-->
<!--        </el-form-item>-->
<!--        <el-form-item label="工具栏">-->
<!--          <el-radio-group v-model="controlForm.headerButtonSize">-->
<!--            <el-radio label="small">small</el-radio>-->
<!--            <el-radio label="default">default</el-radio>-->
<!--            <el-radio label="large">large</el-radio>-->
<!--          </el-radio-group>-->
<!--        </el-form-item>-->
<!--        <el-switch v-model="pageMode" active-text="dark" inactive-text="light" @change="changePageMode"></el-switch>-->
<!--      </el-form>-->
<!--    </el-drawer>-->
  </div>
</template>
<script>
import MyProcessDesigner from '#/package/designer';
// import MyProcessPalette from '@/package/palette';
import MyProcessPenal from '#/package/penal';
// è‡ªå®šä¹‰æ¸²æŸ“(隐藏了 label æ ‡ç­¾ï¼‰
import CustomRenderer from '#/modules/custom-renderer';
// è‡ªå®šä¹‰å…ƒç´ é€‰ä¸­æ—¶çš„弹出菜单(修改 é»˜è®¤ä»»åŠ¡ ä¸º ç”¨æˆ·ä»»åŠ¡ï¼‰
import CustomContentPadProvider from '#/package/designer/plugins/content-pad';
// è‡ªå®šä¹‰å·¦ä¾§èœå•(修改 é»˜è®¤ä»»åŠ¡ ä¸º ç”¨æˆ·ä»»åŠ¡ï¼‰
import CustomPaletteProvider from '#/package/designer/plugins/palette';
import '#/package/theme/index.scss';
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';
export default {
  name: 'ProcessDesigner',
  props: {
    bpmnXml: {
      type: String,
      required: true
    },
    designerForm: {
      type: Object,
      required: true
    }
  },
  components: {
    MyProcessDesigner,
    MyProcessPenal
  },
  data () {
    return {
      height: document.documentElement.clientHeight - 94.5 + "px;",
      xmlString: this.bpmnXml,
      modeler: null,
      controlDrawerVisible: false,
      infoTipVisible: false,
      pageMode: false,
      controlForm: {
        processId: this.designerForm.processKey || '',
        processName: this.designerForm.processName || '',
        simulation: true,
        labelEditing: false,
        labelVisible: false,
        prefix: 'flowable',
        headerButtonSize: 'default',
        additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
      },
      addis: {
        CustomContentPadProvider,
        CustomPaletteProvider
      }
    }
  },
  methods: {
    reloadProcessDesigner(notDeep) {
      this.controlForm.additionalModel = [];
      for (const key in this.addis) {
        if (this.addis[key]) {
          this.controlForm.additionalModel.push(this.addis[key]);
        }
      }
      !notDeep && (this.xmlString = undefined);
      this.reloadIndex += 1;
      this.modeler = null; // é¿å… panel å¼‚常
    },
    changeLabelEditingStatus(status) {
      this.addis.labelEditing = status ? { labelEditingProvider: ['value', ''] } : false;
      this.reloadProcessDesigner();
    },
    changeLabelVisibleStatus(status) {
      this.addis.customRenderer = status ? CustomRenderer : false;
      this.reloadProcessDesigner();
    },
    elementClick(element) {
      this.element = element;
    },
    initModeler(modeler) {
      setTimeout(() => {
        this.modeler = modeler;
      }, 10);
    },
    elementContextmenu(element) {
    },
    onSaveProcess(saveData) {
      this.$emit('save', saveData);
    }
  }
}
</script>
<style lang="scss">
body {
  overflow: hidden;
  margin: 0;
  box-sizing: border-box;
}
body,
body * {
  /* æ»šåŠ¨æ¡ */
  &::-webkit-scrollbar-track-piece {
    background-color: #fff; /*滚动条的背景颜色*/
    -webkit-border-radius: 0; /*滚动条的圆角宽度*/
  }
  &::-webkit-scrollbar {
    width: 10px; /*滚动条的宽度*/
    height: 8px; /*滚动条的高度*/
  }
  &::-webkit-scrollbar-thumb:vertical {
    /*垂直滚动条的样式*/
    height: 50px;
    background-color: rgba(153, 153, 153, 0.5);
    -webkit-border-radius: 4px;
    outline: 2px solid #fff;
    outline-offset: -2px;
    border: 2px solid #fff;
  }
  &::-webkit-scrollbar-thumb {
    /*滚动条的hover样式*/
    background-color: rgba(159, 159, 159, 0.3);
    -webkit-border-radius: 4px;
  }
  &::-webkit-scrollbar-thumb:hover {
    /*滚动条的hover样式*/
    background-color: rgba(159, 159, 159, 0.5);
    -webkit-border-radius: 4px;
  }
}
.demo-control-bar {
  position: fixed;
  right: 8px;
  bottom: 48px;
  z-index: 1;
}
.open-model-button {
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  font-size: 32px;
  background: rgba(64, 158, 255, 1);
  color: #ffffff;
  cursor: pointer;
}
</style>
ruoyi-ui/apps/web-antd/src/components/ProcessViewer/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,237 @@
<template>
  <div class="process-viewer">
    <div class="process-canvas" style="height: 100%;" ref="processCanvas" v-show="!isLoading" />
    <!-- è‡ªå®šä¹‰ç®­å¤´æ ·å¼ï¼Œç”¨äºŽæˆåŠŸçŠ¶æ€ä¸‹æµç¨‹è¿žçº¿ç®­å¤´ -->
    <defs ref="customSuccessDefs">
      <marker id="sequenceflow-end-white-success" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
        <path class="success-arrow" d="M 1 5 L 11 10 L 1 15 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-success" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="10" orient="auto">
        <path class="success-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
    </defs>
    <!-- è‡ªå®šä¹‰ç®­å¤´æ ·å¼ï¼Œç”¨äºŽå¤±è´¥çŠ¶æ€ä¸‹æµç¨‹è¿žçº¿ç®­å¤´ -->
    <defs ref="customFailDefs">
      <marker id="sequenceflow-end-white-fail" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
        <path class="fail-arrow" d="M 1 5 L 11 10 L 1 15 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-fail" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="10" orient="auto">
        <path class="fail-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
    </defs>
    <!-- å·²å®ŒæˆèŠ‚ç‚¹æ‚¬æµ®å¼¹çª— -->
    <el-dialog class="comment-dialog" :title="dlgTitle || '审批记录'" v-model="dialogVisible">
      <el-row>
        <el-table :data="taskCommentList" border header-cell-class-name="table-header-gray">
          <el-table-column label="序号" header-align="center" align="center" type="index" width="55px" />
          <el-table-column label="候选办理" prop="candidate" width="150px" align="center" />
          <el-table-column label="实际办理" prop="assigneeName" width="100px" align="center" />
          <el-table-column label="处理时间" prop="createTime" width="140px" align="center" />
          <el-table-column label="办结时间" prop="finishTime" width="140px" align="center" />
          <el-table-column label="耗时" prop="duration" width="100px" align="center" />
          <el-table-column label="审批意见" align="center">
            <template #default="scope">
              {{scope.row.commentList&&scope.row.commentList[0]?scope.row.commentList[0].fullMessage:''}}
            </template>
          </el-table-column>
        </el-table>
      </el-row>
    </el-dialog>
    <div style="position: absolute; top: 0; left: 0; width: 100%;">
      <el-row type="flex" justify="end">
        <el-button-group key="scale-control">
          <el-button :plain="true" :disabled="defaultZoom <= 0.3" icon="ZoomOut" @click="processZoomOut()" />
          <el-button style="width: 90px;">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
          <el-button :plain="true" :disabled="defaultZoom >= 3.9" icon="ZoomIn" @click="processZoomIn()" />
          <el-button icon="ScaleToOriginal" @click="processReZoom()" />
          <slot />
        </el-button-group>
      </el-row>
    </div>
  </div>
</template>
<script>
import '#/package/theme/index.scss';
import BpmnViewer from 'bpmn-js/lib/Viewer';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
export default {
  props: {
    xml: {
      type: String
    },
    finishedInfo: {
      type: Object
    },
    // æ‰€æœ‰èŠ‚ç‚¹å®¡æ‰¹è®°å½•
    allCommentList: {
      type: Array
    }
  },
  data () {
    return {
      dialogVisible: false,
      dlgTitle: undefined,
      defaultZoom: 1,
      // æ˜¯å¦æ­£åœ¨åŠ è½½æµç¨‹å›¾
      isLoading: false,
      bpmnViewer: undefined,
      // å·²å®Œæˆæµç¨‹å…ƒç´ 
      processNodeInfo: undefined,
      // å½“前任务id
      selectTaskId: undefined,
      // ä»»åŠ¡èŠ‚ç‚¹å®¡æ‰¹è®°å½•
      taskCommentList: [],
      // å·²å®Œæˆä»»åŠ¡æ‚¬æµ®å»¶è¿ŸTimer
      hoverTimer: null
    }
  },
  watch: {
    xml: {
      handler(newXml) {
        this.importXML(newXml);
      },
      immediate: true
    },
    finishedInfo: {
      handler(newInfo) {
        this.setProcessStatus(newInfo);
      },
      immediate: true
    }
  },
  created() {
    this.$nextTick(() => {
      this.importXML(this.xml)
      this.setProcessStatus(this.finishedInfo);
    })
  },
  methods: {
    processReZoom() {
      this.defaultZoom = 1;
      this.bpmnViewer.get('canvas').zoom('fit-viewport', 'auto');
    },
    processZoomIn(zoomStep = 0.1) {
      let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
      if (newZoom > 4) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4');
      }
      this.defaultZoom = newZoom;
      this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
    },
    processZoomOut(zoomStep = 0.1) {
      let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
      if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
      }
      this.defaultZoom = newZoom;
      this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
    },
    // æµç¨‹å›¾é¢„览清空
    clearViewer() {
      if (this.$refs.processCanvas) {
        this.$refs.processCanvas.innerHTML = '';
      }
      if (this.bpmnViewer) {
        this.bpmnViewer.destroy();
      }
      this.bpmnViewer = null;
    },
    // æ·»åŠ è‡ªå®šä¹‰ç®­å¤´
    addCustomDefs() {
      const canvas = this.bpmnViewer.get('canvas');
      const svg = canvas._svg;
      const customSuccessDefs = this.$refs.customSuccessDefs;
      const customFailDefs = this.$refs.customFailDefs;
      svg.appendChild(customSuccessDefs);
      svg.appendChild(customFailDefs);
    },
    // ä»»åŠ¡æ‚¬æµ®å¼¹çª—
    onSelectElement(element) {
      this.selectTaskId = undefined;
      this.dlgTitle = undefined;
      if (this.processNodeInfo == null || this.processNodeInfo.finishedTaskSet == null) return;
      if (element == null || this.processNodeInfo.finishedTaskSet.indexOf(element.id) === -1) {
        return;
      }
      this.selectTaskId = element.id;
      this.dlgTitle = element.businessObject ? element.businessObject.name : undefined;
      // è®¡ç®—当前悬浮任务审批记录,如果记录为空不显示弹窗
      this.taskCommentList = (this.allCommentList || []).filter(item => {
        return item.activityId === this.selectTaskId;
      });
      this.dialogVisible = true;
    },
    // æ˜¾ç¤ºæµç¨‹å›¾
    async importXML(xml) {
      this.clearViewer();
      if (xml != null && xml !== '') {
        try {
          this.bpmnViewer = new BpmnViewer({
            additionalModules: [
              // ç§»åŠ¨æ•´ä¸ªç”»å¸ƒ
              MoveCanvasModule
            ],
            container: this.$refs.processCanvas,
          });
          // ä»»åŠ¡èŠ‚ç‚¹æ‚¬æµ®äº‹ä»¶
          this.bpmnViewer.on('element.click', ({ element }) => {
            this.onSelectElement(element);
          });
          this.isLoading = true;
          await this.bpmnViewer.importXML(xml);
          this.addCustomDefs();
        } catch (e) {
          this.clearViewer();
        } finally {
          this.isLoading = false;
          this.setProcessStatus(this.processNodeInfo);
        }
      }
    },
    // è®¾ç½®æµç¨‹å›¾å…ƒç´ çŠ¶æ€
    setProcessStatus (processNodeInfo) {
      this.processNodeInfo = processNodeInfo;
      if (this.isLoading || this.processNodeInfo == null || this.bpmnViewer == null) return;
      let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = this.processNodeInfo;
      const canvas = this.bpmnViewer.get('canvas');
      const elementRegistry = this.bpmnViewer.get('elementRegistry');
      if (Array.isArray(finishedSequenceFlowSet)) {
        finishedSequenceFlowSet.forEach(item => {
          if (item != null) {
            canvas.addMarker(item, 'success');
            let element = elementRegistry.get(item);
            const conditionExpression = element.businessObject.conditionExpression;
            if (conditionExpression) {
              canvas.addMarker(item, 'condition-expression');
            }
          }
        });
      }
      if (Array.isArray(finishedTaskSet)) {
        finishedTaskSet.forEach(item => canvas.addMarker(item, 'success'));
      }
      if (Array.isArray(unfinishedTaskSet)) {
        unfinishedTaskSet.forEach(item => canvas.addMarker(item, 'primary'));
      }
      if (Array.isArray(rejectedTaskSet)) {
        rejectedTaskSet.forEach(item => {
          if (item != null) {
            let element = elementRegistry.get(item);
            if (element.type.includes('Task')) {
              canvas.addMarker(item, 'danger');
            } else {
              canvas.addMarker(item, 'warning');
            }
          }
        })
      }
    }
  }
}
</script>
ruoyi-ui/apps/web-antd/src/modules/auto-place/CustomAutoPlace.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
import AutoPlace from 'diagram-js/lib/features/auto-place/AutoPlace';
export default function CustomAutoPlace(eventBus, modeling) {
  AutoPlace.call(this, eventBus, modeling, 3000);
  eventBus.on('autoPlace', 3000, function(context) {
    const shape = context.shape;
    const source = context.source;
    return getNewCustomShapePosition(source, shape);
  });
  this.append = function(source, shape, hints) {
    eventBus.fire('autoPlace.start', {
      source: source,
      shape: shape
    });
    // allow others to provide the position
    var position = eventBus.fire('autoPlace', {
      source: source,
      shape: shape
    });
    console.log('hints', hints, 'position', position);
    var newShape = modeling.appendShape(source, shape, position, source.parent, hints);
    eventBus.fire('autoPlace.end', {
      source: source,
      shape: newShape
    });
    return newShape;
  };
}
export function asTRBL(bounds) {
  return {
    top: bounds.y,
    right: bounds.x + (bounds.width || 0),
    bottom: bounds.y + (bounds.height || 0),
    left: bounds.x
  };
}
export function roundPoint(point) {
  return {
    x: Math.round(point.x),
    y: Math.round(point.y)
  };
}
export function getMid(bounds) {
  return roundPoint({
    x: bounds.x + (bounds.width || 0) / 2,
    y: bounds.y + (bounds.height || 0) / 2
  });
}
export function getNewCustomShapePosition(source, element, hints) {
  if (!hints) {
    hints = {};
  }
  var distance = hints.defaultDistance || 50;
  var sourceMid = getMid(source);
  var sourceTrbl = asTRBL(source);
  // simply put element right next to source
  return {
    x: sourceMid.x,
    y: sourceTrbl.bottom + distance + element.height / 2
  };
}
const F = function() {}; // æ ¸å¿ƒï¼Œåˆ©ç”¨ç©ºå¯¹è±¡ä½œä¸ºä¸­ä»‹ï¼›
F.prototype = AutoPlace.prototype; // æ ¸å¿ƒï¼Œå°†çˆ¶ç±»çš„原型赋值给空对象F;
CustomAutoPlace.prototype = new F(); // æ ¸å¿ƒï¼Œå°† F的实例赋值给子类;
CustomAutoPlace.prototype.constructor = AutoPlace; // ä¿®å¤å­ç±»CustomRenderer的构造器指向,防止原型链的混乱;
ruoyi-ui/apps/web-antd/src/modules/auto-place/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
import CustomAutoPlace from './CustomAutoPlace';
export default {
  __init__: ['autoPlace'],
  autoPlace: ['type', CustomAutoPlace]
};
ruoyi-ui/apps/web-antd/src/modules/custom-renderer/CustomRenderer.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer';
export default function CustomRenderer(eventBus, styles, pathMap, canvas, textRenderer) {
  const config = {
    defaultFillColor: '',
    defaultStrokeColor: '#8b238f',
    defaultLabelColor: '#2dd257'
  };
  BpmnRenderer.call(this, config, eventBus, styles, pathMap, canvas, textRenderer, 2000);
}
CustomRenderer.$inject = ['eventBus', 'styles', 'pathMap', 'canvas', 'textRenderer'];
const F = function() {}; // æ ¸å¿ƒï¼Œåˆ©ç”¨ç©ºå¯¹è±¡ä½œä¸ºä¸­ä»‹ï¼›
F.prototype = BpmnRenderer.prototype; // æ ¸å¿ƒï¼Œå°†çˆ¶ç±»çš„原型赋值给空对象F;
CustomRenderer.prototype = new F(); // æ ¸å¿ƒï¼Œå°† F的实例赋值给子类;
CustomRenderer.prototype.constructor = CustomRenderer; // ä¿®å¤å­ç±»CustomRenderer的构造器指向,防止原型链的混乱;
ruoyi-ui/apps/web-antd/src/modules/custom-renderer/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
import CustomRenderer from './CustomRenderer';
export default {
  __init__: ['customRenderer'],
  customRenderer: ['type', CustomRenderer]
};
ruoyi-ui/apps/web-antd/src/modules/rules/CustomRules.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules';
import inherits from 'inherits';
export default function CustomRules(eventBus) {
  BpmnRules.call(this, eventBus);
}
inherits(CustomRules, BpmnRules);
CustomRules.prototype.canDrop = function() {
  return false;
};
CustomRules.prototype.canMove = function() {
  return false;
};
ruoyi-ui/apps/web-antd/src/modules/rules/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
import CustomRules from './CustomRules';
export default {
  __init__: ['customRules'],
  customRules: ['type', CustomRules]
};
ruoyi-ui/apps/web-antd/src/package/Log.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
function Log() {}
Log.prototype.type = ['primary', 'success', 'warn', 'error', 'info'];
Log.prototype.typeColor = function(type) {
  let color = '';
  switch (type) {
    case 'primary':
      color = '#2d8cf0';
      break;
    case 'success':
      color = '#19be6b';
      break;
    case 'info':
      color = '#909399';
      break;
    case 'warn':
      color = '#ff9900';
      break;
    case 'error':
      color = '#f03f14';
      break;
    default:
      color = '#35495E';
      break;
  }
  return color;
};
Log.prototype.isArray = function(obj) {
  return Object.prototype.toString.call(obj) === '[object Array]';
};
Log.prototype.print = function(text, type = 'default', back = false) {
  if (typeof text === 'object') {
    // å¦‚果是對象則調用打印對象方式
    this.isArray(text) ? console.table(text) : console.dir(text);
    return;
  }
  if (back) {
    // å¦‚果是打印帶背景圖的
    console.log(`%c ${text} `, `background:${this.typeColor(type)}; padding: 2px; border-radius: 4px; color: #fff;`);
  } else {
    console.log(
      `%c ${text} `,
      `border: 1px solid ${this.typeColor(type)};
        padding: 2px; border-radius: 4px;
        color: ${this.typeColor(type)};`
    );
  }
};
Log.prototype.printBack = function(type = 'primary', title) {
  this.print(type, title, true);
};
Log.prototype.pretty = function(type = 'primary', title, text) {
  if (typeof text === 'object') {
    console.group('Console Group', title);
    console.log(
      `%c ${title}`,
      `background:${this.typeColor(type)};border:1px solid ${this.typeColor(type)};
        padding: 1px; border-radius: 4px; color: #fff;`
    );
    this.isArray(text) ? console.table(text) : console.dir(text);
    console.groupEnd();
    return;
  }
  console.log(
    `%c ${title} %c ${text} %c`,
    `background:${this.typeColor(type)};border:1px solid ${this.typeColor(type)};
      padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`,
    `border:1px solid ${this.typeColor(type)};
      padding: 1px; border-radius: 0 4px 4px 0; color: ${this.typeColor(type)};`,
    'background:transparent'
  );
};
Log.prototype.prettyPrimary = function(title, ...text) {
  text.forEach(t => this.pretty('primary', title, t));
};
Log.prototype.prettySuccess = function(title, ...text) {
  text.forEach(t => this.pretty('success', title, t));
};
Log.prototype.prettyWarn = function(title, ...text) {
  text.forEach(t => this.pretty('warn', title, t));
};
Log.prototype.prettyError = function(title, ...text) {
  text.forEach(t => this.pretty('error', title, t));
};
Log.prototype.prettyInfo = function(title, ...text) {
  text.forEach(t => this.pretty('info', title, t));
};
export default new Log();
ruoyi-ui/apps/web-antd/src/package/designer/ProcessDesigner.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,519 @@
<template>
  <div class="my-process-designer">
    <div class="my-process-designer__header">
      <slot name="control-header"></slot>
      <template v-if="!$slots['control-header']">
        <el-button-group key="file-control">
          <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-edit-outline" @click="onSave">保存流程</el-button>
          <el-button :size="headerButtonSize" :type="headerButtonType" :icon="FolderOpened" @click="$refs.refFile.click()">打开文件</el-button>
          <el-tooltip effect="light">
            <template #content>
              <a :size="headerButtonSize"  @click="downloadProcessAsXml()" class="link-type">下载为XML文件</a>
              <br />
              <a :size="headerButtonSize" type="text" @click="downloadProcessAsSvg()" class="link-type">下载为SVG文件</a>
              <br />
              <a :size="headerButtonSize" type="text" @click="downloadProcessAsBpmn()" class="link-type">下载为BPMN文件</a>
            </template>
            <el-button :size="headerButtonSize" :type="headerButtonType" :icon="Download">下载文件</el-button>
          </el-tooltip>
          <el-tooltip effect="light">
            <template #content>
              <a :size="headerButtonSize" type="text" @click="previewProcessXML" class="link-type">预览XML</a>
              <br />
               <a :size="headerButtonSize" type="text" @click="previewProcessJson" class="link-type">预览JSON</a>
            </template>
            <el-button :size="headerButtonSize" :type="headerButtonType" :icon="View">预览</el-button>
          </el-tooltip>
          <el-tooltip v-if="simulation" effect="light" :content="this.simulationStatus ? '退出模拟' : '开启模拟'">
            <el-button :size="headerButtonSize" :type="headerButtonType" :icon="Cpu" @click="processSimulation">
              æ¨¡æ‹Ÿ
            </el-button>
          </el-tooltip>
        </el-button-group>
        <el-button-group key="align-control">
          <el-tooltip effect="light" content="向左对齐">
            <el-button :size="headerButtonSize" class="align align-left" :icon="Histogram" @click="elementsAlign('left')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向右对齐">
            <el-button :size="headerButtonSize" class="align align-right" :icon="Histogram" @click="elementsAlign('right')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向上对齐">
            <el-button :size="headerButtonSize" class="align align-top" :icon="Histogram" @click="elementsAlign('top')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向下对齐">
            <el-button :size="headerButtonSize" class="align align-bottom" :icon="Histogram" @click="elementsAlign('bottom')" />
          </el-tooltip>
          <el-tooltip effect="light" content="水平居中">
            <el-button :size="headerButtonSize" class="align align-center" :icon="Histogram" @click="elementsAlign('center')" />
          </el-tooltip>
          <el-tooltip effect="light" content="垂直居中">
            <el-button :size="headerButtonSize" class="align align-middle" :icon="Histogram" @click="elementsAlign('middle')" />
          </el-tooltip>
        </el-button-group>
        <el-button-group key="scale-control">
          <el-tooltip effect="light" content="缩小视图">
            <el-button :size="headerButtonSize" :disabled="defaultZoom < 0.2" :icon="ZoomOut" @click="processZoomOut()" />
          </el-tooltip>
          <el-button :size="headerButtonSize">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
          <el-tooltip effect="light" content="放大视图">
            <el-button :size="headerButtonSize" :disabled="defaultZoom > 4" :icon="ZoomIn" @click="processZoomIn()" />
          </el-tooltip>
          <el-tooltip effect="light" content="重置视图并居中">
            <el-button :size="headerButtonSize" :icon="ScaleToOriginal" @click="processReZoom()" />
          </el-tooltip>
        </el-button-group>
        <el-button-group key="stack-control">
          <el-tooltip effect="light" content="撤销">
            <el-button :size="headerButtonSize" :disabled="!revocable" :icon="RefreshLeft" @click="processUndo()" />
          </el-tooltip>
          <el-tooltip effect="light" content="恢复">
            <el-button :size="headerButtonSize" :disabled="!recoverable" :icon="RefreshRight" @click="processRedo()" />
          </el-tooltip>
          <el-tooltip effect="light" content="重新绘制">
            <el-button :size="headerButtonSize" :icon="Refresh" @click="processRestart" />
          </el-tooltip>
        </el-button-group>
      </template>
      <!-- ç”¨äºŽæ‰“开本地文件-->
      <input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" />
    </div>
    <div class="my-process-designer__container">
      <div class="my-process-designer__canvas" ref="bpmn-canvas"></div>
    </div>
    <el-dialog :title="`预览${previewType}`" width="60%" v-model="previewModelVisible" append-to-body destroy-on-close>
        <Codemirror
          v-model:value="previewResult"
          :options="cmOptions"
          border
          :height="600"
        />
    </el-dialog>
  </div>
</template>
<script>
import BpmnModeler from "bpmn-js/lib/Modeler";
import DefaultEmptyXML from "./plugins/defaultEmpty";
// ç¿»è¯‘方法
import customTranslate from "./plugins/translate/customTranslate";
import translationsCN from "./plugins/translate/zh";
// æ¨¡æ‹Ÿæµè½¬æµç¨‹
import tokenSimulation from "bpmn-js-token-simulation";
// æ ‡ç­¾è§£æžæž„建器
// import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
// æ ‡ç­¾è§£æž Moddle
import camundaModdleDescriptor from './plugins/descriptor/camundaDescriptor.json';
import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json';
import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json';
// æ ‡ç­¾è§£æž Extension
import camundaModdleExtension from './plugins/extension-moddle/camunda';
import activitiModdleExtension from './plugins/extension-moddle/activiti';
import flowableModdleExtension from './plugins/extension-moddle/flowable';
// å¼•å…¥json转换与高亮
// import convert from "xml-js";
import X2JS from "x2js";
import Codemirror from 'codemirror-editor-vue3';
import 'codemirror/theme/monokai.css'
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/xml/xml.js';
export default {
  name: "MyProcessDesigner",
  componentName: "MyProcessDesigner",
  components: {
    Codemirror
  },
  setup() {
    return {
      Histogram, Cpu, Refresh, RefreshLeft, RefreshRight, ZoomOut, ZoomIn, View, Download, FolderOpened, ScaleToOriginal
    }
  },
  emits: ['destroy', 'init-finished', 'commandStack-changed', 'update:modelValue', 'change', 'canvas-viewbox-changed', 'element-click',"save","connection-added"],
  props: {
    modelValue: String, // xml å­—符串
    processId: String,
    processName: String,
    translations: Object, // è‡ªå®šä¹‰çš„翻译文件
    options: {
      type: Object,
      default: () => ({})
    }, // è‡ªå®šä¹‰çš„翻译文件
    additionalModel: [Object, Array], // è‡ªå®šä¹‰model
    moddleExtension: Object, // è‡ªå®šä¹‰moddle
    onlyCustomizeAddi: {
      type: Boolean,
      default: false
    },
    onlyCustomizeModdle: {
      type: Boolean,
      default: false
    },
    simulation: {
      type: Boolean,
      default: true
    },
    keyboard: {
      type: Boolean,
      default: true
    },
    prefix: {
      type: String,
      default: "camunda"
    },
    events: {
      type: Array,
      default: () => ["element.click"]
    },
    headerButtonSize: {
      type: String,
      default: "small",
      validator: value => ["default", "medium", "small", "mini"].indexOf(value) !== -1
    },
    headerButtonType: {
      type: String,
      default: "primary",
      validator: value => ["default", "primary", "success", "warning", "danger", "info"].indexOf(value) !== -1
    }
  },
  data() {
    return {
      defaultZoom: 1,
      previewModelVisible: false,
      simulationStatus: false,
      previewResult: "",
      previewType: "xml",
      recoverable: false,
      revocable: false,
      cmOptions: {
        mode: 'xml', // è¯­è¨€æ¨¡å¼
        theme: 'monokai', // ä¸»é¢˜
        lineNumbers: true, // æ˜¾ç¤ºè¡Œå·
        smartIndent: true, // æ™ºèƒ½ç¼©è¿›
        readOnly: true,
        indentUnit: 2, // æ™ºèƒ½ç¼©è¿›å•位为4个空格长度
        foldGutter: true, // å¯ç”¨è¡Œæ§½ä¸­çš„代码折叠
        styleActiveLine: true // æ˜¾ç¤ºé€‰ä¸­è¡Œçš„æ ·å¼
      }
    };
  },
  computed: {
    additionalModules() {
      const Modules = [];
      // ä»…保留用户自定义扩展模块
      if (this.onlyCustomizeAddi) {
        if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
          return this.additionalModel || [];
        }
        return [this.additionalModel];
      }
      // æ’入用户自定义扩展模块
      if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
        Modules.push(...this.additionalModel);
      } else {
        this.additionalModel && Modules.push(this.additionalModel);
      }
      // ç¿»è¯‘模块
      const TranslateModule = {
        translate: ["value", customTranslate(this.translations || translationsCN)]
      };
      Modules.push(TranslateModule);
      // æ¨¡æ‹Ÿæµè½¬æ¨¡å—
      if (this.simulation) {
        Modules.push(tokenSimulation);
      }
      // æ ¹æ®éœ€è¦çš„æµç¨‹ç±»åž‹è®¾ç½®æ‰©å±•元素构建模块
      // if (this.prefix === "bpmn") {
      //   Modules.push(bpmnModdleExtension);
      // }
      if (this.prefix === "camunda") {
        Modules.push(camundaModdleExtension);
      }
      if (this.prefix === "flowable") {
        Modules.push(flowableModdleExtension);
      }
      if (this.prefix === "activiti") {
        Modules.push(activitiModdleExtension);
      }
      return Modules;
    },
    moddleExtensions() {
      const Extensions = {};
      // ä»…使用用户自定义模块
      if (this.onlyCustomizeModdle) {
        return this.moddleExtension || null;
      }
      // æ’入用户自定义模块
      if (this.moddleExtension) {
        for (let key in this.moddleExtension) {
          Extensions[key] = this.moddleExtension[key];
        }
      }
      // æ ¹æ®éœ€è¦çš„ "流程类型" è®¾ç½® å¯¹åº”的解析文件
      if (this.prefix === "activiti") {
        Extensions.activiti = activitiModdleDescriptor;
      }
      if (this.prefix === "flowable") {
        Extensions.flowable = flowableModdleDescriptor;
      }
      if (this.prefix === "camunda") {
        Extensions.camunda = camundaModdleDescriptor;
      }
      return Extensions;
    }
  },
  mounted() {
    this.initBpmnModeler();
    this.createNewDiagram(this.modelValue);
    // this.$once("hook:beforeUnmount", () => {
    //   if (this.bpmnModeler) this.bpmnModeler.destroy();
    //   this.$emit("destroy", this.bpmnModeler);
    //   this.bpmnModeler = null;
    // });
  },
  beforeUnmount() {
    if (this.bpmnModeler) this.bpmnModeler.destroy();
    this.$emit("destroy", this.bpmnModeler);
    this.bpmnModeler = null;
  },
  methods: {
    onSave () {
      return new Promise((resolve, reject) => {
        if (this.bpmnModeler == null) {
          reject();
        }
        this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
          this.$emit('save', xml);
          resolve(xml);
        });
      })
    },
    initBpmnModeler() {
      if (this.bpmnModeler) return;
      this.bpmnModeler = new BpmnModeler({
        container: this.$refs["bpmn-canvas"],
        keyboard: this.keyboard ? { bindTo: document } : null,
        additionalModules: this.additionalModules,
        moddleExtensions: this.moddleExtensions,
        ...this.options
      });
      this.$emit("init-finished", this.bpmnModeler);
      this.initModelListeners();
    },
    initModelListeners() {
      const EventBus = this.bpmnModeler.get("eventBus");
      const that = this;
      // æ³¨å†Œéœ€è¦çš„监听事件, å°†. æ›¿æ¢ä¸º - , é¿å…è§£æžå¼‚常
      this.events.forEach(event => {
        EventBus.on(event, function(eventObj) {
          let eventName = event.replace(/\./g, "-");
          let element = eventObj ? eventObj.element : null;
          that.$emit(eventName, element, eventObj);
        });
      });
      // ç›‘听图形改变返回xml
      EventBus.on("commandStack.changed", async event => {
        try {
          this.recoverable = this.bpmnModeler.get("commandStack").canRedo();
          this.revocable = this.bpmnModeler.get("commandStack").canUndo();
          let { xml } = await this.bpmnModeler.saveXML({ format: true });
          this.$emit("commandStack-changed", event);
          this.$emit('update:modelValue', xml);
          this.$emit("change", xml);
        } catch (e) {
          console.error(`[Process Designer Warn]: ${e.message || e}`);
        }
      });
      // ç›‘听视图缩放变化
      this.bpmnModeler.on("canvas.viewbox.changed", ({ viewbox }) => {
        this.$emit("canvas-viewbox-changed", { viewbox });
        const { scale } = viewbox;
        this.defaultZoom = Math.floor(scale * 100) / 100;
      });
    },
    /* åˆ›å»ºæ–°çš„æµç¨‹å›¾ */
    async createNewDiagram(xml) {
      // å°†å­—符串转换成图显示出来
      let newId = this.processId || `Process_${new Date().getTime()}`;
      let newName = this.processName || `业务流程_${new Date().getTime()}`;
      let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
      try {
        let { warnings } = await this.bpmnModeler.importXML(xmlString);
        if (warnings && warnings.length) {
          warnings.forEach(warn => console.warn(warn));
        }
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e?.message || e}`);
      }
    },
    // ä¸‹è½½æµç¨‹å›¾åˆ°æœ¬åœ°
    /**
     * @param {string} type
     * @param {*} name
     */
    async downloadProcess(type, name) {
      try {
        const _this = this;
        // æŒ‰éœ€è¦ç±»åž‹åˆ›å»ºæ–‡ä»¶å¹¶ä¸‹è½½
        if (type === "xml" || type === "bpmn") {
          const { err, xml } = await this.bpmnModeler.saveXML();
          // è¯»å–异常时抛出异常
          if (err) {
            console.error(`[Process Designer Warn ]: ${err.message || err}`);
          }
          let { href, filename } = _this.setEncoded(type.toUpperCase(), name, xml);
          downloadFunc(href, filename);
        } else {
          const { err, svg } = await this.bpmnModeler.saveSVG();
          // è¯»å–异常时抛出异常
          if (err) {
            return console.error(err);
          }
          let { href, filename } = _this.setEncoded("SVG", name, svg);
          downloadFunc(href, filename);
        }
      } catch (e) {
        console.error(`[Process Designer Warn ]: ${e.message || e}`);
      }
      // æ–‡ä»¶ä¸‹è½½æ–¹æ³•
      function downloadFunc(href, filename) {
        if (href && filename) {
          let a = document.createElement("a");
          a.download = filename; //指定下载的文件名
          a.href = href; //  URL对象
          a.click(); // æ¨¡æ‹Ÿç‚¹å‡»
          URL.revokeObjectURL(a.href); // é‡Šæ”¾URL å¯¹è±¡
        }
      }
    },
    // æ ¹æ®æ‰€éœ€ç±»åž‹è¿›è¡Œè½¬ç å¹¶è¿”回下载地址
    setEncoded(type, filename = "diagram", data) {
      const encodedData = encodeURIComponent(data);
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === "svg" ? "text/xml" : "bpmn20-xml"};charset=UTF-8,${encodedData}`,
        data: data
      };
    },
    // åŠ è½½æœ¬åœ°æ–‡ä»¶
    importLocalFile() {
      const that = this;
      const file = this.$refs.refFile.files[0];
      const reader = new FileReader();
      reader.readAsText(file);
      reader.onload = function() {
        let xmlStr = this.result;
        that.createNewDiagram(xmlStr);
      };
    },
    /* ------------------------------------------------ refs methods ------------------------------------------------------ */
    downloadProcessAsXml() {
      this.downloadProcess("xml");
    },
    downloadProcessAsBpmn() {
      this.downloadProcess("bpmn");
    },
    downloadProcessAsSvg() {
      this.downloadProcess("svg");
    },
    processSimulation() {
      this.simulationStatus = !this.simulationStatus;
      this.simulation && this.bpmnModeler.get("toggleMode").toggleMode();
    },
    processRedo() {
      this.bpmnModeler.get("commandStack").redo();
    },
    processUndo() {
      this.bpmnModeler.get("commandStack").undo();
    },
    processZoomIn(zoomStep = 0.1) {
      let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
      if (newZoom > 4) {
        throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
      }
      this.defaultZoom = newZoom;
      this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
    },
    processZoomOut(zoomStep = 0.1) {
      let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
      if (newZoom < 0.2) {
        throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
      }
      this.defaultZoom = newZoom;
      this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
    },
    processZoomTo(newZoom = 1) {
      if (newZoom < 0.2) {
        throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
      }
      if (newZoom > 4) {
        throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
      }
      this.defaultZoom = newZoom;
      this.bpmnModeler.get("canvas").zoom(newZoom);
    },
    processReZoom() {
      this.defaultZoom = 1;
      this.bpmnModeler.get("canvas").zoom("fit-viewport", "auto");
    },
    processRestart() {
      this.recoverable = false;
      this.revocable = false;
      this.createNewDiagram(null);
    },
    elementsAlign(align) {
      const Align = this.bpmnModeler.get("alignElements");
      const Selection = this.bpmnModeler.get("selection");
      const SelectedElements = Selection.get();
      if (!SelectedElements || SelectedElements.length <= 1) {
        this.$message.warning("请按住 Ctrl é”®é€‰æ‹©å¤šä¸ªå…ƒç´ å¯¹é½");
        return;
      }
      this.$confirm("自动对齐可能造成图形变形,是否继续?", "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(() => Align.trigger(SelectedElements, align));
    },
    /*-----------------------------    æ–¹æ³•结束     ---------------------------------*/
    previewProcessXML() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.previewResult = xml;
        this.previewType = 'xml';
        this.cmOptions.mode = 'xml'
        this.previewModelVisible = true;
      });
    },
    previewProcessJson() {
      // this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
      //   this.previewResult = convert.xml2json(xml, { spaces: 2 });
      //   this.previewType = "json";
      //   this.previewModelVisible = true;
      // });
      const newConvert = new X2JS();
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        const { definitions } = newConvert.xml2js(xml);
        if (definitions) {
          this.previewResult = JSON.stringify(definitions, null, 4);
        } else {
          this.previewResult = "";
        }
        this.previewType = "json";
        this.previewModelVisible = true;
      });
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/designer/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
import MyProcessDesigner from './ProcessDesigner.vue';
MyProcessDesigner.install = function(Vue) {
  Vue.component(MyProcessDesigner.name, MyProcessDesigner);
};
export default MyProcessDesigner;
ruoyi-ui/apps/web-antd/src/package/designer/plugins/content-pad/contentPadProvider.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,390 @@
import { assign, forEach, isArray } from 'min-dash';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { isExpanded, isEventSubProcess } from 'bpmn-js/lib/util/DiUtil';
import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil';
import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
/**
 * A provider for BPMN 2.0 elements context pad
 */
export default function ContextPadProvider(
  config,
  injector,
  eventBus,
  contextPad,
  modeling,
  elementFactory,
  connect,
  create,
  popupMenu,
  canvas,
  rules,
  translate,
  elementRegistry
) {
  config = config || {};
  contextPad.registerProvider(this);
  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._translate = translate;
  if (config.autoPlace !== false) {
    this._autoPlace = injector.get('autoPlace', false);
  }
  eventBus.on('create.end', 250, function(event) {
    var context = event.context;
    var shape = context.shape;
    if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
      return;
    }
    var entries = contextPad.getEntries(shape);
    if (entries.replace) {
      entries.replace.action.click(event, shape);
    }
  });
}
ContextPadProvider.$inject = [
  'config.contextPad',
  'injector',
  'eventBus',
  'contextPad',
  'modeling',
  'elementFactory',
  'connect',
  'create',
  'popupMenu',
  'canvas',
  'rules',
  'translate',
  'elementRegistry'
];
ContextPadProvider.prototype.getContextPadEntries = function(element) {
  var contextPad = this._contextPad;
  var modeling = this._modeling;
  var elementFactory = this._elementFactory;
  var connect = this._connect;
  var create = this._create;
  var popupMenu = this._popupMenu;
  var canvas = this._canvas;
  var rules = this._rules;
  var autoPlace = this._autoPlace;
  var translate = this._translate;
  var actions = {};
  if (element.type === 'label') {
    return actions;
  }
  var businessObject = element.businessObject;
  function startConnect(event, element) {
    connect.start(event, element);
  }
  function removeElement() {
    modeling.removeElements([element]);
  }
  function getReplaceMenuPosition(element) {
    var Y_OFFSET = 5;
    var diagramContainer = canvas.getContainer();
    var pad = contextPad.getPad(element).html;
    var diagramRect = diagramContainer.getBoundingClientRect();
    var padRect = pad.getBoundingClientRect();
    var top = padRect.top - diagramRect.top;
    var left = padRect.left - diagramRect.left;
    var pos = {
      x: left,
      y: top + padRect.height + Y_OFFSET
    };
    return pos;
  }
  /**
   * Create an append action
   *
   * @param {string} type
   * @param {string} className
   * @param {string} [title]
   * @param {Object} [options]
   *
   * @return {Object} descriptor
   */
  function appendAction(type, className, title, options) {
    if (typeof title !== 'string') {
      options = title;
      title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
    }
    function appendStart(event, element) {
      var shape = elementFactory.createShape(assign({ type: type }, options));
      create.start(event, shape, {
        source: element
      });
    }
    var append = autoPlace
      ? function(event, element) {
        var shape = elementFactory.createShape(assign({ type: type }, options));
        autoPlace.append(element, shape);
      }
      : appendStart;
    return {
      group: 'model',
      className: className,
      title: title,
      action: {
        dragstart: appendStart,
        click: append
      }
    };
  }
  function splitLaneHandler(count) {
    return function(event, element) {
      // actual split
      modeling.splitLane(element, count);
      // refresh context pad after split to
      // get rid of split icons
      contextPad.open(element, true);
    };
  }
  if (isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) && isExpanded(businessObject)) {
    var childLanes = getChildLanes(element);
    assign(actions, {
      'lane-insert-above': {
        group: 'lane-insert-above',
        className: 'bpmn-icon-lane-insert-above',
        title: translate('Add Lane above'),
        action: {
          click: function(event, element) {
            modeling.addLane(element, 'top');
          }
        }
      }
    });
    if (childLanes.length < 2) {
      if (element.height >= 120) {
        assign(actions, {
          'lane-divide-two': {
            group: 'lane-divide',
            className: 'bpmn-icon-lane-divide-two',
            title: translate('Divide into two Lanes'),
            action: {
              click: splitLaneHandler(2)
            }
          }
        });
      }
      if (element.height >= 180) {
        assign(actions, {
          'lane-divide-three': {
            group: 'lane-divide',
            className: 'bpmn-icon-lane-divide-three',
            title: translate('Divide into three Lanes'),
            action: {
              click: splitLaneHandler(3)
            }
          }
        });
      }
    }
    assign(actions, {
      'lane-insert-below': {
        group: 'lane-insert-below',
        className: 'bpmn-icon-lane-insert-below',
        title: translate('Add Lane below'),
        action: {
          click: function(event, element) {
            modeling.addLane(element, 'bottom');
          }
        }
      }
    });
  }
  if (is(businessObject, 'bpmn:FlowNode')) {
    if (is(businessObject, 'bpmn:EventBasedGateway')) {
      assign(actions, {
        'append.receive-task': appendAction('bpmn:ReceiveTask', 'bpmn-icon-receive-task', translate('Append ReceiveTask')),
        'append.message-intermediate-event': appendAction(
          'bpmn:IntermediateCatchEvent',
          'bpmn-icon-intermediate-event-catch-message',
          translate('Append MessageIntermediateCatchEvent'),
          { eventDefinitionType: 'bpmn:MessageEventDefinition' }
        ),
        'append.timer-intermediate-event': appendAction(
          'bpmn:IntermediateCatchEvent',
          'bpmn-icon-intermediate-event-catch-timer',
          translate('Append TimerIntermediateCatchEvent'),
          { eventDefinitionType: 'bpmn:TimerEventDefinition' }
        ),
        'append.condition-intermediate-event': appendAction(
          'bpmn:IntermediateCatchEvent',
          'bpmn-icon-intermediate-event-catch-condition',
          translate('Append ConditionIntermediateCatchEvent'),
          { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
        ),
        'append.signal-intermediate-event': appendAction(
          'bpmn:IntermediateCatchEvent',
          'bpmn-icon-intermediate-event-catch-signal',
          translate('Append SignalIntermediateCatchEvent'),
          { eventDefinitionType: 'bpmn:SignalEventDefinition' }
        )
      });
    } else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
      assign(actions, {
        'append.compensation-activity': appendAction('bpmn:Task', 'bpmn-icon-task', translate('Append compensation activity'), {
          isForCompensation: true
        })
      });
    } else if (
      !is(businessObject, 'bpmn:EndEvent') &&
      !businessObject.isForCompensation &&
      !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
      !isEventSubProcess(businessObject)
    ) {
      assign(actions, {
        'append.end-event': appendAction('bpmn:EndEvent', 'bpmn-icon-end-event-none', translate('Append EndEvent')),
        'append.gateway': appendAction('bpmn:ExclusiveGateway', 'bpmn-icon-gateway-none', translate('Append Gateway')),
        'append.append-task': appendAction('bpmn:UserTask', 'bpmn-icon-user-task', translate('Append Task')),
        'append.intermediate-event': appendAction(
          'bpmn:IntermediateThrowEvent',
          'bpmn-icon-intermediate-event-none',
          translate('Append Intermediate/Boundary Event')
        )
      });
    }
  }
  if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
    // Replace menu entry
    assign(actions, {
      replace: {
        group: 'edit',
        className: 'bpmn-icon-screw-wrench',
        title: translate('Change type'),
        action: {
          click: function(event, element) {
            var position = assign(getReplaceMenuPosition(element), {
              cursor: { x: event.x, y: event.y }
            });
            popupMenu.open(element, 'bpmn-replace', position);
          }
        }
      }
    });
  }
  if (isAny(businessObject, ['bpmn:FlowNode', 'bpmn:InteractionNode', 'bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
    assign(actions, {
      'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation'),
      connect: {
        group: 'connect',
        className: 'bpmn-icon-connection-multi',
        title: translate('Connect using ' + (businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or ') + 'Association'),
        action: {
          click: startConnect,
          dragstart: startConnect
        }
      }
    });
  }
  if (isAny(businessObject, ['bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
    assign(actions, {
      connect: {
        group: 'connect',
        className: 'bpmn-icon-connection-multi',
        title: translate('Connect using DataInputAssociation'),
        action: {
          click: startConnect,
          dragstart: startConnect
        }
      }
    });
  }
  if (is(businessObject, 'bpmn:Group')) {
    assign(actions, {
      'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
    });
  }
  // delete element entry, only show if allowed by rules
  var deleteAllowed = rules.allowed('elements.delete', { elements: [element] });
  if (isArray(deleteAllowed)) {
    // was the element returned as a deletion candidate?
    deleteAllowed = deleteAllowed[0] === element;
  }
  if (deleteAllowed) {
    assign(actions, {
      delete: {
        group: 'edit',
        className: 'bpmn-icon-trash',
        title: translate('Remove'),
        action: {
          click: removeElement
        }
      }
    });
  }
  return actions;
};
// helpers /////////
function isEventType(eventBo, type, definition) {
  var isType = eventBo.$instanceOf(type);
  var isDefinition = false;
  var definitions = eventBo.eventDefinitions || [];
  forEach(definitions, function(def) {
    if (def.$type === definition) {
      isDefinition = true;
    }
  });
  return isType && isDefinition;
}
ruoyi-ui/apps/web-antd/src/package/designer/plugins/content-pad/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
import CustomContextPadProvider from './contentPadProvider';
export default {
  __init__: ['contextPadProvider'],
  contextPadProvider: ['type', CustomContextPadProvider]
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/defaultEmpty.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
export default (key, name, type) => {
  if (!type) type = 'camunda';
  const TYPE_TARGET = {
    activiti: 'http://activiti.org/bpmn',
    camunda: 'http://bpmn.io/schema/bpmn',
    flowable: 'http://flowable.org/bpmn'
  };
  return `<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
  xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
  xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
  id="diagram_${key}"
  targetNamespace="${TYPE_TARGET[type]}">
  <bpmn2:process id="${key}" name="${name}" isExecutable="true">
  </bpmn2:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}">
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn2:definitions>`;
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/descriptor/activitiDescriptor.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1071 @@
{
  "name": "Activiti",
  "uri": "http://activiti.org/bpmn",
  "prefix": "activiti",
  "xml": {
    "tagAlias": "lowerCase"
  },
  "associations": [],
  "types": [
    {
      "name": "Definitions",
      "isAbstract": true,
      "extends": [
        "bpmn:Definitions"
      ],
      "properties": [
        {
          "name": "diagramRelationId",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "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": "JobPriorized",
      "isAbstract": true,
      "extends": [
        "bpmn:Process",
        "activiti: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": "activiti:errorMessage",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "PotentialStarter",
      "superClass": [
        "Element"
      ],
      "properties": [
        {
          "name": "resourceAssignmentExpression",
          "type": "bpmn:ResourceAssignmentExpression"
        }
      ]
    },
    {
      "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":"executionListener",
          "isAbstract": true,
          "type":"Expression"
        }
      ]
    },
    {
      "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": "multiinstance_type",
      "superClass":[
        "Element"
      ]
    },
    {
      "name": "multiinstance_condition",
      "superClass":[
        "Element"
      ]
    },
    {
      "name": "Assignable",
      "extends": [ "bpmn:UserTask" ],
      "properties": [
        {
          "name": "assignee",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "candidateUsers",
          "isAttr": true,
          "type": "String"
        },
        {
          "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": "multiinstance_condition",
          "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": "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": [
        "activiti:ServiceTaskLike"
      ],
      "properties": [
        {
          "name": "type",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "topic",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "TaskPriorized",
      "extends": [
        "bpmn:Process",
        "activiti: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": [
          "activiti:ServiceTaskLike"
        ]
      },
      "properties": [
        {
          "name": "inputOutput",
          "type": "InputOutput"
        },
        {
          "name": "connectorId",
          "type": "String"
        }
      ]
    },
    {
      "name": "InputOutput",
      "superClass": [
        "Element"
      ],
      "meta": {
        "allowedIn": [
          "bpmn:FlowNode",
          "activiti: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": [
          "activiti:ServiceTaskLike",
          "activiti:ExecutionListener",
          "activiti:TaskListener"
        ]
      },
      "properties": [
        {
          "name": "name",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "expression",
          "type": "String"
        },
        {
          "name": "stringValue",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "string",
          "type": "String"
        }
      ]
    },
    {
      "name": "InputParameter",
      "superClass": [ "InputOutputParameter" ]
    },
    {
      "name": "OutputParameter",
      "superClass": [ "InputOutputParameter" ]
    },
    {
      "name": "Collectable",
      "isAbstract": true,
      "extends": [ "bpmn:MultiInstanceLoopCharacteristics" ],
      "superClass": [ "activiti:AsyncCapable" ],
      "properties": [
        {
          "name": "collection",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "elementVariable",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "FailedJobRetryTimeCycle",
      "superClass": [ "Element" ],
      "meta": {
        "allowedIn": [
          "activiti: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": "FormProperty",
      "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": [ ]
}
ruoyi-ui/apps/web-antd/src/package/designer/plugins/descriptor/camundaDescriptor.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1087 @@
{
  "name": "Camunda",
  "uri": "http://camunda.org/schema/1.0/bpmn",
  "prefix": "camunda",
  "xml": {
    "tagAlias": "lowerCase"
  },
  "associations": [],
  "types": [
    {
      "name": "Definitions",
      "isAbstract": true,
      "extends": [
        "bpmn:Definitions"
      ],
      "properties": [
        {
          "name": "diagramRelationId",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "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",
          "bpmn:SignalEventDefinition"
        ]
      }
    },
    {
      "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": "JobPriorized",
      "isAbstract": true,
      "extends": [
        "bpmn:Process",
        "camunda: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": "camunda:errorMessage",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "PotentialStarter",
      "superClass": [
        "Element"
      ],
      "properties": [
        {
          "name": "resourceAssignmentExpression",
          "type": "bpmn:ResourceAssignmentExpression"
        }
      ]
    },
    {
      "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": "modelerTemplateVersion",
          "isAttr": true,
          "type": "Integer"
        }
      ]
    },
    {
      "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": "assignee",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "candidateUsers",
          "isAttr": true,
          "type": "String"
        },
        {
          "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": "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": [
        "camunda:ServiceTaskLike"
      ],
      "properties": [
        {
          "name": "type",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "topic",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "TaskPriorized",
      "extends": [
        "bpmn:Process",
        "camunda: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": [
          "camunda:ServiceTaskLike"
        ]
      },
      "properties": [
        {
          "name": "inputOutput",
          "type": "InputOutput"
        },
        {
          "name": "connectorId",
          "type": "String"
        }
      ]
    },
    {
      "name": "InputOutput",
      "superClass": [
        "Element"
      ],
      "meta": {
        "allowedIn": [
          "bpmn:FlowNode",
          "camunda: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": [
          "camunda:ServiceTaskLike",
          "camunda:ExecutionListener",
          "camunda:TaskListener"
        ]
      },
      "properties": [
        {
          "name": "name",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "expression",
          "type": "String"
        },
        {
          "name": "stringValue",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "string",
          "type": "String"
        }
      ]
    },
    {
      "name": "InputParameter",
      "superClass": [ "InputOutputParameter" ]
    },
    {
      "name": "OutputParameter",
      "superClass": [ "InputOutputParameter" ]
    },
    {
      "name": "Collectable",
      "isAbstract": true,
      "extends": [ "bpmn:MultiInstanceLoopCharacteristics" ],
      "superClass": [ "camunda:AsyncCapable" ],
      "properties": [
        {
          "name": "collection",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "elementVariable",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "FailedJobRetryTimeCycle",
      "superClass": [ "Element" ],
      "meta": {
        "allowedIn": [
          "camunda: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": "id",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "eventDefinitions",
          "type": "bpmn:TimerEventDefinition",
          "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": "variableEvents",
          "isAttr": true,
          "type": "String"
        }
      ]
    }
  ],
  "emumerations": [ ]
}
ruoyi-ui/apps/web-antd/src/package/designer/plugins/descriptor/flowableDescriptor.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1230 @@
{
  "name": "Flowable",
  "uri": "http://flowable.org/bpmn",
  "prefix": "flowable",
  "xml": {
    "tagAlias": "lowerCase"
  },
  "associations": [],
  "types": [
    {
      "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": "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": "FormSupported",
      "isAbstract": true,
      "extends": ["bpmn:StartEvent", "bpmn:UserTask"],
      "properties": [
        {
          "name": "formHandlerClass",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "formKey",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "formType",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "formReadOnly",
          "isAttr": true,
          "type": "Boolean",
          "default": false
        },
        {
          "name": "formInit",
          "isAttr": true,
          "type": "Boolean",
          "default": true
        }
      ]
    },
    {
      "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": "assignee",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "candidateUsers",
          "isAttr": true,
          "type": "String"
        },
        {
          "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": "dataType",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "text",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "deptId",
          "isAttr": true,
          "type": "String"
        }
      ]
    },
    {
      "name": "Assignee",
      "supperClass": "Element",
      "meta": {
        "allowedIn": ["*"]
      },
      "properties": [
        {
          "name": "label",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "viewId",
          "type": "Number",
          "isAttr": true
        }
      ]
    },
    {
      "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": "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": "Button",
      "superClass": ["Element"],
      "meta": {
        "allowedIn": ["bpmn:UserTask"]
      },
      "properties": [
        {
          "name": "id",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "name",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "code",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "isHide",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "next",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "sort",
          "type": "Integer",
          "isAttr": true
        }
      ]
    },
    {
      "name": "Assignee",
      "superClass": ["Element"],
      "meta": {
        "allowedIn": ["bpmn:UserTask"]
      },
      "properties": [
        {
          "name": "id",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "type",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "value",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "condition",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "operationType",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "sort",
          "type": "Integer",
          "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",
          "bpmn:ServiceTask"
        ]
      },
      "properties": [
        {
          "name": "name",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "expression",
          "type": "String"
        },
        {
          "name": "stringValue",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "string",
          "type": "String"
        },
        {
          "name": "htmlVar",
          "type": "Expression"
        }
      ]
    },
    {
      "name": "ChildField",
      "superClass": ["Element"],
      "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": "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": "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": "children",
          "type": "ChildField",
          "isMany": true
        },
        {
          "name": "extensionElements",
          "type": "bpmn:ExtensionElements",
          "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"
        }
      ]
    },
    {
      "name": "Condition",
      "superClass": ["Element"],
      "meta": {
        "allowedIn": ["bpmn:SequenceFlow"]
      },
      "properties": [
        {
          "name": "id",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "field",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "compare",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "value",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "logic",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "sort",
          "type": "Integer",
          "isAttr": true
        }
      ]
    }
  ],
  "emumerations": []
}
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/activiti/activitiExtension.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
'use strict';
import { some } from '#/utils/min-dash.js';
var ALLOWED_TYPES = {
  FailedJobRetryTimeCycle: ['bpmn:StartEvent', 'bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:Activity'],
  Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
  Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent']
};
function is(element, type) {
  return element && typeof element.$instanceOf === 'function' && element.$instanceOf(type);
}
function exists(element) {
  return element && element.length;
}
function includesType(collection, type) {
  return (
    exists(collection) &&
    some(collection, function(element) {
      return is(element, type);
    })
  );
}
function anyType(element, types) {
  return some(types, function(type) {
    return is(element, type);
  });
}
function isAllowed(propName, propDescriptor, newElement) {
  var name = propDescriptor.name;
  var types = ALLOWED_TYPES[name.replace(/activiti:/, '')];
  return name === propName && anyType(newElement, types);
}
export default function ActivitiModdleExtension(eventBus) {
  eventBus.on(
    'property.clone',
    function(context) {
      var newElement = context.newElement;
      var propDescriptor = context.propertyDescriptor;
      this.canCloneProperty(newElement, propDescriptor);
    },
    this
  );
}
ActivitiModdleExtension.$inject = ['eventBus'];
ActivitiModdleExtension.prototype.canCloneProperty = function(newElement, propDescriptor) {
  if (isAllowed('activiti:FailedJobRetryTimeCycle', propDescriptor, newElement)) {
    return (
      includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') ||
      includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') ||
      is(newElement.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')
    );
  }
  if (isAllowed('activiti:Connector', propDescriptor, newElement)) {
    return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition');
  }
  if (isAllowed('activiti:Field', propDescriptor, newElement)) {
    return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition');
  }
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/activiti/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
/*
 * @author igdianov
 * address https://github.com/igdianov/activiti-bpmn-moddle
 * */
import ActivitiModdleExtension from './activitiExtension.js'
export default {
  __init__: ['ActivitiModdleExtension'],
  ActivitiModdleExtension: ['type', ActivitiModdleExtension]
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/camunda/extension.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
'use strict';
import { some, isObject, isFunction } from '#/utils/min-dash.js';
var WILDCARD = '*';
export default function CamundaModdleExtension(eventBus) {
  var self = this;
  eventBus.on('moddleCopy.canCopyProperty', function(context) {
    var property = context.property;
    var parent = context.parent;
    return self.canCopyProperty(property, parent);
  });
}
CamundaModdleExtension.$inject = ['eventBus'];
/**
 * Check wether to disallow copying property.
 */
CamundaModdleExtension.prototype.canCopyProperty = function(property, parent) {
  // (1) check wether property is allowed in parent
  if (isObject(property) && !isAllowedInParent(property, parent)) {
    return false;
  }
  // (2) check more complex scenarios
  if (is(property, 'camunda:InputOutput') && !this.canHostInputOutput(parent)) {
    return false;
  }
  if (isAny(property, ['camunda:Connector', 'camunda:Field']) && !this.canHostConnector(parent)) {
    return false;
  }
  if (is(property, 'camunda:In') && !this.canHostIn(parent)) {
    return false;
  }
};
CamundaModdleExtension.prototype.canHostInputOutput = function(parent) {
  // allowed in camunda:Connector
  var connector = getParent(parent, 'camunda:Connector');
  if (connector) {
    return true;
  }
  // special rules inside bpmn:FlowNode
  var flowNode = getParent(parent, 'bpmn:FlowNode');
  if (!flowNode) {
    return false;
  }
  if (isAny(flowNode, ['bpmn:StartEvent', 'bpmn:Gateway', 'bpmn:BoundaryEvent'])) {
    return false;
  }
  if (is(flowNode, 'bpmn:SubProcess') && flowNode.get('triggeredByEvent')) {
    return false;
  }
  return true;
};
CamundaModdleExtension.prototype.canHostConnector = function(parent) {
  var serviceTaskLike = getParent(parent, 'camunda:ServiceTaskLike');
  if (is(serviceTaskLike, 'bpmn:MessageEventDefinition')) {
    // only allow on throw and end events
    return getParent(parent, 'bpmn:IntermediateThrowEvent') || getParent(parent, 'bpmn:EndEvent');
  }
  return true;
};
CamundaModdleExtension.prototype.canHostIn = function(parent) {
  var callActivity = getParent(parent, 'bpmn:CallActivity');
  if (callActivity) {
    return true;
  }
  var signalEventDefinition = getParent(parent, 'bpmn:SignalEventDefinition');
  if (signalEventDefinition) {
    // only allow on throw and end events
    return getParent(parent, 'bpmn:IntermediateThrowEvent') || getParent(parent, 'bpmn:EndEvent');
  }
  return true;
};
// helpers //////////
function is(element, type) {
  return element && isFunction(element.$instanceOf) && element.$instanceOf(type);
}
function isAny(element, types) {
  return some(types, function(t) {
    return is(element, t);
  });
}
function getParent(element, type) {
  if (!type) {
    return element.$parent;
  }
  if (is(element, type)) {
    return element;
  }
  if (!element.$parent) {
    return;
  }
  return getParent(element.$parent, type);
}
function isAllowedInParent(property, parent) {
  // (1) find property descriptor
  var descriptor = property.$type && property.$model.getTypeDescriptor(property.$type);
  var allowedIn = descriptor && descriptor.meta && descriptor.meta.allowedIn;
  if (!allowedIn || isWildcard(allowedIn)) {
    return true;
  }
  // (2) check wether property has parent of allowed type
  return some(allowedIn, function(type) {
    return getParent(parent, type);
  });
}
function isWildcard(allowedIn) {
  return allowedIn.indexOf(WILDCARD) !== -1;
}
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/camunda/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
'use strict';
import CamundaModdleExtension from './extension.js';
export default {
  __init__: ['CamundaModdleExtension'],
  CamundaModdleExtension: ['type', CamundaModdleExtension]
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/flowable/flowableExtension.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
'use strict';
import { some } from '#/utils/min-dash.js';
var ALLOWED_TYPES = {
  FailedJobRetryTimeCycle: ['bpmn:StartEvent', 'bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:Activity'],
  Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
  Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent']
};
function is(element, type) {
  return element && typeof element.$instanceOf === 'function' && element.$instanceOf(type);
}
function exists(element) {
  return element && element.length;
}
function includesType(collection, type) {
  return (
    exists(collection) &&
    some(collection, function(element) {
      return is(element, type);
    })
  );
}
function anyType(element, types) {
  return some(types, function(type) {
    return is(element, type);
  });
}
function isAllowed(propName, propDescriptor, newElement) {
  var name = propDescriptor.name;
  var types = ALLOWED_TYPES[name.replace(/flowable:/, '')];
  return name === propName && anyType(newElement, types);
}
export default function FlowableModdleExtension(eventBus) {
  eventBus.on(
    'property.clone',
    function(context) {
      var newElement = context.newElement;
      var propDescriptor = context.propertyDescriptor;
      this.canCloneProperty(newElement, propDescriptor);
    },
    this
  );
}
FlowableModdleExtension.$inject = ['eventBus'];
FlowableModdleExtension.prototype.canCloneProperty = function(newElement, propDescriptor) {
  if (isAllowed('flowable:FailedJobRetryTimeCycle', propDescriptor, newElement)) {
    return (
      includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') ||
      includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') ||
      is(newElement.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')
    );
  }
  if (isAllowed('flowable:Connector', propDescriptor, newElement)) {
    return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition');
  }
  if (isAllowed('flowable:Field', propDescriptor, newElement)) {
    return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition');
  }
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/extension-moddle/flowable/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
/*
 * @author igdianov
 * address https://github.com/igdianov/activiti-bpmn-moddle
 * */
import FlowableModdleExtension from './flowableExtension.js'
export default {
  __init__: ['FlowableModdleExtension'],
  FlowableModdleExtension: ['type', FlowableModdleExtension]
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/palette/CustomPalette.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,156 @@
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
import { assign } from '#/utils/min-dash.js';
export default function CustomPalette(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) {
  PaletteProvider.call(this, palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate, 2000);
}
const F = function() {}; // æ ¸å¿ƒï¼Œåˆ©ç”¨ç©ºå¯¹è±¡ä½œä¸ºä¸­ä»‹ï¼›
F.prototype = PaletteProvider.prototype; // æ ¸å¿ƒï¼Œå°†çˆ¶ç±»çš„原型赋值给空对象F;
// åˆ©ç”¨ä¸­ä»‹å‡½æ•°é‡å†™åŽŸåž‹é“¾æ–¹æ³•
F.prototype.getPaletteEntries = function() {
  var actions = {};
  var create = this._create;
  var elementFactory = this._elementFactory;
  var spaceTool = this._spaceTool;
  var lassoTool = this._lassoTool;
  var handTool = this._handTool;
  var globalConnect = this._globalConnect;
  var translate = this._translate;
  function createAction(type, group, className, title, options) {
    function createListener(event) {
      var shape = elementFactory.createShape(assign({ type: type }, options));
      if (options) {
        shape.businessObject.di.isExpanded = options.isExpanded;
      }
      create.start(event, shape);
    }
    var shortType = type.replace(/^bpmn:/, '');
    return {
      group: group,
      className: className,
      title: title || translate('Create {type}', { type: shortType }),
      action: {
        dragstart: createListener,
        click: createListener
      }
    };
  }
  function createSubprocess(event) {
    var subProcess = elementFactory.createShape({
      type: 'bpmn:SubProcess',
      x: 0,
      y: 0,
      isExpanded: true
    });
    var startEvent = elementFactory.createShape({
      type: 'bpmn:StartEvent',
      x: 40,
      y: 82,
      parent: subProcess
    });
    create.start(event, [subProcess, startEvent], {
      hints: {
        autoSelect: [startEvent]
      }
    });
  }
  function createParticipant(event) {
    create.start(event, elementFactory.createParticipantShape());
  }
  assign(actions, {
    'hand-tool': {
      group: 'tools',
      className: 'bpmn-icon-hand-tool',
      title: translate('Activate the hand tool'),
      action: {
        click: function(event) {
          handTool.activateHand(event);
        }
      }
    },
    'lasso-tool': {
      group: 'tools',
      className: 'bpmn-icon-lasso-tool',
      title: translate('Activate the lasso tool'),
      action: {
        click: function(event) {
          lassoTool.activateSelection(event);
        }
      }
    },
    'space-tool': {
      group: 'tools',
      className: 'bpmn-icon-space-tool',
      title: translate('Activate the create/remove space tool'),
      action: {
        click: function(event) {
          spaceTool.activateSelection(event);
        }
      }
    },
    'global-connect-tool': {
      group: 'tools',
      className: 'bpmn-icon-connection-multi',
      title: translate('Activate the global connect tool'),
      action: {
        click: function(event) {
          globalConnect.toggle(event);
        }
      }
    },
    'tool-separator': {
      group: 'tools',
      separator: true
    },
    'create.start-event': createAction('bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none', translate('Create StartEvent')),
    'create.intermediate-event': createAction(
      'bpmn:IntermediateThrowEvent',
      'event',
      'bpmn-icon-intermediate-event-none',
      translate('Create Intermediate/Boundary Event')
    ),
    'create.end-event': createAction('bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none', translate('Create EndEvent')),
    'create.exclusive-gateway': createAction('bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none', translate('Create Gateway')),
    'create.user-task': createAction('bpmn:UserTask', 'activity', 'bpmn-icon-user-task', translate('Create User Task')),
    'create.data-object': createAction('bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object', translate('Create DataObjectReference')),
    'create.data-store': createAction('bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store', translate('Create DataStoreReference')),
    'create.subprocess-expanded': {
      group: 'activity',
      className: 'bpmn-icon-subprocess-expanded',
      title: translate('Create expanded SubProcess'),
      action: {
        dragstart: createSubprocess,
        click: createSubprocess
      }
    },
    'create.participant-expanded': {
      group: 'collaboration',
      className: 'bpmn-icon-participant',
      title: translate('Create Pool/Participant'),
      action: {
        dragstart: createParticipant,
        click: createParticipant
      }
    },
    'create.group': createAction('bpmn:Group', 'artifact', 'bpmn-icon-group', translate('Create Group'))
  });
  return actions;
};
CustomPalette.$inject = ['palette', 'create', 'elementFactory', 'spaceTool', 'lassoTool', 'handTool', 'globalConnect', 'translate'];
CustomPalette.prototype = new F(); // æ ¸å¿ƒï¼Œå°† F的实例赋值给子类;
CustomPalette.prototype.constructor = CustomPalette; // ä¿®å¤å­ç±»CustomPalette的构造器指向,防止原型链的混乱;
ruoyi-ui/apps/web-antd/src/package/designer/plugins/palette/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
import CustomPalette from './CustomPalette';
export default {
  __init__: ['customPalette'],
  customPalette: ['type', CustomPalette]
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/palette/paletteProvider.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
import { assign } from '#/utils/min-dash.js';
/**
 * A palette provider for BPMN 2.0 elements.
 */
export default function PaletteProvider(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) {
  this._palette = palette;
  this._create = create;
  this._elementFactory = elementFactory;
  this._spaceTool = spaceTool;
  this._lassoTool = lassoTool;
  this._handTool = handTool;
  this._globalConnect = globalConnect;
  this._translate = translate;
  palette.registerProvider(this);
}
PaletteProvider.$inject = ['palette', 'create', 'elementFactory', 'spaceTool', 'lassoTool', 'handTool', 'globalConnect', 'translate'];
PaletteProvider.prototype.getPaletteEntries = function() {
  var actions = {};
  var create = this._create;
  var elementFactory = this._elementFactory;
  var spaceTool = this._spaceTool;
  var lassoTool = this._lassoTool;
  var handTool = this._handTool;
  var globalConnect = this._globalConnect;
  var translate = this._translate;
  function createAction(type, group, className, title, options) {
    function createListener(event) {
      var shape = elementFactory.createShape(assign({ type: type }, options));
      if (options) {
        shape.businessObject.di.isExpanded = options.isExpanded;
      }
      create.start(event, shape);
    }
    var shortType = type.replace(/^bpmn:/, '');
    return {
      group: group,
      className: className,
      title: title || translate('Create {type}', { type: shortType }),
      action: {
        dragstart: createListener,
        click: createListener
      }
    };
  }
  function createSubprocess(event) {
    var subProcess = elementFactory.createShape({
      type: 'bpmn:SubProcess',
      x: 0,
      y: 0,
      isExpanded: true
    });
    var startEvent = elementFactory.createShape({
      type: 'bpmn:StartEvent',
      x: 40,
      y: 82,
      parent: subProcess
    });
    create.start(event, [subProcess, startEvent], {
      hints: {
        autoSelect: [startEvent]
      }
    });
  }
  function createParticipant(event) {
    create.start(event, elementFactory.createParticipantShape());
  }
  assign(actions, {
    'hand-tool': {
      group: 'tools',
      className: 'bpmn-icon-hand-tool',
      title: translate('Activate the hand tool'),
      action: {
        click: function(event) {
          handTool.activateHand(event);
        }
      }
    },
    'lasso-tool': {
      group: 'tools',
      className: 'bpmn-icon-lasso-tool',
      title: translate('Activate the lasso tool'),
      action: {
        click: function(event) {
          lassoTool.activateSelection(event);
        }
      }
    },
    'space-tool': {
      group: 'tools',
      className: 'bpmn-icon-space-tool',
      title: translate('Activate the create/remove space tool'),
      action: {
        click: function(event) {
          spaceTool.activateSelection(event);
        }
      }
    },
    'global-connect-tool': {
      group: 'tools',
      className: 'bpmn-icon-connection-multi',
      title: translate('Activate the global connect tool'),
      action: {
        click: function(event) {
          globalConnect.toggle(event);
        }
      }
    },
    'tool-separator': {
      group: 'tools',
      separator: true
    },
    'create.start-event': createAction('bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none', translate('Create StartEvent')),
    'create.intermediate-event': createAction(
      'bpmn:IntermediateThrowEvent',
      'event',
      'bpmn-icon-intermediate-event-none',
      translate('Create Intermediate/Boundary Event')
    ),
    'create.end-event': createAction('bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none', translate('Create EndEvent')),
    'create.exclusive-gateway': createAction('bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none', translate('Create Gateway')),
    'create.user-task': createAction('bpmn:UserTask', 'activity', 'bpmn-icon-user-task', translate('Create User Task')),
    'create.data-object': createAction('bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object', translate('Create DataObjectReference')),
    'create.data-store': createAction('bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store', translate('Create DataStoreReference')),
    'create.subprocess-expanded': {
      group: 'activity',
      className: 'bpmn-icon-subprocess-expanded',
      title: translate('Create expanded SubProcess'),
      action: {
        dragstart: createSubprocess,
        click: createSubprocess
      }
    },
    'create.participant-expanded': {
      group: 'collaboration',
      className: 'bpmn-icon-participant',
      title: translate('Create Pool/Participant'),
      action: {
        dragstart: createParticipant,
        click: createParticipant
      }
    },
    'create.group': createAction('bpmn:Group', 'artifact', 'bpmn-icon-group', translate('Create Group'))
  });
  return actions;
};
ruoyi-ui/apps/web-antd/src/package/designer/plugins/translate/customTranslate.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
// import translations from "./zh";
//
// export default function customTranslate(template, replacements) {
//   replacements = replacements || {};
//
//   // Translate
//   template = translations[template] || template;
//
//   // Replace
//   return template.replace(/{([^}]+)}/g, function(_, key) {
//     let str = replacements[key];
//     if (
//       translations[replacements[key]] !== null &&
//       translations[replacements[key]] !== "undefined"
//     ) {
//       // eslint-disable-next-line no-mixed-spaces-and-tabs
//       str = translations[replacements[key]];
//       // eslint-disable-next-line no-mixed-spaces-and-tabs
//     }
//     return str || "{" + key + "}";
//   });
// }
export default function customTranslate(translations) {
  return function(template, replacements) {
    replacements = replacements || {};
    // Translate
    template = translations[template] || template;
    // Replace
    return template.replace(/{([^}]+)}/g, function(_, key) {
      let str = replacements[key];
      if (translations[replacements[key]] !== null && translations[replacements[key]] !== undefined) {
        // eslint-disable-next-line no-mixed-spaces-and-tabs
        str = translations[replacements[key]];
        // eslint-disable-next-line no-mixed-spaces-and-tabs
      }
      return str || '{' + key + '}';
    });
  };
}
ruoyi-ui/apps/web-antd/src/package/designer/plugins/translate/zh.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,238 @@
/**
 * This is a sample file that should be replaced with the actual translation.
 *
 * Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available
 * translations and labels to translate.
 */
export default {
  // æ·»åŠ éƒ¨åˆ†
  'Append EndEvent': '追加结束事件',
  'Append Gateway': '追加网关',
  'Append Task': '追加任务',
  'Append Intermediate/Boundary Event': '追加中间抛出事件/边界事件',
  'Activate the global connect tool': '激活全局连接工具',
  'Append {type}': '添加 {type}',
  'Add Lane above': '在上面添加道',
  'Divide into two Lanes': '分割成两个道',
  'Divide into three Lanes': '分割成三个道',
  'Add Lane below': '在下面添加道',
  'Append compensation activity': '追加补偿活动',
  'Change type': '修改类型',
  'Connect using Association': '使用关联连接',
  'Connect using Sequence/MessageFlow or Association': '使用顺序/消息流或者关联连接',
  'Connect using DataInputAssociation': '使用数据输入关联连接',
  Remove: '移除',
  'Activate the hand tool': '激活抓手工具',
  'Activate the lasso tool': '激活套索工具',
  'Activate the create/remove space tool': '激活创建/删除空间工具',
  'Create expanded SubProcess': '创建扩展子过程',
  'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件',
  'Create Pool/Participant': '创建池/参与者',
  'Parallel Multi Instance': '并行多重事件',
  'Sequential Multi Instance': '时序多重事件',
  DataObjectReference: '数据对象参考',
  DataStoreReference: '数据存储参考',
  Loop: '循环',
  'Ad-hoc': '即席',
  'Create {type}': '创建 {type}',
  Task: '任务',
  'Send Task': '发送任务',
  'Receive Task': '接收任务',
  'User Task': '用户任务',
  'Manual Task': '手工任务',
  'Business Rule Task': '业务规则任务',
  'Service Task': '服务任务',
  'Script Task': '脚本任务',
  'Call Activity': '调用活动',
  'Sub Process (collapsed)': '子流程(折叠的)',
  'Sub Process (expanded)': '子流程(展开的)',
  'Start Event': '开始事件',
  StartEvent: '开始事件',
  'Intermediate Throw Event': '中间事件',
  'End Event': '结束事件',
  EndEvent: '结束事件',
  'Create StartEvent': '创建开始事件',
  'Create EndEvent': '创建结束事件',
  'Create Task': '创建任务',
  'Create User Task': '创建用户任务',
  'Create Gateway': '创建网关',
  'Create DataObjectReference': '创建数据对象',
  'Create DataStoreReference': '创建数据存储',
  'Create Group': '创建分组',
  'Create Intermediate/Boundary Event': '创建中间/边界事件',
  'Message Start Event': '消息开始事件',
  'Timer Start Event': '定时开始事件',
  'Conditional Start Event': '条件开始事件',
  'Signal Start Event': '信号开始事件',
  'Error Start Event': '错误开始事件',
  'Escalation Start Event': '升级开始事件',
  'Compensation Start Event': '补偿开始事件',
  'Message Start Event (non-interrupting)': '消息开始事件(非中断)',
  'Timer Start Event (non-interrupting)': '定时开始事件(非中断)',
  'Conditional Start Event (non-interrupting)': '条件开始事件(非中断)',
  'Signal Start Event (non-interrupting)': '信号开始事件(非中断)',
  'Escalation Start Event (non-interrupting)': '升级开始事件(非中断)',
  'Message Intermediate Catch Event': '消息中间捕获事件',
  'Message Intermediate Throw Event': '消息中间抛出事件',
  'Timer Intermediate Catch Event': '定时中间捕获事件',
  'Escalation Intermediate Throw Event': '升级中间抛出事件',
  'Conditional Intermediate Catch Event': '条件中间捕获事件',
  'Link Intermediate Catch Event': '链接中间捕获事件',
  'Link Intermediate Throw Event': '链接中间抛出事件',
  'Compensation Intermediate Throw Event': '补偿中间抛出事件',
  'Signal Intermediate Catch Event': '信号中间捕获事件',
  'Signal Intermediate Throw Event': '信号中间抛出事件',
  'Message End Event': '消息结束事件',
  'Escalation End Event': '定时结束事件',
  'Error End Event': '错误结束事件',
  'Cancel End Event': '取消结束事件',
  'Compensation End Event': '补偿结束事件',
  'Signal End Event': '信号结束事件',
  'Terminate End Event': '终止结束事件',
  'Message Boundary Event': '消息边界事件',
  'Message Boundary Event (non-interrupting)': '消息边界事件(非中断)',
  'Timer Boundary Event': '定时边界事件',
  'Timer Boundary Event (non-interrupting)': '定时边界事件(非中断)',
  'Escalation Boundary Event': '升级边界事件',
  'Escalation Boundary Event (non-interrupting)': '升级边界事件(非中断)',
  'Conditional Boundary Event': '条件边界事件',
  'Conditional Boundary Event (non-interrupting)': '条件边界事件(非中断)',
  'Error Boundary Event': '错误边界事件',
  'Cancel Boundary Event': '取消边界事件',
  'Signal Boundary Event': '信号边界事件',
  'Signal Boundary Event (non-interrupting)': '信号边界事件(非中断)',
  'Compensation Boundary Event': '补偿边界事件',
  'Exclusive Gateway': '互斥网关',
  'Parallel Gateway': '并行网关',
  'Inclusive Gateway': '相容网关',
  'Complex Gateway': '复杂网关',
  'Event based Gateway': '事件网关',
  Transaction: '转运',
  'Sub Process': '子流程',
  'Event Sub Process': '事件子流程',
  'Collapsed Pool': '折叠池',
  'Expanded Pool': '展开池',
  // Errors
  'no parent for {element} in {parent}': '在{parent}里,{element}没有父类',
  'no shape type specified': '没有指定的形状类型',
  'flow elements must be children of pools/participants': '流元素必须是池/参与者的子类',
  'out of bounds release': 'out of bounds release',
  'more than {count} child lanes': '子道大于{count} ',
  'element required': '元素不能为空',
  'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范',
  'no diagram to display': '没有可展示的流程图',
  'no process or collaboration to display': '没有可展示的流程/协作',
  'element {element} referenced by {referenced}#{property} not yet drawn': '由{referenced}#{property}引用的{element}元素仍未绘制',
  'already rendered {element}': '{element} å·²è¢«æ¸²æŸ“',
  'failed to import {element}': '导入{element}失败',
  // å±žæ€§é¢æ¿çš„参数
  Id: '编号',
  Name: '名称',
  General: '常规',
  Details: '详情',
  'Message Name': '消息名称',
  Message: '消息',
  Initiator: '创建者',
  'Asynchronous Continuations': '持续异步',
  'Asynchronous Before': '异步前',
  'Asynchronous After': '异步后',
  'Job Configuration': '工作配置',
  Exclusive: '排除',
  'Job Priority': '工作优先级',
  'Retry Time Cycle': '重试时间周期',
  Documentation: '文档',
  'Element Documentation': '元素文档',
  'History Configuration': '历史配置',
  'History Time To Live': '历史的生存时间',
  Forms: '表单',
  'Form Key': '表单key',
  'Form Fields': '表单字段',
  'Business Key': '业务key',
  'Form Field': '表单字段',
  ID: '编号',
  Type: '类型',
  Label: '名称',
  'Default Value': '默认值',
  'Default Flow': '默认流转路径',
  'Conditional Flow': '条件流转路径',
  'Sequence Flow': '普通流转路径',
  Validation: '校验',
  'Add Constraint': '添加约束',
  Config: '配置',
  Properties: '属性',
  'Add Property': '添加属性',
  Value: '值',
  Listeners: '监听器',
  'Execution Listener': '执行监听',
  'Event Type': '事件类型',
  'Listener Type': '监听器类型',
  'Java Class': 'Javaç±»',
  Expression: '表达式',
  'Must provide a value': '必须提供一个值',
  'Delegate Expression': '代理表达式',
  Script: '脚本',
  'Script Format': '脚本格式',
  'Script Type': '脚本类型',
  'Inline Script': '内联脚本',
  'External Script': '外部脚本',
  Resource: '资源',
  'Field Injection': '字段注入',
  Extensions: '扩展',
  'Input/Output': '输入/输出',
  'Input Parameters': '输入参数',
  'Output Parameters': '输出参数',
  Parameters: '参数',
  'Output Parameter': '输出参数',
  'Timer Definition Type': '定时器定义类型',
  'Timer Definition': '定时器定义',
  Date: '日期',
  Duration: '持续',
  Cycle: '循环',
  Signal: '信号',
  'Signal Name': '信号名称',
  Escalation: '升级',
  Error: '错误',
  'Link Name': '链接名称',
  Condition: '条件名称',
  'Variable Name': '变量名称',
  'Variable Event': '变量事件',
  'Specify more than one variable change event as a comma separated list.': '多个变量事件以逗号隔开',
  'Wait for Completion': '等待完成',
  'Activity Ref': '活动参考',
  'Version Tag': '版本标签',
  Executable: '可执行文件',
  'External Task Configuration': '扩展任务配置',
  'Task Priority': '任务优先级',
  External: '外部',
  Connector: '连接器',
  'Must configure Connector': '必须配置连接器',
  'Connector Id': '连接器编号',
  Implementation: '实现方式',
  'Field Injections': '字段注入',
  Fields: '字段',
  'Result Variable': '结果变量',
  Topic: '主题',
  'Configure Connector': '配置连接器',
  'Input Parameter': '输入参数',
  Assignee: '代理人',
  'Candidate Users': '候选用户',
  'Candidate Groups': '候选组',
  'Due Date': '到期时间',
  'Follow Up Date': '跟踪日期',
  Priority: '优先级',
  'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
    '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
  'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
    '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
  Variables: '变量',
  'Candidate Starter Configuration': '候选人起动器配置',
  'Candidate Starter Groups': '候选人起动器组',
  'This maps to the process definition key.': '这映射到流程定义键。',
  'Candidate Starter Users': '候选人起动器的用户',
  'Specify more than one user as a comma separated list.': '指定多个用户作为逗号分隔的列表。',
  'Tasklist Configuration': 'Tasklist配置',
  Startable: '启动',
  'Specify more than one group as a comma separated list.': '指定多个组作为逗号分隔的列表。'
};
ruoyi-ui/apps/web-antd/src/package/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
import MyProcessDesigner from './designer';
import MyProcessPalette from './palette';
import MyProcessPenal from './penal';
const components = [MyProcessDesigner, MyProcessPenal, MyProcessPalette];
const install = function(Vue) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}
export default {
  version: '0.0.1',
  install,
  ...components
};
ruoyi-ui/apps/web-antd/src/package/palette/ProcessPalette.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<template>
  <div class="my-process-palette">
    <p>简易palette</p>
    <el-collapse>
      <el-collapse-item title="任务" name="1">
        <!--  å¯ä»¥ç®€åŒ–。。。 -->
        <div class="custom-button" @click="createElement($event, 'Task')" @mousedown="createElement($event, 'Task')">
          ä»»åŠ¡
        </div>
        <div class="custom-button" @click="createElement($event, 'UserTask')" @mousedown="createElement($event, 'UserTask')">
          ç”¨æˆ·ä»»åŠ¡
        </div>
        <div class="custom-button" @click="createElement($event, 'SendTask')" @mousedown="createElement($event, 'SendTask')">
          å‘送任务
        </div>
        <div class="custom-button" @click="createElement($event, 'ReceiveTask')" @mousedown="createElement($event, 'ReceiveTask')">
          æŽ¥æ”¶ä»»åŠ¡
        </div>
        <div class="custom-button" @click="createElement($event, 'ScriptTask')" @mousedown="createElement($event, 'ScriptTask')">
          è„šæœ¬ä»»åŠ¡
        </div>
        <div class="custom-button" @click="createElement($event, 'ServiceTask')" @mousedown="createElement($event, 'ServiceTask')">
          æœåŠ¡ä»»åŠ¡
        </div>
      </el-collapse-item>
      <el-collapse-item title="网关" name="2">
        <div class="custom-button" @click="createElement($event, 'Gateway')" @mousedown="createElement($event, 'Gateway')">
          ç½‘å…³
        </div>
      </el-collapse-item>
      <el-collapse-item title="开始" name="3">
        <div class="custom-button" @click="createElement($event, 'StartEvent')" @mousedown="createElement($event, 'StartEvent')">
          å¼€å§‹
        </div>
      </el-collapse-item>
      <el-collapse-item title="结束" name="4">
        <div class="custom-button" @click="createElement($event, 'EndEvent')" @mousedown="createElement($event, 'EndEvent')">
          ç»“束
        </div>
      </el-collapse-item>
      <el-collapse-item title="工具" name="5">
        <div class="custom-button" @click="startTool($event, 'handTool')" @mousedown="startTool($event, 'handTool')">
          æ‰‹åž‹å·¥å…·
        </div>
        <div class="custom-button" @click="startTool($event, 'lassoTool')" @mousedown="startTool($event, 'lassoTool')">
          æ¡†é€‰å·¥å…·
        </div>
        <div class="custom-button" @click="startTool($event, 'connectTool')" @mousedown="startTool($event, 'connectTool')">
          è¿žçº¿å·¥å…·
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script>
import { assign } from 'min-dash';
export default {
  name: 'MyProcessPalette',
  data() {
    return {};
  },
  mounted() {},
  methods: {
    createElement(event, type, options = {}) {
      const ElementFactory = window.bpmnInstances.elementFactory;
      const create = window.bpmnInstances.modeler.get('create');
      const shape = ElementFactory.createShape(assign({ type: `bpmn:${type}` }, options));
      if (options) {
        shape.businessObject.di.isExpanded = options.isExpanded;
      }
      create.start(event, shape);
    },
    startTool(event, type) {
      if (type === 'handTool') {
        window.bpmnInstances.modeler.get('handTool').activateHand(event);
      }
      if (type === 'lassoTool') {
        window.bpmnInstances.modeler.get('lassoTool').activateSelection(event);
      }
      if (type === 'connectTool') {
        window.bpmnInstances.modeler.get('globalConnect').toggle(event);
      }
    }
  }
};
</script>
<style scoped lang="scss">
.my-process-palette {
  box-sizing: border-box;
  padding: 8px;
  .custom-button {
    box-sizing: border-box;
    padding: 4px 8px;
    border-radius: 4px;
    border: 1px solid rgba(24, 144, 255, 0.8);
    cursor: pointer;
    margin-bottom: 8px;
    &:first-child {
      margin-top: 8px;
    }
  }
}
</style>
ruoyi-ui/apps/web-antd/src/package/palette/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
import MyPropertiesPalette from './ProcessPalette.vue';
MyPropertiesPalette.install = function(Vue) {
  Vue.component(MyPropertiesPalette.name, MyPropertiesPalette);
};
export default MyPropertiesPalette;
ruoyi-ui/apps/web-antd/src/package/penal/PropertiesPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,216 @@
<template>
  <div class="process-panel__container" :style="{ width: `${this.width}px` }">
    <el-collapse v-model="activeTab">
      <el-collapse-item name="base">
        <template #title>
          <div class="panel-tab__title"><el-icon><info-filled /></el-icon>常规</div>
        </template>
        <element-base-info :id-edit-disabled="idEditDisabled" :business-object="elementBusinessObject" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="condition" v-if="elementType === 'Process'" key="message">
        <template #title>
          <div class="panel-tab__title"><el-icon><comment /></el-icon>消息与信号</div>
        </template>
        <signal-and-massage />
      </el-collapse-item>
      <el-collapse-item name="condition" v-if="conditionFormVisible" key="condition">
        <template #title>
          <div class="panel-tab__title"><el-icon><promotion /></el-icon>流转条件</div>
        </template>
        <flow-condition :business-object="elementBusinessObject" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="condition" v-if="formVisible" key="form">
        <template #title>
          <div class="panel-tab__title"><el-icon><list /></el-icon>表单</div>
        </template>
        <element-form :id="elementId" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="task" v-if="elementType.indexOf('Task') !== -1" key="task">
        <template #title>
          <div class="panel-tab__title"><el-icon><checked /></el-icon>任务</div>
        </template>
        <element-task :id="elementId" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="multiInstance" v-if="elementType.indexOf('Task') !== -1" key="multiInstance">
        <template #title>
          <div class="panel-tab__title"><el-icon><help-filled /></el-icon>多实例</div>
        </template>
        <element-multi-instance :business-object="elementBusinessObject" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="taskListeners" v-if="elementType === 'UserTask'" key="taskListeners">
        <template #title>
          <div class="panel-tab__title"><el-icon><bell-filled /></el-icon>任务监听器</div>
        </template>
        <user-task-listeners :id="elementId" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="listeners" key="listeners">
        <template #title>
          <div class="panel-tab__title"><el-icon><bell-filled /></el-icon>执行监听器</div>
        </template>
        <element-listeners :id="elementId" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="extensions" key="extensions">
        <template #title>
          <div class="panel-tab__title"><el-icon><circle-plus /></el-icon>扩展属性</div>
        </template>
        <element-properties :id="elementId" :type="elementType" />
      </el-collapse-item>
      <el-collapse-item name="other" key="other">
        <template #title>
          <div class="panel-tab__title"><el-icon><promotion /></el-icon>其他</div>
        </template>
        <element-other-config :id="elementId" />
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script>
import ElementBaseInfo from "./base/ElementBaseInfo.vue";
import ElementOtherConfig from "./other/ElementOtherConfig.vue";
import ElementTask from "./task/ElementTask.vue";
import ElementMultiInstance from "./multi-instance/ElementMultiInstance.vue";
import FlowCondition from "./flow-condition/FlowCondition.vue";
import SignalAndMassage from "./signal-message/SignalAndMessage.vue";
import ElementListeners from "./listeners/ElementListeners.vue";
import ElementProperties from "./properties/ElementProperties.vue";
import ElementForm from "./form/ElementForm.vue";
import UserTaskListeners from "./listeners/UserTaskListeners.vue";
import Log from "../Log";
/**
 * ä¾§è¾¹æ 
 * @Author MiyueFE
import ElementProperties from "./properties/ElementProperties";
import ElementForm from "./form/ElementForm";
import UserTaskListeners from "./listeners/UserTaskListeners";
import Log from "../Log";
/**
 * ä¾§è¾¹æ 
 * @Author MiyueFE
 * @Home https://github.com/miyuesc
 * @Date 2021å¹´3月31日18:57:51
 */
export default {
  name: "MyPropertiesPanel",
  components: {
    UserTaskListeners,
    ElementForm,
    ElementProperties,
    ElementListeners,
    SignalAndMassage,
    FlowCondition,
    ElementMultiInstance,
    ElementTask,
    ElementOtherConfig,
    ElementBaseInfo
  },
  componentName: "MyPropertiesPanel",
  props: {
    bpmnModeler: Object,
    prefix: {
      type: String,
      default: "camunda"
    },
    width: {
      type: Number,
      default: 480
    },
    idEditDisabled: {
      type: Boolean,
      default: false
    }
  },
  provide() {
    return {
      prefix: this.prefix,
      width: this.width
    };
  },
  data() {
    return {
      activeTab: "base",
      elementId: "",
      elementType: "",
      elementBusinessObject: {}, // å…ƒç´  businessObject é•œåƒï¼Œæä¾›ç»™éœ€è¦åšåˆ¤æ–­çš„组件使用
      conditionFormVisible: false, // æµè½¬æ¡ä»¶è®¾ç½®
      formVisible: false // è¡¨å•配置
    };
  },
  watch: {
    elementId: {
      handler() {
        this.activeTab = "base";
      }
    }
  },
  created() {
    this.initModels();
  },
  methods: {
    initModels() {
      // åˆå§‹åŒ– modeler ä»¥åŠå…¶ä»– moddle
      if (!this.bpmnModeler) {
        // é¿å…åŠ è½½æ—¶ æµç¨‹å›¾ å¹¶æœªåŠ è½½å®Œæˆ
        this.timer = setTimeout(() => this.initModels(), 10);
        return;
      }
      if (this.timer) clearTimeout(this.timer);
      window.bpmnInstances = {
        modeler: this.bpmnModeler,
        modeling: this.bpmnModeler.get("modeling"),
        moddle: this.bpmnModeler.get("moddle"),
        eventBus: this.bpmnModeler.get("eventBus"),
        bpmnFactory: this.bpmnModeler.get("bpmnFactory"),
        elementFactory: this.bpmnModeler.get("elementFactory"),
        elementRegistry: this.bpmnModeler.get("elementRegistry"),
        replace: this.bpmnModeler.get("replace"),
        selection: this.bpmnModeler.get("selection")
      };
      this.getActiveElement();
    },
    getActiveElement() {
      // åˆå§‹ç¬¬ä¸€ä¸ªé€‰ä¸­å…ƒç´  bpmn:Process
      this.initFormOnChanged(null);
      this.bpmnModeler.on("import.done", e => {
        this.initFormOnChanged(null);
      });
      // ç›‘听选择事件,修改当前激活的元素以及表单
      this.bpmnModeler.on("selection.changed", ({ newSelection }) => {
        this.initFormOnChanged(newSelection[0] || null);
      });
      this.bpmnModeler.on("element.changed", ({ element }) => {
        // ä¿è¯ ä¿®æ”¹ "默认流转路径" ç±»ä¼¼éœ€è¦ä¿®æ”¹å¤šä¸ªå…ƒç´ çš„事件发生的时候,更新表单的元素与原选中元素不一致。
        if (element && element.id === this.elementId) {
          this.initFormOnChanged(element);
        }
      });
    },
    // åˆå§‹åŒ–数据
    initFormOnChanged(element) {
      let activatedElement = element;
      if (!activatedElement) {
        activatedElement =
          window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process") ??
          window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Collaboration");
      }
      if (!activatedElement) return;
      Log.printBack(`select element changed: id: ${activatedElement.id} , type: ${activatedElement.businessObject.$type}`);
      Log.prettyInfo("businessObject", activatedElement.businessObject);
      window.bpmnInstances.bpmnElement = activatedElement;
      this.bpmnElement = activatedElement;
      this.elementId = activatedElement.id;
      this.elementType = activatedElement.type.split(":")[1] || "";
      this.elementBusinessObject = JSON.parse(JSON.stringify(activatedElement.businessObject));
      this.conditionFormVisible = !!(
        this.elementType === "SequenceFlow" &&
        activatedElement.source &&
        activatedElement.source.type.indexOf("StartEvent") === -1
      );
      this.formVisible = this.elementType === "UserTask" || this.elementType === "StartEvent";
    },
    beforeUnmount() {
      window.bpmnInstances = null;
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/base/ElementBaseInfo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
<template>
  <div class="panel-tab__content">
    <el-form size="small" label-width="90px" @submit.prevent>
      <el-form-item label="ID">
        <el-input v-model="elementBaseInfo.id" :disabled="idEditDisabled" clearable @change="updateBaseInfo('id')" />
      </el-form-item>
      <el-form-item label="名称">
        <el-input v-model="elementBaseInfo.name" clearable @change="updateBaseInfo('name')" />
      </el-form-item>
      <!--流程的基础属性-->
      <template v-if="elementBaseInfo.$type === 'bpmn:Process'">
        <el-form-item label="版本标签">
          <el-input v-model="elementBaseInfo.versionTag" clearable @change="updateBaseInfo('versionTag')" />
        </el-form-item>
        <el-form-item label="可执行">
          <el-switch v-model="elementBaseInfo.isExecutable" active-text="是" inactive-text="否" @change="updateBaseInfo('isExecutable')" />
        </el-form-item>
      </template>
      <el-form-item v-if="elementBaseInfo.$type === 'bpmn:SubProcess'" label="状态">
        <el-switch v-model="elementBaseInfo.isExpanded" active-text="展开" inactive-text="折叠" @change="updateBaseInfo('isExpanded')" />
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  name: "ElementBaseInfo",
  props: {
    businessObject: Object,
    type: String,
    idEditDisabled: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      elementBaseInfo: {}
    };
  },
  watch: {
    businessObject: {
      immediate: false,
      handler: function(val) {
        if (val) {
          this.$nextTick(() => this.resetBaseInfo());
        }
      }
    }
  },
  methods: {
    resetBaseInfo() {
      this.bpmnElement = window?.bpmnInstances?.bpmnElement || {};
      this.elementBaseInfo = JSON.parse(JSON.stringify(this.bpmnElement.businessObject));
      if (this.elementBaseInfo && this.elementBaseInfo.$type === "bpmn:SubProcess") {
        this.elementBaseInfo["isExpanded"] = this.elementBaseInfo.di?.isExpanded
      }
    },
    updateBaseInfo(key) {
      if (key === "id") {
        window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
          id: this.elementBaseInfo[key],
          di: { id: `${this.elementBaseInfo[key]}_di` }
        });
        return;
      }
      if (key === "isExpanded") {
        window?.bpmnInstances?.modeling.toggleCollapse(this.bpmnElement);
        return;
      }
      const attrObj = Object.create(null);
      attrObj[key] = this.elementBaseInfo[key];
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, attrObj);
    }
  },
  beforeUnmount() {
    this.bpmnElement = null;
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/flow-condition/FlowCondition.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,142 @@
<template>
  <div class="panel-tab__content">
    <el-form :model="flowConditionForm" label-width="90px" size="small" @submit.prevent>
      <el-form-item label="流转类型">
        <el-select v-model="flowConditionForm.type" @change="updateFlowType">
          <el-option label="普通流转路径" value="normal" />
          <el-option label="默认流转路径" value="default" />
          <el-option label="条件流转路径" value="condition" />
        </el-select>
      </el-form-item>
      <el-form-item label="条件格式" v-if="flowConditionForm.type === 'condition'" key="condition">
        <el-select v-model="flowConditionForm.conditionType">
          <el-option label="表达式" value="expression" />
          <el-option label="脚本" value="script" />
        </el-select>
      </el-form-item>
      <el-form-item label="表达式" v-if="flowConditionForm.conditionType && flowConditionForm.conditionType === 'expression'" key="express">
        <el-input v-model="flowConditionForm.body" clearable @change="updateFlowCondition" />
      </el-form-item>
      <template v-if="flowConditionForm.conditionType && flowConditionForm.conditionType === 'script'">
        <el-form-item label="脚本语言" key="language">
          <el-input v-model="flowConditionForm.language" clearable @change="updateFlowCondition" />
        </el-form-item>
        <el-form-item label="脚本类型" key="scriptType">
          <el-select v-model="flowConditionForm.scriptType">
            <el-option label="内联脚本" value="inlineScript" />
            <el-option label="外部脚本" value="externalScript" />
          </el-select>
        </el-form-item>
        <el-form-item label="脚本" v-if="flowConditionForm.scriptType === 'inlineScript'" key="body">
          <el-input v-model="flowConditionForm.body" type="textarea" clearable @change="updateFlowCondition" />
        </el-form-item>
        <el-form-item label="资源地址" v-if="flowConditionForm.scriptType === 'externalScript'" key="resource">
          <el-input v-model="flowConditionForm.resource" clearable @change="updateFlowCondition" />
        </el-form-item>
      </template>
    </el-form>
  </div>
</template>
<script>
export default {
  name: 'FlowCondition',
  props: {
    businessObject: Object,
    type: String
  },
  data() {
    return {
      flowConditionForm: {}
    };
  },
  watch: {
    businessObject: {
      immediate: true,
      handler() {
        this.$nextTick(() => this.resetFlowCondition());
      }
    }
  },
  methods: {
    resetFlowCondition() {
      this.bpmnElement = window.bpmnInstances.bpmnElement;
      this.bpmnElementSource = this.bpmnElement.source;
      this.bpmnElementSourceRef = this.bpmnElement.businessObject.sourceRef;
      if (this.bpmnElementSourceRef && this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.bpmnElement.id) {
        // é»˜è®¤
        this.flowConditionForm = { type: 'default' };
      } else if (!this.bpmnElement.businessObject.conditionExpression) {
        // æ™®é€š
        this.flowConditionForm = { type: 'normal' };
      } else {
        // å¸¦æ¡ä»¶
        const conditionExpression = this.bpmnElement.businessObject.conditionExpression;
        this.flowConditionForm = { ...conditionExpression, type: 'condition' };
        // resource å¯ç›´æŽ¥æ ‡è¯† æ˜¯å¦æ˜¯å¤–部资源脚本
        if (this.flowConditionForm.resource) {
          this.flowConditionForm['conditionType'] = 'script'
          this.flowConditionForm['scriptType'] = 'externalScript'
          return;
        }
        if (conditionExpression.language) {
          this.flowConditionForm['conditionType'] = 'script'
          this.flowConditionForm['scriptType'] = 'inlineScript'
          return;
        }
        this.flowConditionForm['conditionType'] = 'expression'
      }
    },
    updateFlowType(flowType) {
      // æ­£å¸¸æ¡ä»¶ç±»
      if (flowType === 'condition') {
        this.flowConditionRef = window.bpmnInstances.moddle.create('bpmn:FormalExpression');
        window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
          conditionExpression: this.flowConditionRef
        });
        return;
      }
      // é»˜è®¤è·¯å¾„
      if (flowType === 'default') {
        window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
          conditionExpression: null
        });
        window.bpmnInstances.modeling.updateProperties(this.bpmnElementSource, {
          default: this.bpmnElement
        });
        return;
      }
      // æ­£å¸¸è·¯å¾„,如果来源节点的默认路径是当前连线时,清除父元素的默认路径配置
      if (this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.bpmnElement.id) {
        window.bpmnInstances.modeling.updateProperties(this.bpmnElementSource, {
          default: null
        });
      }
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
        conditionExpression: null
      });
    },
    updateFlowCondition() {
      const { conditionType, scriptType, body, resource, language } = this.flowConditionForm;
      let condition;
      if (conditionType === 'expression') {
        condition = window.bpmnInstances.moddle.create('bpmn:FormalExpression', { body });
      } else {
        if (scriptType === 'inlineScript') {
          condition = window.bpmnInstances.moddle.create('bpmn:FormalExpression', { body, language });
          this.flowConditionForm['resource'] = ''
        } else {
          this.flowConditionForm['body'] = ''
          condition = window.bpmnInstances.moddle.create('bpmn:FormalExpression', { resource, language });
        }
      }
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { conditionExpression: condition });
    }
  },
  beforeUnmount() {
    this.bpmnElement = null;
    this.bpmnElementSource = null;
    this.bpmnElementSourceRef = null;
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/form/ElementForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,417 @@
<template>
  <div class="panel-tab__content">
    <el-form size="small" label-width="80px"  @submit.prevent>
      <el-form-item label="表单" prop="formKey">
        <el-select v-model="formKey" placeholder="请选择表单" @change="updateElementFormKey" clearable>
          <el-option v-for="item in formOptions" :key="item.formId" :label="item.formName" :value="`key_${item.formId}`" />
        </el-select>
      </el-form-item>
      <el-form-item prop="localScope">
        <template #label>
          <div class="flex align-center">
            <el-tooltip content="若为节点表单,则表单信息仅在此节点可用,默认为全局表单,表单信息在整个流程实例中可用" placement="top-start">
              <el-icon><InfoFilled /></el-icon>
            </el-tooltip>
            <span>节点表单</span>
          </div>
        </template>
        <el-switch
          :disabled="type === 'StartEvent'"
          v-model="localScope"
          active-text="是"
          inactive-text="否"
          @change="updateElementFormScope()"
        ></el-switch>
      </el-form-item>
      <!--      <el-form-item label="表单标识">-->
      <!--        <el-input v-model="formKey" clearable @change="updateElementFormKey" />-->
      <!--      </el-form-item>-->
      <!--      <el-form-item label="业务标识">-->
      <!--        <el-select v-model="businessKey" @change="updateElementBusinessKey">-->
      <!--          <el-option v-for="i in fieldList" :key="i.id" :value="i.id" :label="i.label" />-->
      <!--          <el-option label="无" value="" />-->
      <!--        </el-select>-->
      <!--      </el-form-item>-->
    </el-form>
    <!--字段列表-->
    <!--    <div class="element-property list-property">-->
    <!--      <el-divider><el-icon><coin /></el-icon> è¡¨å•字段</el-divider>-->
    <!--      <el-table :data="fieldList" size="small" max-height="240" border fit>-->
    <!--        <el-table-column label="序号" type="index" width="50px" />-->
    <!--        <el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />-->
    <!--        <el-table-column label="字段类型" prop="type" min-width="80px" :formatter="row => fieldType[row.type] || row.type" show-overflow-tooltip />-->
    <!--        <el-table-column label="默认值" prop="defaultValue" min-width="80px" show-overflow-tooltip />-->
    <!--        <el-table-column label="操作" width="90px">-->
    <!--          <template v-slot="{ row, $index }">-->
    <!--            <el-button link type=""  @click="openFieldForm(row, $index)">编辑</el-button>-->
    <!--            <el-divider direction="vertical" />-->
    <!--            <el-button link type=""  style="color: #ff4d4f" @click="removeField(row, $index)">移除</el-button>-->
    <!--          </template>-->
    <!--        </el-table-column>-->
    <!--      </el-table>-->
    <!--    </div>-->
    <!--    <div class="element-drawer__button">-->
    <!--      <el-button size="small" type="primary" :icon="Plus"  @click="openFieldForm(null, -1)">添加字段</el-button>-->
    <!--    </div>-->
    <!--字段配置侧边栏-->
    <!--    <el-drawer v-model="fieldModelVisible" title="字段配置" :size="`${width}px`" append-to-body destroy-on-close>-->
    <!--      <el-form :model="formFieldForm" label-width="90px" size="small" @submit.prevent>-->
    <!--        <el-form-item label="字段ID">-->
    <!--          <el-input v-model="formFieldForm.id" clearable />-->
    <!--        </el-form-item>-->
    <!--        <el-form-item label="类型">-->
    <!--          <el-select v-model="formFieldForm.typeType" placeholder="请选择字段类型" clearable @change="changeFieldTypeType">-->
    <!--            <el-option v-for="(value, key) of fieldType" :key="key" :label="value" :value="key" />-->
    <!--          </el-select>-->
    <!--        </el-form-item>-->
    <!--        <el-form-item v-if="formFieldForm.typeType === 'custom'" label="类型名称">-->
    <!--          <el-input v-model="formFieldForm.type" clearable />-->
    <!--        </el-form-item>-->
    <!--        <el-form-item label="名称">-->
    <!--          <el-input v-model="formFieldForm.label" clearable />-->
    <!--        </el-form-item>-->
    <!--        <el-form-item v-if="formFieldForm.typeType === 'date'" label="时间格式">-->
    <!--          <el-input v-model="formFieldForm.datePattern" clearable />-->
    <!--        </el-form-item>-->
    <!--        <el-form-item label="默认值">-->
    <!--          <el-input v-model="formFieldForm.defaultValue" clearable />-->
    <!--        </el-form-item>-->
    <!--      </el-form>-->
    <!-- æžšä¸¾å€¼è®¾ç½® -->
    <!--      <template v-if="formFieldForm.type === 'enum'">-->
    <!--        <el-divider key="enum-divider" />-->
    <!--        <p key="enum-title" class="listener-filed__title">-->
    <!--          <span>-->
    <!--            <el-icon>-->
    <!--               <menu />-->
    <!--            </el-icon>-->
    <!--          æžšä¸¾å€¼åˆ—表:-->
    <!--          </span>-->
    <!--          <el-button size="small" type="primary" @click="openFieldOptionForm(null, -1, 'enum')">添加枚举值</el-button>-->
    <!--        </p>-->
    <!--        <el-table key="enum-table" :data="fieldEnumList" size="small" max-height="240" border fit>-->
    <!--          <el-table-column label="序号" width="50px" type="index" />-->
    <!--          <el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />-->
    <!--          <el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />-->
    <!--          <el-table-column label="操作" width="90px">-->
    <!--            <template v-slot="{ row, $index }">-->
    <!--              <el-button link type="" @click="openFieldOptionForm(row, $index, 'enum')">编辑</el-button>-->
    <!--              <el-divider direction="vertical" />-->
    <!--              <el-button link type="" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'enum')">移除</el-button>-->
    <!--            </template>-->
    <!--          </el-table-column>-->
    <!--        </el-table>-->
    <!--      </template>-->
    <!-- æ ¡éªŒè§„则 -->
    <!--      <el-divider key="validation-divider" />-->
    <!--      <p key="validation-title" class="listener-filed__title">-->
    <!--        <span><el-icon><menu /></el-icon>约束条件列表:</span>-->
    <!--        <el-button size="small" type="primary" @click="openFieldOptionForm(null, -1, 'constraint')">添加约束</el-button>-->
    <!--      </p>-->
    <!--      <el-table key="validation-table" :data="fieldConstraintsList" size="small" max-height="240" border fit>-->
    <!--        <el-table-column label="序号" width="50px" type="index" />-->
    <!--        <el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />-->
    <!--        <el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />-->
    <!--        <el-table-column label="操作" width="90px">-->
    <!--          <template v-slot="{ row, $index }">-->
    <!--            <el-button link type="" @click="openFieldOptionForm(row, $index, 'constraint')">编辑</el-button>-->
    <!--            <el-divider direction="vertical" />-->
    <!--            <el-button link type="" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'constraint')">移除</el-button>-->
    <!--          </template>-->
    <!--        </el-table-column>-->
    <!--      </el-table>-->
    <!-- è¡¨å•属性 -->
    <!--      <el-divider key="property-divider" />-->
    <!--      <p key="property-title" class="listener-filed__title">-->
    <!--        <span><el-icon><menu /></el-icon>字段属性列表:</span>-->
    <!--        <el-button size="small" type="primary" @click="openFieldOptionForm(null, -1, 'property')">添加属性</el-button>-->
    <!--      </p>-->
    <!--      <el-table key="property-table" :data="fieldPropertiesList" size="small" max-height="240" border fit>-->
    <!--        <el-table-column label="序号" width="50px" type="index" />-->
    <!--        <el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />-->
    <!--        <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />-->
    <!--        <el-table-column label="操作" width="90px">-->
    <!--          <template v-slot="{ row, $index }">-->
    <!--            <el-button link type="" @click="openFieldOptionForm(row, $index, 'property')">编辑</el-button>-->
    <!--            <el-divider direction="vertical" />-->
    <!--            <el-button link type="" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'property')">移除</el-button>-->
    <!--          </template>-->
    <!--        </el-table-column>-->
    <!--      </el-table>-->
    <!-- åº•部按钮 -->
    <!--      <div class="element-drawer__button">-->
    <!--        <el-button size="small">取 æ¶ˆ</el-button>-->
    <!--        <el-button size="small" type="primary" @click="saveField">保 å­˜</el-button>-->
    <!--      </div>-->
    <!--    </el-drawer>-->
    <el-dialog v-model="fieldOptionModelVisible" :title="optionModelTitle" width="600px" append-to-body destroy-on-close>
      <el-form :model="fieldOptionForm" size="small" label-width="96px" @submit.prevent>
        <el-form-item v-if="fieldOptionType !== 'constraint'" key="option-id" label="编号/ID">
          <el-input v-model="fieldOptionForm.id" clearable />
        </el-form-item>
        <el-form-item v-if="fieldOptionType !== 'property'" key="option-name" label="名称">
          <el-input v-model="fieldOptionForm.name" clearable />
        </el-form-item>
        <el-form-item v-if="fieldOptionType === 'constraint'" key="option-config" label="配置">
          <el-input v-model="fieldOptionForm.config" clearable />
        </el-form-item>
        <el-form-item v-if="fieldOptionType === 'property'" key="option-value" label="值">
          <el-input v-model="fieldOptionForm.value" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" @click="fieldOptionModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveFieldOption">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { listForm } from "#/api/workflow/form";
export default {
  name: 'ElementForm',
  setup() {
    return {
      Plus
    }
  },
  props: {
    id: String,
    type: String
  },
  inject: {
    prefix: 'prefix',
    width: 'width'
  },
  data() {
    return {
      formKey: '',
      formOptions: [],
      localScope: false,
      businessKey: '',
      optionModelTitle: '',
      fieldList: [],
      formFieldForm: {},
      fieldType: {
        long: '长整型',
        string: '字符串',
        boolean: '布尔类',
        date: '日期类',
        enum: '枚举类',
        custom: '自定义类型'
      },
      formFieldIndex: -1, // ç¼–辑中的字段, -1 ä¸ºæ–°å¢ž
      formFieldOptionIndex: -1, // ç¼–辑中的字段配置项, -1 ä¸ºæ–°å¢ž
      fieldModelVisible: false,
      fieldOptionModelVisible: false,
      fieldOptionForm: {}, // å½“前激活的字段配置项数据
      fieldOptionType: '', // å½“前激活的字段配置项弹窗 ç±»åž‹
      fieldEnumList: [], // æžšä¸¾å€¼åˆ—表
      fieldConstraintsList: [], // çº¦æŸæ¡ä»¶åˆ—表
      fieldPropertiesList: [] // ç»‘定属性列表
    };
  },
  watch: {
    id: {
      immediate: true,
      handler(val) {
        val && val.length && this.$nextTick(() => this.resetFormList());
      }
    }
  },
  created() {
    /** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
    this.getFormList();
  },
  methods: {
    /** æŸ¥è¯¢è¡¨å•列表 */
    getFormList() {
      listForm().then(response => {
        this.formOptions = response.rows;
    }
    )
    },
    resetFormList() {
      this.bpmnELement = window.bpmnInstances.bpmnElement;
      this.formKey = this.bpmnELement.businessObject.formKey;
      this.localScope = this.bpmnELement.businessObject.localScope;
      // èŽ·å–å…ƒç´ æ‰©å±•å±žæ€§ æˆ–者 åˆ›å»ºæ‰©å±•属性
      this.elExtensionElements =
        this.bpmnELement.businessObject.get('extensionElements') || window.bpmnInstances.moddle.create('bpmn:ExtensionElements', { values: [] });
      // èŽ·å–å…ƒç´ è¡¨å•é…ç½® æˆ–者 åˆ›å»ºæ–°çš„表单配置
      // try {
      // this.formData =
      //   this.elExtensionElements.values.filter(ex => ex.$type === `${this.prefix}:FormData`)[0] ||
      //   window.bpmnInstances.moddle.create(`${this.prefix}:FormData`, { fields: [] });
      // } catch (error) {
      //   this.formData = {}
      //   console.log(error)
      // }
      // ä¸šåŠ¡æ ‡è¯† businessKey, ç»‘定在 formData ä¸­
      // this.businessKey = this.formData.businessKey;
      // ä¿ç•™å‰©ä½™æ‰©å±•元素,便于后面更新该元素对应属性
      this.otherExtensions = this.elExtensionElements.values.filter(ex => ex.$type !== `${this.prefix}:FormData`);
      // å¤åˆ¶åŽŸå§‹å€¼ï¼Œå¡«å……è¡¨æ ¼
      // this.fieldList = JSON.parse(JSON.stringify(this.formData.fields || []));
      // æ›´æ–°å…ƒç´ æ‰©å±•属性,避免后续报错
      // this.updateElementExtensions();
    },
    updateElementFormKey() {
      window.bpmnInstances.modeling.updateProperties(this.bpmnELement, { formKey: this.formKey });
    },
    updateElementFormScope() {
      window.bpmnInstances.modeling.updateProperties(this.bpmnELement, { localScope: this.localScope });
    },
    updateElementBusinessKey() {
      window.bpmnInstances.modeling.updateModdleProperties(this.bpmnELement, this.formData, { businessKey: this.businessKey });
    },
    // æ ¹æ®ç±»åž‹è°ƒæ•´å­—段type
    changeFieldTypeType(type) {
      this.formFieldForm['type'] = type === 'custom' ? '' : type
    },
    // æ‰“开字段详情侧边栏
    openFieldForm(field, index) {
      this.formFieldIndex = index;
      if (index !== -1) {
        const FieldObject = this.formData.fields[index];
        this.formFieldForm = JSON.parse(JSON.stringify(field));
        // è®¾ç½®è‡ªå®šä¹‰ç±»åž‹
        this.formFieldForm['typeType'] = !this.fieldType[field.type] ? 'custom' : field.type
        // åˆå§‹åŒ–枚举值列表
        field.type === 'enum' && (this.fieldEnumList = JSON.parse(JSON.stringify(FieldObject?.values || [])));
        // åˆå§‹åŒ–约束条件列表
        this.fieldConstraintsList = JSON.parse(JSON.stringify(FieldObject?.validation?.constraints || []));
        // åˆå§‹åŒ–自定义属性列表
        this.fieldPropertiesList = JSON.parse(JSON.stringify(FieldObject?.properties?.values || []));
      } else {
        this.formFieldForm = {};
        // åˆå§‹åŒ–枚举值列表
        this.fieldEnumList = [];
        // åˆå§‹åŒ–约束条件列表
        this.fieldConstraintsList = [];
        // åˆå§‹åŒ–自定义属性列表
        this.fieldPropertiesList = [];
      }
      this.fieldModelVisible = true;
    },
    // æ‰“开字段 æŸä¸ª é…ç½®é¡¹ å¼¹çª—
    openFieldOptionForm(option, index, type) {
      this.fieldOptionModelVisible = true;
      this.fieldOptionType = type;
      this.formFieldOptionIndex = index;
      if (type === 'property') {
        this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
        return (this.optionModelTitle = '属性配置');
      }
      if (type === 'enum') {
        this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
        return (this.optionModelTitle = '枚举值配置');
      }
      this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
      return (this.optionModelTitle = '约束条件配置');
    },
    // ä¿å­˜å­—段 æŸä¸ª é…ç½®é¡¹
    saveFieldOption() {
      if (this.formFieldOptionIndex === -1) {
        if (this.fieldOptionType === 'property') {
          this.fieldPropertiesList.push(this.fieldOptionForm);
        }
        if (this.fieldOptionType === 'constraint') {
          this.fieldConstraintsList.push(this.fieldOptionForm);
        }
        if (this.fieldOptionType === 'enum') {
          this.fieldEnumList.push(this.fieldOptionForm);
        }
      } else {
        this.fieldOptionType === 'property' && this.fieldPropertiesList.splice(this.formFieldOptionIndex, 1, this.fieldOptionForm);
        this.fieldOptionType === 'constraint' && this.fieldConstraintsList.splice(this.formFieldOptionIndex, 1, this.fieldOptionForm);
        this.fieldOptionType === 'enum' && this.fieldEnumList.splice(this.formFieldOptionIndex, 1, this.fieldOptionForm);
      }
      this.fieldOptionModelVisible = false;
      this.fieldOptionForm = {};
    },
    // ä¿å­˜å­—段配置
    saveField() {
      const { id, type, label, defaultValue, datePattern } = this.formFieldForm;
      const Field = window.bpmnInstances.moddle.create(`${this.prefix}:FormField`, { id, type, label });
      defaultValue && (Field.defaultValue = defaultValue);
      datePattern && (Field.datePattern = datePattern);
      // æž„建属性
      if (this.fieldPropertiesList && this.fieldPropertiesList.length) {
        const fieldPropertyList = this.fieldPropertiesList.map(fp => {
          return window.bpmnInstances.moddle.create(`${this.prefix}:Property`, { id: fp.id, value: fp.value });
        });
        Field.properties = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, { values: fieldPropertyList });
      }
      // æž„建校验规则
      if (this.fieldConstraintsList && this.fieldConstraintsList.length) {
        const fieldConstraintList = this.fieldConstraintsList.map(fc => {
          return window.bpmnInstances.moddle.create(`${this.prefix}:Constraint`, { name: fc.name, config: fc.config });
        });
        Field.validation = window.bpmnInstances.moddle.create(`${this.prefix}:Validation`, { constraints: fieldConstraintList });
      }
      // æž„建枚举值
      if (this.fieldEnumList && this.fieldEnumList.length) {
        Field.values = this.fieldEnumList.map(fe => {
          return window.bpmnInstances.moddle.create(`${this.prefix}:Value`, { name: fe.name, id: fe.id });
        });
      }
      // æ›´æ–°æ•°ç»„ ä¸Ž è¡¨å•配置实例
      if (this.formFieldIndex === -1) {
        this.fieldList.push(this.formFieldForm);
        this.formData.fields && this.formData.fields.push(Field);
      } else {
        this.fieldList.splice(this.formFieldIndex, 1, this.formFieldForm);
        this.formData.fields.splice(this.formFieldIndex, 1, Field);
      }
      this.updateElementExtensions();
      this.fieldModelVisible = false;
    },
    // ç§»é™¤æŸä¸ª å­—段的 é…ç½®é¡¹
    removeFieldOptionItem(option, index, type) {
      if (type === 'property') {
        this.fieldPropertiesList.splice(index, 1);
        return;
      }
      if (type === 'enum') {
        this.fieldEnumList.splice(index, 1);
        return;
      }
      this.fieldConstraintsList.splice(index, 1);
    },
    // ç§»é™¤ å­—段
    removeField(field, index) {
      this.fieldList.splice(index, 1);
      this.formData.fields.splice(index, 1);
      this.updateElementExtensions();
    },
    updateElementExtensions() {
      // æ›´æ–°å›žæ‰©å±•元素
      const newElExtensionElements = window.bpmnInstances.moddle.create(`bpmn:ExtensionElements`, {
        values: this.otherExtensions.concat(this.formData)
      });
      // æ›´æ–°åˆ°å…ƒç´ ä¸Š
      window.bpmnInstances.modeling.updateProperties(this.bpmnELement, {
        extensionElements: newElExtensionElements
      });
    }
  }
}
</script>
ruoyi-ui/apps/web-antd/src/package/penal/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
import MyPropertiesPanel from './PropertiesPanel.vue';
MyPropertiesPanel.install = function(Vue) {
  Vue.component(MyPropertiesPanel.name, MyPropertiesPanel);
};
export default MyPropertiesPanel;
ruoyi-ui/apps/web-antd/src/package/penal/listeners/ElementListeners.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
<template>
  <div class="panel-tab__content">
    <el-table :data="elementListenersList" size="small" border>
      <el-table-column label="序号" width="50px" type="index" />
      <el-table-column label="事件类型" min-width="100px" prop="event" />
      <el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
      <el-table-column label="操作" width="90px">
        <template v-slot="{ row, $index }">
          <el-button link type="" @click="openListenerForm(row, $index)">编辑</el-button>
          <el-divider direction="vertical" />
          <el-button link type="" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="element-drawer__button">
      <el-button size="small" type="primary" :icon="Plus" @click="openListenerForm(null)">添加监听器</el-button>
    </div>
    <!-- ç›‘听器 ç¼–辑/创建 éƒ¨åˆ† -->
    <el-drawer v-model="listenerFormModelVisible" title="执行监听器" :size="`${width}px`" append-to-body destroy-on-close>
      <el-form size="small" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.prevent>
        <el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-select v-model="listenerForm.event">
            <el-option label="start" value="start" />
            <el-option label="end" value="end" />
          </el-select>
        </el-form-item>
        <el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-select v-model="listenerForm.listenerType">
            <el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
          </el-select>
        </el-form-item>
        <el-form-item
          v-if="listenerForm.listenerType === 'classListener'"
          label="Javaç±»"
          prop="class"
          key="listener-class"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerForm.class" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerForm.listenerType === 'expressionListener'"
          label="表达式"
          prop="expression"
          key="listener-expression"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerForm.expression" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerForm.listenerType === 'delegateExpressionListener'"
          label="代理表达式"
          prop="delegateExpression"
          key="listener-delegate"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerForm.delegateExpression" clearable />
        </el-form-item>
        <template v-if="listenerForm.listenerType === 'scriptListener'">
          <el-form-item
            label="脚本格式"
            prop="scriptFormat"
            key="listener-script-format"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
          >
            <el-input v-model="listenerForm.scriptFormat" clearable />
          </el-form-item>
          <el-form-item
            label="脚本类型"
            prop="scriptType"
            key="listener-script-type"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
          >
            <el-select v-model="listenerForm.scriptType">
              <el-option label="内联脚本" value="inlineScript" />
              <el-option label="外部脚本" value="externalScript" />
            </el-select>
          </el-form-item>
          <el-form-item
            v-if="listenerForm.scriptType === 'inlineScript'"
            label="脚本内容"
            prop="value"
            key="listener-script"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
          >
            <el-input v-model="listenerForm.value" clearable />
          </el-form-item>
          <el-form-item
            v-if="listenerForm.scriptType === 'externalScript'"
            label="资源地址"
            prop="resource"
            key="listener-resource"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
          >
            <el-input v-model="listenerForm.resource" clearable />
          </el-form-item>
        </template>
      </el-form>
      <el-divider />
      <p class="listener-filed__title">
        <span><el-icon><Menu /></el-icon>注入字段:</span>
        <el-button size="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
      </p>
      <el-table :data="fieldsListOfListener" size="small" max-height="240" border fit style="flex: none">
        <el-table-column label="序号" width="50px" type="index" />
        <el-table-column label="字段名称" min-width="100px" prop="name" />
        <el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
        <el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
        <el-table-column label="操作" width="100px">
          <template v-slot="{ row, $index }">
            <el-button link type="" @click="openListenerFieldForm(row, $index)">编辑</el-button>
            <el-divider direction="vertical" />
            <el-button link type="" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="element-drawer__button">
        <el-button size="small" @click="listenerFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveListenerConfig">保 å­˜</el-button>
      </div>
    </el-drawer>
    <!-- æ³¨å…¥è¥¿æ®µ ç¼–辑/创建 éƒ¨åˆ† -->
    <el-dialog title="字段配置" v-model="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
      <el-form :model="listenerFieldForm" size="small" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.prevent>
        <el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-input v-model="listenerFieldForm.name" clearable />
        </el-form-item>
        <el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-select v-model="listenerFieldForm.fieldType">
            <el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
          </el-select>
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === 'string'"
          label="字段值:"
          prop="string"
          key="field-string"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerFieldForm.string" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === 'expression'"
          label="表达式:"
          prop="expression"
          key="field-expression"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerFieldForm.expression" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" @click="listenerFieldFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveListenerFiled">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { createListenerObject, updateElementExtensions } from "../../utils";
import { initListenerType, initListenerForm, LISTENER_TYPE, FIELD_TYPE } from "./utilSelf";
export default {
  name: "ElementListeners",
  setup() {
    return { Plus }
  },
  props: {
    id: String,
    type: String
  },
  inject: {
    prefix: "prefix",
    width: "width"
  },
  data() {
    return {
      elementListenersList: [], // ç›‘听器列表
      listenerForm: {}, // ç›‘听器详情表单
      listenerFormModelVisible: false, // ç›‘听器 ç¼–辑 ä¾§è¾¹æ æ˜¾ç¤ºçŠ¶æ€
      fieldsListOfListener: [],
      listenerFieldForm: {}, // ç›‘听器 æ³¨å…¥å­—段 è¯¦æƒ…表单
      listenerFieldFormModelVisible: false, // ç›‘听器 æ³¨å…¥å­—段表单弹窗 æ˜¾ç¤ºçŠ¶æ€
      editingListenerIndex: -1, // ç›‘听器所在下标,-1 ä¸ºæ–°å¢ž
      editingListenerFieldIndex: -1, // å­—段所在下标,-1 ä¸ºæ–°å¢ž
      listenerTypeObject: LISTENER_TYPE,
      fieldTypeObject: FIELD_TYPE
    };
  },
  watch: {
    id: {
      immediate: true,
      handler(val) {
        val && val.length && this.$nextTick(() => this.resetListenersList());
      }
    }
  },
  methods: {
    resetListenersList() {
      this.bpmnElement = window.bpmnInstances.bpmnElement;
      this.otherExtensionList = [];
      this.bpmnElementListeners =
        this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:ExecutionListener`) ?? [];
      this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
    },
    // æ‰“å¼€ ç›‘听器详情 ä¾§è¾¹æ 
    openListenerForm(listener, index) {
      if (listener) {
        this.listenerForm = initListenerForm(listener);
        this.editingListenerIndex = index;
      } else {
        this.listenerForm = {};
        this.editingListenerIndex = -1; // æ ‡è®°ä¸ºæ–°å¢ž
      }
      if (listener && listener.fields) {
        this.fieldsListOfListener = listener.fields.map(field => ({
          ...field,
          fieldType: field.string ? "string" : "expression"
        }));
      } else {
        this.fieldsListOfListener = [];
        this.listenerForm["fields"] = []
      }
      // æ‰“开侧边栏并清楚验证状态
      this.listenerFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
      });
    },
    // æ‰“开监听器字段编辑弹窗
    openListenerFieldForm(field, index) {
      this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
      this.editingListenerFieldIndex = field ? index : -1;
      this.listenerFieldFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
      });
    },
    // ä¿å­˜ç›‘听器注入字段
    async saveListenerFiled() {
      let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
      if (!validateStatus) return; // éªŒè¯ä¸é€šè¿‡ç›´æŽ¥è¿”回
      if (this.editingListenerFieldIndex === -1) {
        this.fieldsListOfListener.push(this.listenerFieldForm);
        this.listenerForm.fields.push(this.listenerFieldForm);
      } else {
        this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
        this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
      }
      this.listenerFieldFormModelVisible = false;
      this.$nextTick(() => (this.listenerFieldForm = {}));
    },
    // ç§»é™¤ç›‘听器字段
    removeListenerField(field, index) {
      this.$confirm("确认移除该字段吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.fieldsListOfListener.splice(index, 1);
          this.listenerForm.fields.splice(index, 1);
        })
        .catch(() => console.info("操作取消"));
    },
    // ç§»é™¤ç›‘听器
    removeListener(listener, index) {
      this.$confirm("确认移除该监听器吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.bpmnElementListeners.splice(index, 1);
          this.elementListenersList.splice(index, 1);
          updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
        })
        .catch(() => console.info("操作取消"));
    },
    // ä¿å­˜ç›‘听器配置
    async saveListenerConfig() {
      let validateStatus = await this.$refs["listenerFormRef"].validate();
      if (!validateStatus) return; // éªŒè¯ä¸é€šè¿‡ç›´æŽ¥è¿”回
      const listenerObject = createListenerObject(this.listenerForm, false, this.prefix);
      if (this.editingListenerIndex === -1) {
        this.bpmnElementListeners.push(listenerObject);
        this.elementListenersList.push(this.listenerForm);
      } else {
        this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
        this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
      }
      // ä¿å­˜å…¶ä»–配置
      this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:ExecutionListener`) ?? [];
      updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
      // 4. éšè—ä¾§è¾¹æ 
      this.listenerFormModelVisible = false;
      this.listenerForm = {};
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/listeners/UserTaskListeners.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,335 @@
<template>
  <div class="panel-tab__content">
    <el-table :data="elementListenersList" size="small" border>
      <el-table-column label="序号" width="50px" type="index" />
      <el-table-column label="事件类型" min-width="80px" show-overflow-tooltip :formatter="formatterEvent" />
      <!--      <el-table-column label="事件id" min-width="80px" prop="id" show-overflow-tooltip />-->
      <el-table-column label="监听器类型" min-width="80px" show-overflow-tooltip :formatter="formatterListener" />
      <el-table-column label="操作" width="90px">
        <template v-slot="{ row, $index }">
          <el-button link type="primary" @click="openListenerForm(row, $index)">编辑</el-button>
          <el-button link type="danger" @click="removeListener(row, $index)">移除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="element-drawer__button">
      <el-button size="small" type="primary" :icon="Plus" @click="openListenerForm(null)">添加监听器</el-button>
    </div>
    <!-- ç›‘听器 ç¼–辑/创建 éƒ¨åˆ† -->
    <el-drawer
      v-model="listenerFormModelVisible"
      title="任务监听器"
      :size="`${width}px`"
      class="listener-drawer"
      append-to-body
      destroy-on-close>
      <el-form :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.prevent>
        <el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择事件类型' }">
          <el-select v-model="listenerForm.event" style="width: 100%">
            <el-option v-for="item in TASK_EVENT_TYPE" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择监听器类型' }">
          <el-input
            v-model="listenerForm[selectedProp]"
            clearable
            class="input-with-select">
            <template #prepend>
              <el-select v-model="listenerForm.listenerType" style="width: 100%">
                <el-option v-for="item in LISTENER_TYPE" :key="item.key" :label="item.label" :value="item.value" />
              </el-select>
            </template>
            <template #append>
              <el-button :icon="Search" />
            </template>
          </el-input>
        </el-form-item>
        <!--   è„šæœ¬ç±»åž‹       -->
        <!--        <template v-if="listenerForm.listenerType === LISTENER_TYPE[LISTENER_TYPE.length - 1].value">-->
        <!--          <el-form-item-->
        <!--            label="脚本格式"-->
        <!--            prop="scriptFormat"-->
        <!--            key="listener-script-format"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"-->
        <!--          >-->
        <!--            <el-input v-model="listenerForm.scriptFormat" clearable />-->
        <!--          </el-form-item>-->
        <!--          <el-form-item-->
        <!--            label="脚本类型"-->
        <!--            prop="scriptType"-->
        <!--            key="listener-script-type"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"-->
        <!--          >-->
        <!--            <el-select v-model="listenerForm.scriptType">-->
        <!--              <el-option v-for="item in SCRIPT_TYPE" :key="item.value" :label="item.label" :value="item.value" />-->
        <!--            </el-select>-->
        <!--          </el-form-item>-->
        <!--          <el-form-item-->
        <!--            v-if="listenerForm.scriptType === SCRIPT_TYPE[0].value"-->
        <!--            label="脚本内容"-->
        <!--            prop="value"-->
        <!--            key="listener-script"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"-->
        <!--          >-->
        <!--            <el-input v-model="listenerForm.value" clearable />-->
        <!--          </el-form-item>-->
        <!--          <el-form-item-->
        <!--            v-if="listenerForm.scriptType === SCRIPT_TYPE[1].value"-->
        <!--            label="资源地址"-->
        <!--            prop="resource"-->
        <!--            key="listener-resource"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"-->
        <!--          >-->
        <!--            <el-input v-model="listenerForm.resource" clearable />-->
        <!--          </el-form-item>-->
        <!--        </template>-->
        <!--  ç›‘听事件: è¶…æ—¶      -->
        <template v-if="listenerForm.event === TASK_EVENT_TYPE[TASK_EVENT_TYPE.length - 1].value">
          <el-form-item label="定时器类型" prop="eventDefinitionType" key="eventDefinitionType">
            <el-select v-model="listenerForm.eventDefinitionType">
              <el-option v-for="item in EVENT_DEFINITION_TYPE" :key="item.value" :value="item.value" :label="item.label" />
            </el-select>
          </el-form-item>
          <el-form-item
            v-if="!!listenerForm.eventDefinitionType && listenerForm.eventDefinitionType !== 'null'"
            label="定时器"
            prop="eventTimeDefinitions"
            key="eventTimeDefinitions"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写定时器配置' }"
          >
            <el-input v-model="listenerForm.eventTimeDefinitions" clearable />
          </el-form-item>
        </template>
      </el-form>
      <el-divider />
      <div class="listener-filed__title">
        <span><el-icon><Menu /></el-icon>注入字段:</span>
        <el-button size="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
      </div>
      <el-table :data="fieldsListOfListener" size="small" max-height="240" border fit style="flex: none">
        <el-table-column label="序号" width="50px" type="index" />
        <el-table-column label="字段名称" min-width="100px" prop="name" />
        <el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="formatterField" />
        <el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
        <el-table-column label="操作" width="100px">
          <template v-slot="{ row, $index }">
            <el-button link type="primary" @click="openListenerFieldForm(row, $index)">编辑</el-button>
            <el-button link type="danger" @click="removeListenerField(row, $index)">移除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="element-drawer__button">
        <el-button @click="listenerFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="saveListenerConfig">保 å­˜</el-button>
      </div>
    </el-drawer>
    <!-- æ³¨å…¥è¥¿æ®µ ç¼–辑/创建 éƒ¨åˆ† -->
    <el-dialog title="字段配置" v-model="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
      <el-form :model="listenerFieldForm" size="small" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.prevent>
        <el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写字段名称' }">
          <el-input v-model="listenerFieldForm.name" clearable />
        </el-form-item>
        <el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择字段类型' }">
          <el-select v-model="listenerFieldForm.fieldType">
            <el-option v-for="item in FIELD_TYPE" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === FIELD_TYPE[0].value"
          label="字段值:"
          prop="string"
          key="field-string"
          :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写字段值' }"
        >
          <el-input v-model="listenerFieldForm.string" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === FIELD_TYPE[1].value"
          label="表达式:"
          prop="expression"
          key="field-expression"
          :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写表达式' }"
        >
          <el-input v-model="listenerFieldForm.expression" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button @click="listenerFieldFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="saveListenerFiled">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { createListenerObject, updateElementExtensions } from "../../utils";
import {
  initListenerForm,
  initListenerType,
  FIELD_TYPE,
  LISTENER_TYPE,
  TASK_EVENT_TYPE,
  SCRIPT_TYPE,
  EVENT_DEFINITION_TYPE
} from "./utilSelf";
export default {
  name: "UserTaskListeners",
  props: {
    id: String,
    type: String
  },
  inject: {
    prefix: "prefix",
    width: "width"
  },
  setup() {
    return { Plus, Search }
  },
  data() {
    return {
      elementListenersList: [],
      listenerFormModelVisible: false,
      listenerForm: {},
      fieldsListOfListener: [],
      listenerFieldFormModelVisible: false, // ç›‘听器 æ³¨å…¥å­—段表单弹窗 æ˜¾ç¤ºçŠ¶æ€
      editingListenerIndex: -1, // ç›‘听器所在下标,-1 ä¸ºæ–°å¢ž
      editingListenerFieldIndex: -1, // å­—段所在下标,-1 ä¸ºæ–°å¢ž
      listenerFieldForm: {} // ç›‘听器 æ³¨å…¥å­—段 è¯¦æƒ…表单
    };
  },
  watch: {
    id: {
      immediate: true,
      handler(val) {
        val && val.length && this.$nextTick(() => this.resetListenersList());
      }
    }
  },
  computed: {
    TASK_EVENT_TYPE() { return TASK_EVENT_TYPE },
    LISTENER_TYPE() { return LISTENER_TYPE },
    FIELD_TYPE() { return FIELD_TYPE },
    SCRIPT_TYPE() { return SCRIPT_TYPE },
    EVENT_DEFINITION_TYPE() { return EVENT_DEFINITION_TYPE },
    formatterEvent() {
      return (row) => {
        return TASK_EVENT_TYPE.find(find => find.value === row.event)?.label || ""
      }
    },
    formatterListener() {
      return (row) => {
        return LISTENER_TYPE.find(find => find.value === row.listener)?.label || ""
      }
    },
    formatterField() {
      return (row) => {
        return FIELD_TYPE.find(find => find.value === row.fieldType)?.label || ""
      }
    },
    selectedProp() {
      return LISTENER_TYPE.find(find => find.value === this.listenerForm.listenerType)?.prop || ''
    }
  },
  methods: {
    resetListenersList() {
      this.bpmnElement = window.bpmnInstances.bpmnElement;
      this.otherExtensionList = [];
      this.bpmnElementListeners = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:TaskListener`) ?? [];
      this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
    },
    openListenerForm(listener, index) {
      if (listener) {
        this.listenerForm = initListenerForm(listener);
        this.editingListenerIndex = index;
      } else {
        this.listenerForm = {};
        this.editingListenerIndex = -1; // æ ‡è®°ä¸ºæ–°å¢ž
      }
      if (listener && listener.fields) {
        this.fieldsListOfListener = listener.fields.map(field => ({
          ...field,
          fieldType: field.string ? "string" : "expression"
        }));
      } else {
        this.fieldsListOfListener = [];
        this.listenerForm["fields"] = []
      }
      // æ‰“开侧边栏并清楚验证状态
      this.listenerFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
      });
    },
    // ç§»é™¤ç›‘听器
    removeListener(listener, index) {
      this.$confirm("确认移除该监听器吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.bpmnElementListeners.splice(index, 1);
          this.elementListenersList.splice(index, 1);
          updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
        })
        .catch(() => console.info("操作取消"));
    },
    // ä¿å­˜ç›‘听器
    async saveListenerConfig() {
      let validateStatus = await this.$refs["listenerFormRef"].validate();
      if (!validateStatus) return; // éªŒè¯ä¸é€šè¿‡ç›´æŽ¥è¿”回
      const listenerObject = createListenerObject(this.listenerForm, true, this.prefix);
      if (this.editingListenerIndex === -1) {
        this.bpmnElementListeners.push(listenerObject);
        this.elementListenersList.push(this.listenerForm);
      } else {
        this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
        this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
      }
      // ä¿å­˜å…¶ä»–配置
      this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:TaskListener`) ?? [];
      updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
      // 4. éšè—ä¾§è¾¹æ 
      this.listenerFormModelVisible = false;
      this.listenerForm = {};
    },
    // æ‰“开监听器字段编辑弹窗
    openListenerFieldForm(field, index) {
      this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
      this.editingListenerFieldIndex = field ? index : -1;
      this.listenerFieldFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
      });
    },
    // ä¿å­˜ç›‘听器注入字段
    async saveListenerFiled() {
      let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
      if (!validateStatus) return; // éªŒè¯ä¸é€šè¿‡ç›´æŽ¥è¿”回
      if (this.editingListenerFieldIndex === -1) {
        this.fieldsListOfListener.push(this.listenerFieldForm);
        this.listenerForm.fields.push(this.listenerFieldForm);
      } else {
        this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
        this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
      }
      this.listenerFieldFormModelVisible = false;
      this.$nextTick(() => (this.listenerFieldForm = {}));
    },
    // ç§»é™¤ç›‘听器字段
    removeListenerField(field, index) {
      this.$confirm("确认移除该字段吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.fieldsListOfListener.splice(index, 1);
          this.listenerForm.fields.splice(index, 1);
        })
        .catch(() => console.info("操作取消"));
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/listeners/template.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,178 @@
export const template = isTaskListener => {
  return `
  <div class="panel-tab__content">
    <el-table :data="elementListenersList" size="small" border>
      <el-table-column label="序号" width="50px" type="index" />
      <el-table-column label="事件类型" min-width="100px" prop="event" />
      <el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
      <el-table-column label="操作" width="90px">
        <template v-slot="{ row, $index }">
          <el-button link type="" @click="openListenerForm(row, $index)">编辑</el-button>
          <el-divider direction="vertical" />
          <el-button link type="" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="element-drawer__button">
      <el-button size="small" type="primary" :icon="Plus" @click="openListenerForm(null)">添加监听器</el-button>
    </div>
    <!-- ç›‘听器 ç¼–辑/创建 éƒ¨åˆ† -->
    <el-drawer v-model="listenerFormModelVisible" title="执行监听器" :size="width + 'px'" append-to-body destroy-on-close>
      <el-form size="small" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.prevent>
        <el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-select v-model="listenerForm.event">
            <el-option label="start" value="start" />
            <el-option label="end" value="end" />
          </el-select>
        </el-form-item>
        <el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-select v-model="listenerForm.listenerType">
            <el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
          </el-select>
        </el-form-item>
        <el-form-item
          v-if="listenerForm.listenerType === 'classListener'"
          label="Javaç±»"
          prop="class"
          key="listener-class"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerForm.class" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerForm.listenerType === 'expressionListener'"
          label="表达式"
          prop="expression"
          key="listener-expression"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerForm.expression" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerForm.listenerType === 'delegateExpressionListener'"
          label="代理表达式"
          prop="delegateExpression"
          key="listener-delegate"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerForm.delegateExpression" clearable />
        </el-form-item>
        <template v-if="listenerForm.listenerType === 'scriptListener'">
          <el-form-item
            label="脚本格式"
            prop="scriptFormat"
            key="listener-script-format"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
          >
            <el-input v-model="listenerForm.scriptFormat" clearable />
          </el-form-item>
          <el-form-item
            label="脚本类型"
            prop="scriptType"
            key="listener-script-type"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
          >
            <el-select v-model="listenerForm.scriptType">
              <el-option label="内联脚本" value="inlineScript" />
              <el-option label="外部脚本" value="externalScript" />
            </el-select>
          </el-form-item>
          <el-form-item
            v-if="listenerForm.scriptType === 'inlineScript'"
            label="脚本内容"
            prop="value"
            key="listener-script"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
          >
            <el-input v-model="listenerForm.value" clearable />
          </el-form-item>
          <el-form-item
            v-if="listenerForm.scriptType === 'externalScript'"
            label="资源地址"
            prop="resource"
            key="listener-resource"
            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
          >
            <el-input v-model="listenerForm.resource" clearable />
          </el-form-item>
        </template>
        ${
  isTaskListener
    ? "<el-form-item label='定时器类型' prop='eventDefinitionType' key='eventDefinitionType'>" +
              "<el-select v-model='listenerForm.eventDefinitionType'>" +
              "<el-option label='日期' value='date' />" +
              "<el-option label='持续时长' value='duration' />" +
              "<el-option label='循环' value='cycle' />" +
              "<el-option label='无' value='' />" +
              '</el-select>' +
              '</el-form-item>' +
              "<el-form-item v-if='!!listenerForm.eventDefinitionType' label='定时器' prop='eventDefinitions' key='eventDefinitions'>" +
              "<el-input v-model='listenerForm.eventDefinitions' clearable />" +
              '</el-form-item>'
    : ''
}
      </el-form>
      <el-divider />
      <p class="listener-filed__title">
        <span><el-icon><Menu /></el-icon>注入字段:</span>
        <el-button size="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
      </p>
      <el-table :data="fieldsListOfListener" size="small" max-height="240" border fit style="flex: none">
        <el-table-column label="序号" width="50px" type="index" />
        <el-table-column label="字段名称" min-width="100px" prop="name" />
        <el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
        <el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
        <el-table-column label="操作" width="100px">
          <template v-slot="{ row, $index }">
            <el-button link type="" @click="openListenerFieldForm(row, $index)">编辑</el-button>
            <el-divider direction="vertical" />
            <el-button link type="" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="element-drawer__button">
        <el-button size="small" @click="listenerFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveListenerConfig">保 å­˜</el-button>
      </div>
    </el-drawer>
    <!-- æ³¨å…¥è¥¿æ®µ ç¼–辑/创建 éƒ¨åˆ† -->
    <el-dialog title="字段配置" v-model="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
      <el-form :model="listenerFieldForm" size="small" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.prevent>
        <el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-input v-model="listenerFieldForm.name" clearable />
        </el-form-item>
        <el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
          <el-select v-model="listenerFieldForm.fieldType">
            <el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
          </el-select>
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === 'string'"
          label="字段值:"
          prop="string"
          key="field-string"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerFieldForm.string" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === 'expression'"
          label="表达式:"
          prop="expression"
          key="field-expression"
          :rules="{ required: true, trigger: ['blur', 'change'] }"
        >
          <el-input v-model="listenerFieldForm.expression" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" @click="listenerFieldFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveListenerFiled">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
  `;
};
ruoyi-ui/apps/web-antd/src/package/penal/listeners/utilSelf.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
// åˆå§‹åŒ–表单数据
export function initListenerForm(listener) {
  let self = {
    ...listener
  };
  if (listener.script) {
    self = {
      ...listener,
      ...listener.script,
      scriptType: listener.script.resource ? "externalScript" : "inlineScript"
    };
  }
  if (listener.event === "timeout" && listener.eventDefinitions) {
    if (listener.eventDefinitions.length) {
      let k = "";
      for (let key in listener.eventDefinitions[0]) {
        console.log(listener.eventDefinitions, key);
        if (key.indexOf("time") !== -1) {
          k = key;
          self.eventDefinitionType = key.replace("time", "").toLowerCase();
        }
      }
      console.log(k);
      self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
    }
  }
  return self;
}
export function initListenerType(listener) {
  let listenerType;
  if (listener.class) listenerType = "classListener";
  if (listener.expression) listenerType = "expressionListener";
  if (listener.delegateExpression) listenerType = "delegateExpressionListener";
  if (listener.script) listenerType = "scriptListener";
  return {
    ...JSON.parse(JSON.stringify(listener)),
    ...(listener.script ?? {}),
    listenerType: listenerType
  };
}
// ç›‘听类型
export const LISTENER_TYPE = [
  { label: "Java ç±»", value: "classListener", prop: "class", key: "listener-class" },
  { label: "表达式", value: "expressionListener", prop: "expression", key: "listener-expression" },
  { label: "代理表达式", value: "delegateExpressionListener", prop: "delegateExpression", key: "listener-delegate" },
  // { label: "脚本", value: "scriptListener", prop: "scriptFormat", key: "listener-script-format" },
]
// è„šæœ¬ç±»åž‹
export const SCRIPT_TYPE = [
  { label: "内联脚本", value: "inlineScript" },
  { label: "外部脚本", value: "externalScript" },
]
// ä»»åŠ¡ç›‘å¬å™¨: äº‹ä»¶ç±»åž‹
export const TASK_EVENT_TYPE = [
  { label: "创建", value: "create" },
  { label: "指派", value: "assignment" },
  { label: "完成", value: "complete" },
  { label: "删除", value: "delete" },
  { label: "更新", value: "update" },
  { label: "超时", value: "timeout" },
]
// æ‰§è¡Œç›‘听器: äº‹ä»¶ç±»åž‹
export const EXECUTION_EVENT_TYPE = [
  { label: "开始", value: "start" },
  { label: "结束", value: "end" },
]
// äº‹ä»¶ç±»åž‹: å®šæ—¶å™¨ç±»åž‹
export const EVENT_DEFINITION_TYPE = [
  { label: "无", value: "null" },
  { label: "日期", value: "date" },
  { label: "持续时长", value: "duration" },
  { label: "循环", value: "cycle" },
]
// å­—段配置
export const FIELD_TYPE = [
  { label: "字符串", value: "string" },
  { label: "表达式", value: "expression" },
]
ruoyi-ui/apps/web-antd/src/package/penal/multi-instance/ElementMultiInstance.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,331 @@
<template>
  <div class="panel-tab__content">
    <el-table :data="elementListenersList" size="small" border>
      <el-table-column label="序号" width="50px" type="index" />
      <el-table-column label="事件类型" min-width="100px" prop="event" :formatter="formatterEvent" />
      <el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="formatterListener" />
      <el-table-column label="操作" width="90px">
        <template v-slot="{ row, $index }">
          <el-button link type="primary" @click="openListenerForm(row, $index)">编辑</el-button>
          <el-button link type="danger" @click="removeListener(row, $index)">移除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="element-drawer__button">
      <el-button size="small" type="primary" :icon="Plus" @click="openListenerForm(null)">添加监听器</el-button>
    </div>
    <!-- ç›‘听器 ç¼–辑/创建 éƒ¨åˆ† -->
    <el-drawer
      v-model="listenerFormModelVisible"
      title="执行监听器"
      :size="`${width}px`"
      class="listener-drawer"
      append-to-body
      destroy-on-close>
      <el-form :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.prevent>
        <el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择事件类型' }">
          <el-select v-model="listenerForm.event">
            <el-option v-for="item in EXECUTION_EVENT_TYPE" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择监听器类型' }">
          <el-input
            v-model="listenerForm[selectedProp]"
            clearable
            class="input-with-select">
            <template #prepend>
              <el-select v-model="listenerForm.listenerType" style="width: 100%">
                <el-option v-for="item in LISTENER_TYPE" :key="item.key" :label="item.label" :value="item.value" />
              </el-select>
            </template>
            <template #append>
              <el-button :icon="Search" />
            </template>
          </el-input>
        </el-form-item>
        <template v-for="item in LISTENER_TYPEWithoutJB">
          <el-form-item
            v-if="listenerForm.listenerType === item.value"
            :key="item.key"
            :label="item.label"
            :prop="item.prop"
            :rules="{ required: true, trigger: ['blur', 'change'] }"
          >
            <el-input v-model="listenerForm[item.prop]" clearable />
          </el-form-item>
        </template>
        <!--   è„šæœ¬ç±»åž‹       -->
        <!--        <template v-if="listenerForm.listenerType === LISTENER_TYPE[LISTENER_TYPE.length - 1].value">-->
        <!--          <el-form-item-->
        <!--            label="脚本格式"-->
        <!--            prop="scriptFormat"-->
        <!--            key="listener-script-format"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"-->
        <!--          >-->
        <!--            <el-input v-model="listenerForm.scriptFormat" clearable />-->
        <!--          </el-form-item>-->
        <!--          <el-form-item-->
        <!--            label="脚本类型"-->
        <!--            prop="scriptType"-->
        <!--            key="listener-script-type"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"-->
        <!--          >-->
        <!--            <el-select v-model="listenerForm.scriptType">-->
        <!--              <el-option v-for="item in SCRIPT_TYPE" :key="item.value" :label="item.label" :value="item.value" />-->
        <!--            </el-select>-->
        <!--          </el-form-item>-->
        <!--          <el-form-item-->
        <!--            v-if="listenerForm.scriptType === SCRIPT_TYPE[0].value"-->
        <!--            label="脚本内容"-->
        <!--            prop="value"-->
        <!--            key="listener-script"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"-->
        <!--          >-->
        <!--            <el-input v-model="listenerForm.value" clearable />-->
        <!--          </el-form-item>-->
        <!--          <el-form-item-->
        <!--            v-if="listenerForm.scriptType === SCRIPT_TYPE[1].value"-->
        <!--            label="资源地址"-->
        <!--            prop="resource"-->
        <!--            key="listener-resource"-->
        <!--            :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"-->
        <!--          >-->
        <!--            <el-input v-model="listenerForm.resource" clearable />-->
        <!--          </el-form-item>-->
        <!--        </template>-->
      </el-form>
      <el-divider />
      <div class="listener-filed__title">
        <span><el-icon><Menu /></el-icon>注入字段:</span>
        <el-button size="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
      </div>
      <el-table :data="fieldsListOfListener" size="small" max-height="240" border fit style="flex: none">
        <el-table-column label="序号" width="50px" type="index" />
        <el-table-column label="字段名称" min-width="100px" prop="name" />
        <el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="formatterField" />
        <el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
        <el-table-column label="操作" width="100px">
          <template v-slot="{ row, $index }">
            <el-button link type="primary" @click="openListenerFieldForm(row, $index)">编辑</el-button>
            <el-button link type="danger" @click="removeListenerField(row, $index)">移除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="element-drawer__button">
        <el-button size="small" @click="listenerFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveListenerConfig">保 å­˜</el-button>
      </div>
    </el-drawer>
    <!-- æ³¨å…¥è¥¿æ®µ ç¼–辑/创建 éƒ¨åˆ† -->
    <el-dialog title="字段配置" v-model="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
      <el-form :model="listenerFieldForm" size="small" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.prevent>
        <el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写字段名称' }">
          <el-input v-model="listenerFieldForm.name" clearable />
        </el-form-item>
        <el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择字段类型' }">
          <el-select v-model="listenerFieldForm.fieldType">
            <el-option v-for="item in FIELD_TYPE" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === FIELD_TYPE[0].value"
          label="字段值:"
          prop="string"
          key="field-string"
          :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写字段值' }"
        >
          <el-input v-model="listenerFieldForm.string" clearable />
        </el-form-item>
        <el-form-item
          v-if="listenerFieldForm.fieldType === FIELD_TYPE[1].value"
          label="表达式:"
          prop="expression"
          key="field-expression"
          :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写表达式' }"
        >
          <el-input v-model="listenerFieldForm.expression" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" @click="listenerFieldFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveListenerFiled">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { createListenerObject, updateElementExtensions } from "../../utils";
import {
  initListenerType,
  initListenerForm,
  EXECUTION_EVENT_TYPE,
  LISTENER_TYPE,
  FIELD_TYPE,
  SCRIPT_TYPE,
  EVENT_DEFINITION_TYPE
} from "./utilSelf";
import { Plus, Search } from "@element-plus/icons-vue";
export default {
  name: "ElementListeners",
  setup() {
    return { Plus, Search }
  },
  props: {
    id: String,
    type: String
  },
  inject: {
    prefix: "prefix",
    width: "width"
  },
  data() {
    return {
      elementListenersList: [], // ç›‘听器列表
      listenerForm: {}, // ç›‘听器详情表单
      listenerFormModelVisible: false, // ç›‘听器 ç¼–辑 ä¾§è¾¹æ æ˜¾ç¤ºçŠ¶æ€
      fieldsListOfListener: [],
      listenerFieldForm: {}, // ç›‘听器 æ³¨å…¥å­—段 è¯¦æƒ…表单
      listenerFieldFormModelVisible: false, // ç›‘听器 æ³¨å…¥å­—段表单弹窗 æ˜¾ç¤ºçŠ¶æ€
      editingListenerIndex: -1, // ç›‘听器所在下标,-1 ä¸ºæ–°å¢ž
      editingListenerFieldIndex: -1, // å­—段所在下标,-1 ä¸ºæ–°å¢ž
    };
  },
  watch: {
    id: {
      immediate: true,
      handler(val) {
        val && val.length && this.$nextTick(() => this.resetListenersList());
      }
    }
  },
  computed: {
    EXECUTION_EVENT_TYPE() { return EXECUTION_EVENT_TYPE },
    LISTENER_TYPE() { return LISTENER_TYPE },
    FIELD_TYPE() { return FIELD_TYPE },
    SCRIPT_TYPE() { return SCRIPT_TYPE },
    EVENT_DEFINITION_TYPE() { return EVENT_DEFINITION_TYPE },
    formatterEvent() {
      return (row) => {
        return EXECUTION_EVENT_TYPE.find(find => find.value === row.event)?.label || ""
      }
    },
    formatterListener() {
      return (row) => {
        return LISTENER_TYPE.find(find => find.value === row.listener)?.label || ""
      }
    },
    formatterField() {
      return (row) => {
        return FIELD_TYPE.find(find => find.value === row.fieldType)?.label || ""
      }
    },
    selectedProp() {
      return LISTENER_TYPE.find(find => find.value === this.listenerForm.listenerType)?.prop || ''
    }
  },
  methods: {
    resetListenersList() {
      this.bpmnElement = window.bpmnInstances.bpmnElement;
      this.otherExtensionList = [];
      this.bpmnElementListeners =
        this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:ExecutionListener`) ?? [];
      this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
    },
    // æ‰“å¼€ ç›‘听器详情 ä¾§è¾¹æ 
    openListenerForm(listener, index) {
      if (listener) {
        this.listenerForm = initListenerForm(listener);
        this.editingListenerIndex = index;
      } else {
        this.listenerForm = {};
        this.editingListenerIndex = -1; // æ ‡è®°ä¸ºæ–°å¢ž
      }
      if (listener && listener.fields) {
        this.fieldsListOfListener = listener.fields.map(field => ({
          ...field,
          fieldType: field.string ? "string" : "expression"
        }));
      } else {
        this.fieldsListOfListener = [];
        this.listenerForm["fields"] = []
      }
      // æ‰“开侧边栏并清楚验证状态
      this.listenerFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
      });
    },
    // æ‰“开监听器字段编辑弹窗
    openListenerFieldForm(field, index) {
      this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
      this.editingListenerFieldIndex = field ? index : -1;
      this.listenerFieldFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
      });
    },
    // ä¿å­˜ç›‘听器注入字段
    async saveListenerFiled() {
      let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
      if (!validateStatus) return; // éªŒè¯ä¸é€šè¿‡ç›´æŽ¥è¿”回
      if (this.editingListenerFieldIndex === -1) {
        this.fieldsListOfListener.push(this.listenerFieldForm);
        this.listenerForm.fields.push(this.listenerFieldForm);
      } else {
        this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
        this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
      }
      this.listenerFieldFormModelVisible = false;
      this.$nextTick(() => (this.listenerFieldForm = {}));
    },
    // ç§»é™¤ç›‘听器字段
    removeListenerField(field, index) {
      this.$confirm("确认移除该字段吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.fieldsListOfListener.splice(index, 1);
          this.listenerForm.fields.splice(index, 1);
        })
        .catch(() => console.info("操作取消"));
    },
    // ç§»é™¤ç›‘听器
    removeListener(listener, index) {
      this.$confirm("确认移除该监听器吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.bpmnElementListeners.splice(index, 1);
          this.elementListenersList.splice(index, 1);
          updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
        })
        .catch(() => console.info("操作取消"));
    },
    // ä¿å­˜ç›‘听器配置
    async saveListenerConfig() {
      let validateStatus = await this.$refs["listenerFormRef"].validate();
      if (!validateStatus) return; // éªŒè¯ä¸é€šè¿‡ç›´æŽ¥è¿”回
      const listenerObject = createListenerObject(this.listenerForm, false, this.prefix);
      if (this.editingListenerIndex === -1) {
        this.bpmnElementListeners.push(listenerObject);
        this.elementListenersList.push(this.listenerForm);
      } else {
        this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
        this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
      }
      // ä¿å­˜å…¶ä»–配置
      this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:ExecutionListener`) ?? [];
      updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
      // 4. éšè—ä¾§è¾¹æ 
      this.listenerFormModelVisible = false;
      this.listenerForm = {};
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/multi-instance/utilSelf.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
// åˆå§‹åŒ–表单数据
export function initListenerForm(listener) {
  let self = {
    ...listener
  };
  if (listener.script) {
    self = {
      ...listener,
      ...listener.script,
      scriptType: listener.script.resource ? "externalScript" : "inlineScript"
    };
  }
  if (listener.event === "timeout" && listener.eventDefinitions) {
    if (listener.eventDefinitions.length) {
      let k = "";
      for (let key in listener.eventDefinitions[0]) {
        console.log(listener.eventDefinitions, key);
        if (key.indexOf("time") !== -1) {
          k = key;
          self.eventDefinitionType = key.replace("time", "").toLowerCase();
        }
      }
      console.log(k);
      self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
    }
  }
  return self;
}
export function initListenerType(listener) {
  let listenerType;
  if (listener.class) listenerType = "classListener";
  if (listener.expression) listenerType = "expressionListener";
  if (listener.delegateExpression) listenerType = "delegateExpressionListener";
  if (listener.script) listenerType = "scriptListener";
  return {
    ...JSON.parse(JSON.stringify(listener)),
    ...(listener.script ?? {}),
    listenerType: listenerType
  };
}
// ç›‘听类型
export const LISTENER_TYPE = [
  { label: "Java ç±»", value: "classListener", prop: "class", key: "listener-class" },
  { label: "表达式", value: "expressionListener", prop: "expression", key: "listener-expression" },
  { label: "代理表达式", value: "delegateExpressionListener", prop: "delegateExpression", key: "listener-delegate" },
  // { label: "脚本", value: "scriptListener", prop: "scriptFormat", key: "listener-script-format" },
]
// è„šæœ¬ç±»åž‹
export const SCRIPT_TYPE = [
  { label: "内联脚本", value: "inlineScript" },
  { label: "外部脚本", value: "externalScript" },
]
// ä»»åŠ¡ç›‘å¬å™¨: äº‹ä»¶ç±»åž‹
export const TASK_EVENT_TYPE = [
  { label: "创建", value: "create" },
  { label: "指派", value: "assignment" },
  { label: "完成", value: "complete" },
  { label: "删除", value: "delete" },
  { label: "更新", value: "update" },
  { label: "超时", value: "timeout" },
]
// æ‰§è¡Œç›‘听器: äº‹ä»¶ç±»åž‹
export const EXECUTION_EVENT_TYPE = [
  { label: "开始", value: "start" },
  { label: "结束", value: "end" },
]
// äº‹ä»¶ç±»åž‹: å®šæ—¶å™¨ç±»åž‹
export const EVENT_DEFINITION_TYPE = [
  { label: "无", value: "null" },
  { label: "日期", value: "date" },
  { label: "持续时长", value: "duration" },
  { label: "循环", value: "cycle" },
]
// å­—段配置
export const FIELD_TYPE = [
  { label: "字符串", value: "string" },
  { label: "表达式", value: "expression" },
]
ruoyi-ui/apps/web-antd/src/package/penal/other/ElementOtherConfig.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
<template>
  <div class="panel-tab__content">
    <div class="element-property input-property">
      <div class="element-property__label">元素文档:</div>
      <div class="element-property__value">
        <el-input
          type="textarea"
          v-model="documentation"
          size="small"
          resize="vertical"
          :autosize="{ minRows: 2, maxRows: 4 }"
          @input="updateDocumentation"
          @blur="updateDocumentation"
        />
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "ElementOtherConfig",
  props: {
    id: String
  },
  data() {
    return {
      documentation: ""
    };
  },
  watch: {
    id: {
      immediate: true,
      handler: function(id) {
        if (id && id.length) {
          this.$nextTick(() => {
            const documentations = window.bpmnInstances.bpmnElement.businessObject?.documentation;
            this.documentation = documentations && documentations.length ? documentations[0].text : "";
          });
        } else {
          this.documentation = "";
        }
      }
    }
  },
  methods: {
    updateDocumentation() {
      (this.bpmnElement && this.bpmnElement.id === this.id) || (this.bpmnElement = window.bpmnInstances.elementRegistry.get(this.id));
      const documentation = window.bpmnInstances.bpmnFactory.create("bpmn:Documentation", { text: this.documentation });
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
        documentation: [documentation]
      });
    }
  },
  beforeUnmount() {
    this.bpmnElement = null;
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/properties/ElementProperties.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
<template>
  <div class="panel-tab__content">
    <el-table :data="elementPropertyList" size="small" max-height="240" border fit>
      <el-table-column label="序号" width="50px" type="index" />
      <el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
      <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
      <el-table-column label="操作" width="90px">
        <template v-slot="{ row, $index }">
          <el-button link type="" @click="openAttributesForm(row, $index)">编辑</el-button>
          <el-divider direction="vertical" />
          <el-button link type="" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="element-drawer__button">
      <el-button size="small" type="primary" :icon="Plus" @click="openAttributesForm(null, -1)">添加属性</el-button>
    </div>
    <el-dialog v-model="propertyFormModelVisible" title="属性配置" width="600px" append-to-body destroy-on-close>
      <el-form :model="propertyForm" label-width="80px" size="small" ref="attributeFormRef" @submit.prevent>
        <el-form-item label="属性名:" prop="name">
          <el-input v-model="propertyForm.name" clearable />
        </el-form-item>
        <el-form-item label="属性值:" prop="value">
          <el-input v-model="propertyForm.value" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" @click="propertyFormModelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="saveAttribute">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { Plus } from '@element-plus/icons-vue'
export default {
  name: "ElementProperties",
  setup() {
    return {
      Plus
    }
  },
  props: {
    id: String,
    type: String
  },
  inject: {
    prefix: "prefix",
    width: "width"
  },
  data() {
    return {
      elementPropertyList: [],
      propertyForm: {},
      editingPropertyIndex: -1,
      propertyFormModelVisible: false
    };
  },
  watch: {
    id: {
      immediate: true,
      handler(val) {
        val && val.length && this.resetAttributesList();
      }
    }
  },
  methods: {
    resetAttributesList() {
      this.bpmnElement = window.bpmnInstances.bpmnElement;
      this.otherExtensionList = []; // å…¶ä»–扩展配置
      this.bpmnElementProperties =
        this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
          if (ex.$type !== `${this.prefix}:Properties`) {
            this.otherExtensionList.push(ex);
          }
          return ex.$type === `${this.prefix}:Properties`;
        }) ?? [];
      // ä¿å­˜æ‰€æœ‰çš„ æ‰©å±•属性字段
      this.bpmnElementPropertyList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
      // å¤åˆ¶ æ˜¾ç¤º
      this.elementPropertyList = JSON.parse(JSON.stringify(this.bpmnElementPropertyList ?? []));
    },
    openAttributesForm(attr, index) {
      this.editingPropertyIndex = index;
      this.propertyForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
      this.propertyFormModelVisible = true;
      this.$nextTick(() => {
        if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
      });
    },
    removeAttributes(attr, index) {
      this.$confirm("确认移除该属性吗?", "提示", {
        confirmButtonText: "ç¡® è®¤",
        cancelButtonText: "取 æ¶ˆ"
      })
        .then(() => {
          this.elementPropertyList.splice(index, 1);
          this.bpmnElementPropertyList.splice(index, 1);
          // æ–°å»ºä¸€ä¸ªå±žæ€§å­—段的保存列表
          const propertiesObject = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, {
            values: this.bpmnElementPropertyList
          });
          this.updateElementExtensions(propertiesObject);
          this.resetAttributesList();
        })
        .catch(() => console.info("操作取消"));
    },
    saveAttribute() {
      const { name, value } = this.propertyForm;
      console.log(this.bpmnElementPropertyList);
      if (this.editingPropertyIndex !== -1) {
        window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementPropertyList[this.editingPropertyIndex], {
          name,
          value
        });
      } else {
        // æ–°å»ºå±žæ€§å­—段
        const newPropertyObject = window.bpmnInstances.moddle.create(`${this.prefix}:Property`, { name, value });
        // æ–°å»ºä¸€ä¸ªå±žæ€§å­—段的保存列表
        const propertiesObject = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, {
          values: this.bpmnElementPropertyList.concat([newPropertyObject])
        });
        this.updateElementExtensions(propertiesObject);
      }
      this.propertyFormModelVisible = false;
      this.resetAttributesList();
    },
    updateElementExtensions(properties) {
      const extensions = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
        values: this.otherExtensionList.concat([properties])
      });
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
        extensionElements: extensions
      });
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/signal-message/SignalAndMessage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
<template>
  <div class="panel-tab__content">
    <div class="panel-tab__content--title">
      <span><el-icon style="margin-right: 8px; color: #555555"><Menu /></el-icon>消息列表</span>
      <el-button size="small" type="primary" :icon="Plus" @click="openModel('message')">创建新消息</el-button>
    </div>
    <el-table :data="messageList" size="small" border>
      <el-table-column type="index" label="序号" width="60px" />
      <el-table-column label="消息ID" prop="id" max-width="300px" show-overflow-tooltip />
      <el-table-column label="消息名称" prop="name" max-width="300px" show-overflow-tooltip />
    </el-table>
    <div class="panel-tab__content--title" style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eeeeee">
      <span><el-icon style="margin-right: 8px; color: #555555"><Menu /></el-icon>信号列表</span>
      <el-button size="small" type="primary" :icon="Plus" @click="openModel('signal')">创建新信号</el-button>
    </div>
    <el-table :data="signalList" size="small" border>
      <el-table-column type="index" label="序号" width="60px" />
      <el-table-column label="信号ID" prop="id" max-width="300px" show-overflow-tooltip />
      <el-table-column label="信号名称" prop="name" max-width="300px" show-overflow-tooltip />
    </el-table>
    <el-dialog v-model="modelVisible" :title="modelConfig.title" :close-on-click-modal="false" width="400px" append-to-body destroy-on-close>
      <el-form :model="modelObjectForm" size="small" label-width="90px" @submit.prevent>
        <el-form-item :label="modelConfig.idLabel">
          <el-input v-model="modelObjectForm.id" clearable />
        </el-form-item>
        <el-form-item :label="modelConfig.nameLabel">
          <el-input v-model="modelObjectForm.name" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" @click="modelVisible = false">取 æ¶ˆ</el-button>
        <el-button size="small" type="primary" @click="addNewObject">保 å­˜</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { Plus } from '@element-plus/icons-vue'
export default {
  name: 'SignalAndMassage',
  setup() {
    return {
      Plus
    }
  },
  data() {
    return {
      signalList: [],
      messageList: [],
      modelVisible: false,
      modelType: '',
      modelObjectForm: {}
    };
  },
  computed: {
    modelConfig() {
      if (this.modelType === 'message') {
        return { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' };
      } else {
        return { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
      }
    }
  },
  mounted() {
    this.initDataList();
  },
  methods: {
    initDataList() {
      this.rootElements = window.bpmnInstances.modeler.getDefinitions().rootElements;
      this.messageIdMap = {};
      this.signalIdMap = {};
      this.messageList = [];
      this.signalList = [];
      this.rootElements.forEach(el => {
        if (el.$type === 'bpmn:Message') {
          this.messageIdMap[el.id] = true;
          this.messageList.push({ ...el });
        }
        if (el.$type === 'bpmn:Signal') {
          this.signalIdMap[el.id] = true;
          this.signalList.push({ ...el });
        }
      });
    },
    openModel(type) {
      this.modelType = type;
      this.modelObjectForm = {};
      this.modelVisible = true;
    },
    addNewObject() {
      if (this.modelType === 'message') {
        if (this.messageIdMap[this.modelObjectForm.id]) {
          return this.$message.error('该消息已存在,请修改id后重新保存');
        }
        const messageRef = window.bpmnInstances.moddle.create('bpmn:Message', this.modelObjectForm);
        this.rootElements.push(messageRef);
      } else {
        if (this.signalIdMap[this.modelObjectForm.id]) {
          return this.$message.error('该信号已存在,请修改id后重新保存');
        }
        const signalRef = window.bpmnInstances.moddle.create('bpmn:Signal', this.modelObjectForm);
        this.rootElements.push(signalRef);
      }
      this.modelVisible = false;
      this.initDataList();
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/task/ElementTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
<template>
  <div class="panel-tab__content">
    <el-form size="small" label-width="90px" @submit.prevent>
<!--      <el-form-item label="异步延续">-->
<!--        <el-checkbox v-model="taskConfigForm.asyncBefore" label="异步前" @change="changeTaskAsync" />-->
<!--        <el-checkbox v-model="taskConfigForm.asyncAfter" label="异步后" @change="changeTaskAsync" />-->
<!--        <el-checkbox v-model="taskConfigForm.exclusive" v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore" label="排除" @change="changeTaskAsync" />-->
<!--      </el-form-item>-->
      <component :is="witchTaskComponent" v-bind="$props" />
    </el-form>
  </div>
</template>
<script>
import UserTask from "./task-components/UserTask.vue";
import ScriptTask from "./task-components/ScriptTask.vue";
import ReceiveTask from "./task-components/ReceiveTask.vue";
export default {
  name: "ElementTaskConfig",
  components: { UserTask, ScriptTask, ReceiveTask },
  props: {
    id: String,
    type: String
  },
  data() {
    return {
      taskConfigForm: {
        asyncAfter: false,
        asyncBefore: false,
        exclusive: false
      },
      witchTaskComponent: "",
      installedComponent: {
        // æ‰‹å·¥ä»»åŠ¡ä¸Žæ™®é€šä»»åŠ¡ä¸€è‡´ï¼Œä¸éœ€è¦å…¶ä»–é…ç½®
        // æŽ¥æ”¶æ¶ˆæ¯ä»»åŠ¡ï¼Œéœ€è¦åœ¨å…¨å±€ä¸‹æ’å…¥æ–°çš„æ¶ˆæ¯å®žä¾‹ï¼Œå¹¶åœ¨è¯¥èŠ‚ç‚¹ä¸‹çš„ messageRef å±žæ€§ç»‘定该实例
        // å‘送任务、服务任务、业务规则任务共用一个相同配置
        UserTask: "UserTask", // ç”¨æˆ·ä»»åŠ¡é…ç½®
        ScriptTask: "ScriptTask", // è„šæœ¬ä»»åŠ¡é…ç½®
        ReceiveTask: "ReceiveTask" // æ¶ˆæ¯æŽ¥æ”¶ä»»åŠ¡
      }
    };
  },
  watch: {
    id: {
      immediate: true,
      handler() {
        this.bpmnElement = window.bpmnInstances.bpmnElement;
        // this.taskConfigForm.asyncBefore = this.bpmnElement?.businessObject?.asyncBefore;
        // this.taskConfigForm.asyncAfter = this.bpmnElement?.businessObject?.asyncAfter;
        // this.taskConfigForm.exclusive = this.bpmnElement?.businessObject?.exclusive;
      }
    },
    type: {
      immediate: true,
      handler() {
        this.witchTaskComponent = this.installedComponent[this.$props.type];
      }
    }
  },
  methods: {
    changeTaskAsync() {
      if (!this.taskConfigForm.asyncBefore && !this.taskConfigForm.asyncAfter) {
        this.taskConfigForm.exclusive = false;
      }
      window.bpmnInstances.modeling.updateProperties(window.bpmnInstances.bpmnElement, {
        ...this.taskConfigForm
      });
    }
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/task/task-components/ReceiveTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
<template>
  <div style="margin-top: 16px">
    <el-form-item label="消息实例">
      <div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap">
        <el-select v-model="bindMessageId" @change="updateTaskMessage">
          <el-option v-for="id in Object.keys(messageMap)" :value="id" :label="messageMap[id]" :key="id" />
        </el-select>
        <el-button size="small" type="primary" :icon="Plus" style="margin-left: 8px" @click="openMessageModel" />
      </div>
    </el-form-item>
    <el-dialog v-model="messageModelVisible" :close-on-click-modal="false" title="创建新消息" width="400px" append-to-body destroy-on-close>
      <el-form :model="newMessageForm" size="small" label-width="90px" @submit.prevent>
        <el-form-item label="消息ID">
          <el-input v-model="newMessageForm.id" clearable />
        </el-form-item>
        <el-form-item label="消息名称">
          <el-input v-model="newMessageForm.name" clearable />
        </el-form-item>
      </el-form>
      <template v-slot:footer>
        <el-button size="small" type="primary" @click="createNewMessage">ç¡® è®¤</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
export default {
  name: "ReceiveTask",
  setup() {
    return {
      Plus
    }
  },
  props: {
    id: String,
    type: String
  },
  data() {
    return {
      bindMessageId: "",
      newMessageForm: {},
      messageMap: {},
      messageModelVisible: false
    };
  },
  watch: {
    id: {
      immediate: true,
      handler() {
        this.$nextTick(() => this.getBindMessage());
      }
    }
  },
  created() {
    this.bpmnMessageRefsMap = Object.create(null);
    this.bpmnRootElements = window.bpmnInstances.modeler.getDefinitions().rootElements;
    this.bpmnRootElements
      .filter(el => el.$type === "bpmn:Message")
      .forEach(m => {
        this.bpmnMessageRefsMap[m.id] = m;
        this.messageMap[m.id] = m.name
      });
    this.messageMap["-1"] = "无" // æ·»åŠ ä¸€ä¸ªç©ºå¯¹è±¡ï¼Œä¿è¯å¯ä»¥å–æ¶ˆåŽŸæ¶ˆæ¯ç»‘å®š
  },
  methods: {
    getBindMessage() {
      this.bpmnElement = window.bpmnInstances.bpmnElement;
      this.bindMessageId = this.bpmnElement.businessObject?.messageRef?.id || "-1";
    },
    openMessageModel() {
      this.messageModelVisible = true;
      this.newMessageForm = {};
    },
    createNewMessage() {
      if (this.messageMap[this.newMessageForm.id]) {
        this.$message.error("该消息已存在,请修改id后重新保存");
        return;
      }
      const newMessage = window.bpmnInstances.moddle.create("bpmn:Message", this.newMessageForm);
      this.bpmnRootElements.push(newMessage);
      this.messageMap[this.newMessageForm.id] = this.newMessageForm.name
      this.bpmnMessageRefsMap[this.newMessageForm.id] = newMessage;
      this.messageModelVisible = false;
    },
    updateTaskMessage(messageId) {
      if (messageId === "-1") {
        window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
          messageRef: null
        });
      } else {
        window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
          messageRef: this.bpmnMessageRefsMap[messageId]
        });
      }
    }
  },
  beforeUnmount() {
    this.bpmnElement = null;
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/task/task-components/ScriptTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
<template>
  <div style="margin-top: 16px">
    <el-form-item label="脚本格式">
      <el-input v-model="scriptTaskForm.scriptFormat" clearable @input="updateElementTask()" @change="updateElementTask()" />
    </el-form-item>
    <el-form-item label="脚本类型">
      <el-select v-model="scriptTaskForm.scriptType">
        <el-option label="内联脚本" value="inline" />
        <el-option label="外部资源" value="external" />
      </el-select>
    </el-form-item>
    <el-form-item label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
      <el-input
        v-model="scriptTaskForm.script"
        type="textarea"
        resize="vertical"
        :autosize="{ minRows: 2, maxRows: 4 }"
        clearable
        @input="updateElementTask()"
        @change="updateElementTask()"
      />
    </el-form-item>
    <el-form-item label="资源地址" v-show="scriptTaskForm.scriptType === 'external'">
      <el-input v-model="scriptTaskForm.resource" clearable @input="updateElementTask()" @change="updateElementTask()" />
    </el-form-item>
    <el-form-item label="结果变量">
      <el-input v-model="scriptTaskForm.resultVariable" clearable @input="updateElementTask()" @change="updateElementTask()" />
    </el-form-item>
  </div>
</template>
<script>
export default {
  name: "ScriptTask",
  props: {
    id: String,
    type: String
  },
  data() {
    return {
      defaultTaskForm: {
        scriptFormat: "",
        script: "",
        resource: "",
        resultVariable: ""
      },
      scriptTaskForm: {}
    };
  },
  watch: {
    id: {
      immediate: true,
      handler() {
        this.bpmnElement = window.bpmnInstances.bpmnElement;
        this.$nextTick(() => this.resetTaskForm());
      }
    }
  },
  methods: {
    resetTaskForm() {
      for (let key in this.defaultTaskForm) {
        let value = this.bpmnElement?.businessObject[key] || this.defaultTaskForm[key];
        this.scriptTaskForm[key] = value
      }
      this.scriptTaskForm["scriptType"] = this.scriptTaskForm.script ? "inline" : "external"
    },
    updateElementTask() {
      let taskAttr = Object.create(null);
      taskAttr.scriptFormat = this.scriptTaskForm.scriptFormat || null;
      taskAttr.resultVariable = this.scriptTaskForm.resultVariable || null;
      if (this.scriptTaskForm.scriptType === "inline") {
        taskAttr.script = this.scriptTaskForm.script || null;
        taskAttr.resource = null;
      } else {
        taskAttr.resource = this.scriptTaskForm.resource || null;
        taskAttr.script = null;
      }
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
    }
  },
  beforeUnmount() {
    this.bpmnElement = null;
  }
};
</script>
ruoyi-ui/apps/web-antd/src/package/penal/task/task-components/UserTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,684 @@
<template>
  <div style="margin-top: 16px">
    <el-row>
      <h4 class="mb-10 fw-700">审批人设置</h4>
      <el-radio-group v-model="dataType" @change="changeDataType">
        <el-radio
          v-for="item in USER_TASK_GROUP"
          :key="item.value"
          :label="item.value"
        >{{item.label}}</el-radio>
      </el-radio-group>
    </el-row>
    <el-row>
      <el-col :span="24" v-if="dataType === USER_TASK_GROUP[0].value">
        <el-tag v-for="userText in selectedUser.text" :key="userText" effect="plain" closable @close="handleClose(userText)">
          {{userText}}
        </el-tag>
        <div class="element-drawer__button">
          <el-button size="default" type="primary" :icon="Plus" @click="onSelectUsers()">添加用户</el-button>
        </div>
      </el-col>
      <el-col :span="24" v-else-if="dataType === USER_TASK_GROUP[1].value">
        <el-select v-model="roleIds" multiple size="default" placeholder="请选择角色" @change="changeSelectRoles" style="width: 100%">
          <el-option
            v-for="item in roleOptions"
            :key="item.roleId"
            :label="item.roleName"
            :value="`ROLE${item.roleId}`"
            :disabled="item.status === 1">
          </el-option>
        </el-select>
      </el-col>
      <el-col :span="24" v-else-if="dataType === USER_TASK_GROUP[2].value">
        <el-tree-select
          v-model="deptIds"
          :data="deptTreeData"
          :props="deptProps"
           value-key="id"
          Â placeholder="选择部门"
           check-strictly
           clearable
           @clear="clearDept"
           ref="selectTree"
           @node-click="checkedDeptChangeNode"
           />
      </el-col>
    </el-row>
    <el-row>
      <el-col :span="24" v-show="showMultiFlog">
        <el-divider />
        <h4><b>多实例审批方式</b></h4>
        <el-row>
          <el-radio-group v-model="multiLoopType" @change="changeMultiLoopType()" class="flex-column radio-group-flex">
            <el-row v-for="item in MULTI_INSTANCE_TYPE" :key="item.value">
              <el-radio :label="item.value">{{item.label}}</el-radio>
            </el-row>
          </el-radio-group>
        </el-row>
        <el-row v-if="multiLoopType !== MULTI_INSTANCE_TYPE[0].value">
          <el-tooltip content="开启后,实例需按顺序轮流审批" placement="top-start" @click.stop.prevent>
            <el-icon><InfoFilled /></el-icon>
          </el-tooltip>
          <span class="custom-label">顺序审批:</span>
          <el-switch v-model="isSequential" @change="changeMultiLoopType()" />
        </el-row>
      </el-col>
    </el-row>
    <!-- å€™é€‰ç”¨æˆ·å¼¹çª— -->
    <el-dialog title="候选用户" v-model="userOpen" width="60%" append-to-body>
      <el-row type="flex" :gutter="20">
        <!--部门数据-->
        <el-col :span="7">
          <el-card shadow="never" style="height: 100%">
            <div class="head-container">
              <el-input
                v-model="queryParamsDeptIds"
                placeholder="请输入部门名称"
                clearable
                size="small"
                :prefix-icon="Search"
                style="margin-bottom: 20px"
              />
              <el-tree
                :data="deptOptions"
                :props="deptProps"
                :expand-on-click-node="false"
                :filter-node-method="filterNode"
                ref="deptTree"
                :default-expand-all="false"
                node-key="id"
                highlight-current
                @current-change="handleNodeClick"
              />
            </div>
          </el-card>
        </el-col>
        <el-col :span="17">
          <el-form
            :model="queryParams"
            ref="queryForm"
            size="small"
            :inline="true"
            label-width="68px"
          >
            <el-form-item label="用户名称" prop="nickName">
              <el-input
                v-model="queryParams.nickName"
                placeholder="请输入用户名称"
                clearable
                style="width: 150px"
                @keyup.enter.native="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-table
            ref="multipleTable"
            height="600"
            v-loading="tableLoading"
            :data="userTableList"
            border
            @row-click="clickRow"
            @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="50" align="center" />
            <el-table-column label="用户名" align="center" prop="nickName" />
            <el-table-column label="部门" align="center" prop="dept.deptName" />
          </el-table>
          <pagination
            :total="userTotal"
            v-model:page="queryParams.pageNum"
            v-model:limit="queryParams.pageSize"
            @pagination="getList"
          />
        </el-col>
      </el-row>
      <template #footer class="dialog-footer">
        <el-button type="primary" size="default" @click="handleTaskUserComplete">ç¡® å®š</el-button>
        <el-button size="default" @click="userOpen = false">取 æ¶ˆ</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import { userList, getDeptTree } from "#/api/system/user";
import { roleList } from "#/api/system/role";
import { uniqBy } from 'lodash'
const USER_TASK_GROUP = [
  {
    label: "指定用户",
    value: "USERS",
  },
  {
    label: "角色",
    value: "ROLES",
  },
  {
    label: "部门",
    value: "DEPTS",
  },
  {
    label: "发起人",
    value: "INITIATOR",
  },
]
const USER_TASK_FORM = {
  dataType: '',
  assignee: '',
  candidateUsers: '',
  candidateGroups: '',
  text: '',
  deptId: '',
  // dueDate: '',
  // followUpDate: '',
  // priority: ''
}
const MULTI_INSTANCE_TYPE = [
  { label: "无", value: 'Null' },
  { label: "会签(需所有审批人同意)", value: 'SequentialMultiInstance' },
  { label: "或签(一名审批人同意即可)", value: 'ParallelMultiInstance' },
  { label: "自定义", value: 'DiyMultiInstance' },
]
export default {
  name: "UserTask",
  props: {
    id: String,
    type: String
  },
  setup() {
    return { Plus, Search, Refresh  }
  },
  data() {
    return {
      tableLoading: false,
      dataType: USER_TASK_GROUP[0].value,
      selectedUser: {
        ids: [],
        text: []
      },
      userOpen: false,
      deptName: undefined,
      deptOptions: [], //部门列表
      deptProps: {
        children: "children",
        label: "label",
        value: 'id'
      },
      userTableList: [],
      userTotal: 0,
      selectedUserDate: [],
      roleOptions: [],
      roleIds: [],
      deptTreeData: [],
      deptIds: [],
      queryParamsDeptIds:[],
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        deptId: undefined,
        nickName: undefined,
        pageNum: 1,
        pageSize: 20,
      },
      showMultiFlog: false,
      isSequential: false,
      multiLoopType: MULTI_INSTANCE_TYPE[0].value,
    };
  },
  watch: {
    id: {
      immediate: true,
      handler() {
        this.bpmnElement = window.bpmnInstances.bpmnElement;
        this.$nextTick(() => this.resetTaskForm());
      }
    },
    // æ ¹æ®åç§°ç­›é€‰éƒ¨é—¨æ ‘
    queryParamsDeptIds(val) {
      this.$nextTick(() => {
        this.$refs.deptTree.filter(val);
     })
    }
  },
  computed: {
    USER_TASK_GROUP() { return USER_TASK_GROUP },
    MULTI_INSTANCE_TYPE() { return MULTI_INSTANCE_TYPE }
  },
  methods: {
    // åˆå§‹åŒ–变量
    async resetTaskForm() {
      const bpmnElementObj = this.bpmnElement?.businessObject;
      if (!bpmnElementObj) {
        return;
      }
      this.clearOptionsData()
      this.dataType = bpmnElementObj.dataType;
      if (this.dataType === USER_TASK_GROUP[0].value) {
        let userIdData = bpmnElementObj['candidateUsers'] || bpmnElementObj['assignee'];
        let userText = bpmnElementObj.text || [];
        if (userIdData && userIdData.toString().length > 0 && userText && userText.length > 0) {
          this.selectedUser.ids = userIdData.toString().split(',');
          this.selectedUser.text = userText.split(',');
        }
        if (this.selectedUser.ids.length > 1) {
          this.showMultiFlog = true;
        }
      } else if (this.dataType === USER_TASK_GROUP[1].value) {
        // èŽ·å–è§’è‰²åˆ—è¡¨
        this.getRoleOptions();
        let roleIdData = bpmnElementObj['candidateGroups'] || [];
        if (roleIdData && roleIdData.length > 0) {
          this.roleIds = roleIdData.split(',')
        }
        this.showMultiFlog = true;
      } else if (this.dataType === USER_TASK_GROUP[2].value) {
        // èŽ·å–éƒ¨é—¨åˆ—è¡¨
        await this.getDeptTreeData();
        let deptIdData = bpmnElementObj['candidateGroups'] || [];
        if (deptIdData && deptIdData.length > 0) {
          this.deptIds = deptIdData.split(',');
          // å›žæ˜¾
          this.checkedDeptChange(this.deptIds);
        }
        this.showMultiFlog = true;
      }
      this.getElementLoop(bpmnElementObj);
    },
    // æ¸…空选项数据
    clearOptionsData() {
      this.selectedUser.ids = [];
      this.selectedUser.text = [];
      this.roleIds = [];
      this.deptIds = [];
    },
    // æ›´æ–°èŠ‚ç‚¹æ•°æ®
    updateElementTask() {
      const taskObj = Object.assign({}, { ...USER_TASK_FORM })
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskObj);
    },
    // æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
    getDeptOptions() {
      return new Promise((resolve, reject) => {
        if (!this.deptOptions || this.deptOptions.length <= 0) {
          // é€‰æ‹©éƒ¨é—¨æ ‘ æ•°æ®ç¬¬ä¸€æ¬¡è¯·æ±‚
          getDeptTree().then(response => {
            this.deptOptions = [].concat(
              [{ id: '', label: '全部', children: [] }],
              [...response.data]
            );
            this.handleNodeClick(this.deptOptions[0])
            resolve()
          })
        } else {
          // ç¡®ä¿é€‰æ‹©éƒ¨é—¨æ ‘已初始化 ä½†ä»è¯·æ±‚表格数据 å¤é€‰æ¡†å›žæ˜¾
          this.handleNodeClick(this.deptOptions[0])
        }
      });
    },
    // æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构(含部门前缀)
    getDeptTreeData() {
      function refactorTree(data) {
        return data.map(node => {
          let treeData = { id: `DEPT${node.id}`, label: node.label, parentId: node.parentId, weight: node.weight };
          if (node.children && node.children.length > 0) {
            treeData.children = refactorTree(node.children);
          }
          return treeData;
        });
      }
      return new Promise((resolve, reject) => {
        if (!this.deptTreeData || this.deptTreeData.length <= 0) {
          getDeptTree().then((res) => {
            this.deptTreeData = refactorTree(res.data);
            resolve()
          }).catch(() => {
            reject()
          })
        } else {
          resolve()
        }
      })
    },
    // æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
    getRoleOptions() {
      if (!this.roleOptions || this.roleOptions.length <= 0) {
        roleList().then(response => this.roleOptions = response.rows);
      }
    },
    // æŸ¥è¯¢ç”¨æˆ·åˆ—表
    getList() {
      this.tableLoading = true
      userList({ ...this.queryParams }).then(response => {
        this.userTableList = response.rows;
        this.userTotal = response.total;
       // this.$nextTick(() => {
         // setTimeout(() => {
         //   this.selectedUserDate.forEach(each => {
          //    const findObj = this.userTableList.find(find => find.userId === each.userId) || {}
          //    if(Object.keys(findObj).length > 0) {
         //       this.$refs.multipleTable.toggleRowSelection(findObj)
         //     }
        //    })
        //  }, 500)
       // })
      }).finally(() => {
        this.tableLoading = false
      })
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.$refs.queryForm.resetFields();
      this.$refs.queryForm.clearValidate();
      this.queryParams = {
        deptId: '',
        nickName: undefined,
        pageSize: 20
      };
      this.handleQuery();
    },
    // ç­›é€‰èŠ‚ç‚¹
    filterNode(value, data) {
      if (!value) return true;
      return data.label.indexOf(value) !== -1;
    },
    // èŠ‚ç‚¹å•å‡»äº‹ä»¶
    handleNodeClick(data) {
      this.queryParams.deptId = data.id;
      this.handleQuery();
    },
    // å…³é—­æ ‡ç­¾
    handleClose(tag) {
      this.selectedUserDate.splice(this.selectedUserDate.indexOf(tag), 1);
      this.handleTaskUserChange()
    },
    /**
     * é€‰æ‹©è¡Œ
     */
    clickRow(row) {
      this.$refs.multipleTable.toggleRowSelection(row);
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      if(selection.length) {
        const concatData = [].concat(selection, this.selectedUserDate);
        this.selectedUserDate = uniqBy(concatData, 'userId');
      }
    },
    // æ·»åŠ ç”¨æˆ·
    onSelectUsers() {
      this.$nextTick(()=>
       {
          setTimeout(() => {
            this.$refs.multipleTable.clearSelection(),100
          })
      })
      this.getDeptOptions()
      this.userOpen = true;
    },
    // æ·»åŠ ç”¨æˆ·-确认按钮
    handleTaskUserComplete() {
      if (!this.selectedUserDate || this.selectedUserDate.length <= 0) {
        this.$modal.msgError('请选择用户');
        return;
      }
      USER_TASK_FORM.dataType = USER_TASK_GROUP[0].value;
      this.handleTaskUserChange()
      this.changeMultiLoopType();
      this.userOpen = false;
    },
    // æŒ‡å®šç”¨æˆ·æ”¹å˜ç¡®å®š
    handleTaskUserChange() {
      this.selectedUser.text = this.selectedUserDate.map(item => item.nickName) || [];
      if (this.selectedUserDate.length === 1) {
        let data = this.selectedUserDate[0];
        USER_TASK_FORM.assignee = data.userId;
        USER_TASK_FORM.text = data.nickName;
        USER_TASK_FORM.candidateUsers = null;
        this.showMultiFlog = false;
        this.multiLoopType = MULTI_INSTANCE_TYPE[0].value;
      } else {
        USER_TASK_FORM.candidateUsers = this.selectedUserDate.map(item => item.userId).join() || null;
        USER_TASK_FORM.text = this.selectedUserDate.map(item => item.nickName).join() || null;
        USER_TASK_FORM.assignee = null;
        this.showMultiFlog = true;
      }
      this.selectedUserDate=[];
      this.updateElementTask();
    },
    // é€‰ä¸­è§’色
    changeSelectRoles(roleArray) {
      let groups = null;
      let text = null;
      if (roleArray && roleArray.length > 0) {
        USER_TASK_FORM.dataType = USER_TASK_GROUP[1].value;
        groups = roleArray.join() || null;
        let textArr = this.roleOptions.filter(k => roleArray.indexOf(`ROLE${k.roleId}`) >= 0);
        text = textArr?.map(k => k.roleName).join() || null;
      } else {
        USER_TASK_FORM.dataType = null;
        this.multiLoopType = MULTI_INSTANCE_TYPE[0].value;
      }
      USER_TASK_FORM.candidateGroups = groups;
      USER_TASK_FORM.text = text;
      this.updateElementTask();
      this.changeMultiLoopType();
    },
    checkedDeptChangeNode(node){
     this.checkedDeptChange([node.id]);
    },
    clearDept(){
     USER_TASK_FORM.candidateGroups = "";
     USER_TASK_FORM.text = "";
     USER_TASK_FORM.assignee ="";
     USER_TASK_FORM.candidateUsers ="";
     this.updateElementTask();
     this.multiLoopType = MULTI_INSTANCE_TYPE[0].value;
     this.changeMultiLoopType();
    },
    // é€‰ä¸­éƒ¨é—¨
    checkedDeptChange(deptArray) {
      let groups = null;
      let text = null;
      this.deptIds = deptArray[0];
      if (deptArray && deptArray.length > 0) {
        USER_TASK_FORM.dataType = USER_TASK_GROUP[2].value;
        groups = deptArray.join(',') || null;
        let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
        const filterData = this.filterTreeData(treeStarkData, deptArray);
        text = filterData.map(item => item.label).join(',') || null
      } else {
        USER_TASK_FORM.dataType = null;
        this.multiLoopType = MULTI_INSTANCE_TYPE[0].value;
      }
      USER_TASK_FORM.candidateGroups = groups;
      USER_TASK_FORM.text = text;
      this.updateElementTask();
      this.changeMultiLoopType();
    },
   filterTreeData  (data, deptArray)  {
      const result = [];
      const traverse = (items) => {
        for (const item of items) {
          // æ£€æŸ¥å½“前节点是否在 deptArray ä¸­
          if (deptArray.includes(item.id)) {
            result.push(item); // å¦‚果在,添加到结果数组中
          }
          // å¦‚果有 children,递归处理
          if (item.children) {
            traverse(item.children);
          }
        }
      };
      traverse(data); // å¼€å§‹éåކ
      return result; // è¿”回扁平化的结果数组
  },
    // ä¿®æ”¹å®¡æ‰¹äººç±»åž‹
    changeDataType(val) {
      if (val === USER_TASK_GROUP[1].value || val === USER_TASK_GROUP[2].value || (val === USER_TASK_GROUP[0].value && this.selectedUser.ids.length > 1)) {
        this.showMultiFlog = true;
      } else {
        this.showMultiFlog = false;
      }
      this.multiLoopType = MULTI_INSTANCE_TYPE[0].value;
      this.changeMultiLoopType();
      // æ¸…空 USER_TASK_FORM æ‰€æœ‰å±žæ€§å€¼
      Object.keys(USER_TASK_FORM).forEach(key => USER_TASK_FORM[key] = null);
      USER_TASK_FORM.dataType = val;
      if (val === USER_TASK_GROUP[0].value) {
        if (this.selectedUser && this.selectedUser.ids && this.selectedUser.ids.length > 0) {
          if (this.selectedUser.ids.length === 1) {
            USER_TASK_FORM.assignee = this.selectedUser.ids[0];
          } else {
            USER_TASK_FORM.candidateUsers = this.selectedUser.ids.join()
          }
          USER_TASK_FORM.text = this.selectedUser.text?.join() || null
        }
      } else if (val === USER_TASK_GROUP[1].value) {
        this.getRoleOptions();
        if (this.roleIds && this.roleIds.length > 0) {
          USER_TASK_FORM.candidateGroups = this.roleIds.join() || null;
          let textArr = this.roleOptions.filter(k => this.roleIds.indexOf(`ROLE${k.roleId}`) >= 0);
          USER_TASK_FORM.text = textArr?.map(k => k.roleName).join() || null;
        }
      } else if (val === USER_TASK_GROUP[2].value) {
        this.getDeptTreeData();
        if (this.deptIds && this.deptIds.length > 0) {
          USER_TASK_FORM.candidateGroups = this.deptIds.join() || null;
          let textArr = []
          let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
          this.deptIds.forEach(id => {
            let stark = []
            stark = stark.concat(treeStarkData);
            while(stark.length) {
              let temp = stark.shift();
              if(temp.children) {
                stark = temp.children.concat(stark);
              }
              if(id === temp.id) {
                textArr.push(temp);
              }
            }
          })
          USER_TASK_FORM.text = textArr?.map(k => k.label).join() || null;
        }
      } else {
        USER_TASK_FORM.assignee = "${initiator}";
        USER_TASK_FORM.text = "流程发起人";
      }
      this.updateElementTask();
    },
    // åˆå§‹åŒ–多实例变量
    getElementLoop(businessObject) {
      if (!businessObject.loopCharacteristics) {
        this.multiLoopType = MULTI_INSTANCE_TYPE[0].value;
        return;
      }
      this.isSequential = businessObject.loopCharacteristics.isSequential;
      if (businessObject.loopCharacteristics.completionCondition) {
        if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= nrOfInstances}") {
          this.multiLoopType = MULTI_INSTANCE_TYPE[1].value; // ä¼šç­¾
        } else if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= 0}") {
          this.multiLoopType = MULTI_INSTANCE_TYPE[2].value; // æˆ–ç­¾
        } else {
          this.multiLoopType = MULTI_INSTANCE_TYPE[3].value; // è‡ªå®šä¹‰
        }
      }
    },
    // æ›´æ–°å¤šå®žä¾‹å˜é‡
    changeMultiLoopType() {
      // å–消多实例配置
      if (this.multiLoopType === MULTI_INSTANCE_TYPE[0].value) {
        window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null, candidateUsers: null });
        return;
      }
      this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: this.isSequential });
      // æ›´æ–°å¤šå®žä¾‹é…ç½®
      window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
        loopCharacteristics: this.multiLoopInstance,
        assignee: '${assignee}'
      });
      // è®¾ç½®å®¡æ‰¹äºº
      USER_TASK_FORM.assignee = '${assignee}';
      // å®Œæˆæ¡ä»¶
      let completionCondition = null;
      switch (this.multiLoopType) {
        case MULTI_INSTANCE_TYPE[1].value:
          completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances >= nrOfInstances}" });
          break;
        case MULTI_INSTANCE_TYPE[2].value:
          completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances > 0}" });
          break;
        default:
          completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "" });
          break;
      }
      // æ›´æ–°æ¨¡å—属性信息
      window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
        collection: MULTI_INSTANCE_TYPE[1].value || MULTI_INSTANCE_TYPE[2].value ? '${multiInstanceHandler.getUserIds(execution)}' : "",
        elementVariable: MULTI_INSTANCE_TYPE[1].value || MULTI_INSTANCE_TYPE[2].value ? 'assignee' : "",
        completionCondition
      });
    },
  },
  beforeDestroy() {
    this.bpmnElement = null;
  },
};
</script>
<style scoped lang="scss">
.el-row .el-radio-group {
  margin-bottom: 15px;
  .el-radio {
    line-height: 28px;
  }
}
.el-tag {
  margin-bottom: 10px;
  + .el-tag {
    margin-left: 10px;
  }
}
.custom-label {
  padding-left: 5px;
  font-weight: 500;
  font-size: 14px;
  color: #606266;
}
.head-container {
  height: 480px;
  overflow-y: auto;
}
.el-row .radio-group-flex {
  margin: 15px 0 0 0;
  justify-items: flex-start;
  align-items: flex-start !important;
}
</style>
ruoyi-ui/apps/web-antd/src/package/theme/element-variables.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
/* æ”¹å˜ä¸»é¢˜è‰²å˜é‡ */
$--color-primary: #1890ff;
$--color-danger: #ff4d4f;
/* æ”¹å˜ icon å­—体路径变量,必需 */
// $--font-path: '~element-ui/lib/theme-chalk/fonts';
// @import "~element-ui/packages/theme-chalk/src/index";
.el-table td,
.el-table th {
  color: #333;
}
.el-drawer__header {
  padding: 16px 16px 8px 16px;
  margin: 0;
  line-height: 24px;
  font-size: 18px;
  color: #303133;
  box-sizing: border-box;
  border-bottom: 1px solid #e8e8e8;
}
div[class^="el-drawer"]:focus,
span:focus {
  outline: none;
}
.el-drawer__body {
  box-sizing: border-box;
  padding: 16px;
  width: 100%;
  overflow-y: auto;
}
.el-dialog {
  margin-top: 50vh !important;
  transform: translateY(-50%);
  overflow: hidden;
}
.el-dialog__wrapper {
  overflow: hidden;
  max-height: 100vh;
}
.el-dialog__header {
  padding: 16px 16px 8px 16px;
  box-sizing: border-box;
  border-bottom: 1px solid #e8e8e8;
}
.el-dialog__body {
  padding: 16px;
  max-height: 80vh;
  box-sizing: border-box;
  overflow-y: auto;
}
.el-dialog__footer {
  padding: 16px;
  box-sizing: border-box;
  border-top: 1px solid #e8e8e8;
}
.el-dialog__close {
  font-weight: 600;
}
.el-select {
  width: 100%;
}
.el-divider:not(.el-divider--horizontal) {
  margin: 0 8px ;
}
.el-divider.el-divider--horizontal {
  margin: 16px 0;
}
ruoyi-ui/apps/web-antd/src/package/theme/index.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,168 @@
@use "bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css";
@use "bpmn-js/dist/assets/diagram-js.css";
@use "bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
@use "bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
@use "./process-designer.scss";
@use "./process-panel.scss";
$success-color: #4eb819;
$primary-color: #409EFF;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$cancel-color: #909399;
.process-viewer {
  position: relative;
  border: 1px solid #EFEFEF;
  background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
  .success-arrow {
    fill: $success-color;
    stroke: $success-color;
  }
  .success-conditional {
    fill: white;
    stroke: $success-color;
  }
  .fail-arrow {
    fill: $warning-color;
    stroke: $warning-color;
  }
  .fail-conditional {
    fill: white;
    stroke: $warning-color;
  }
  .success.djs-connection {
    .djs-visual path {
      stroke: $success-color!important;
      marker-end: url(#sequenceflow-end-white-success)!important;
    }
  }
  .success.djs-connection.condition-expression {
    .djs-visual path {
      marker-start: url(#conditional-flow-marker-white-success)!important;
    }
  }
  .success.djs-shape {
    .djs-visual rect {
      stroke: $success-color!important;
      fill: $success-color!important;
      fill-opacity: 0.15!important;
    }
    .djs-visual polygon {
      stroke: $success-color!important;
    }
    .djs-visual path:nth-child(2) {
      stroke: $success-color!important;
      fill: $success-color!important;
    }
    .djs-visual circle {
      stroke: $success-color!important;
      fill: $success-color!important;
      fill-opacity: 0.15!important;
    }
  }
  .primary.djs-shape {
    .djs-visual rect {
      stroke: $primary-color!important;
      fill: $primary-color!important;
      fill-opacity: 0.15!important;
    }
    .djs-visual polygon {
      stroke: $primary-color!important;
    }
    .djs-visual circle {
      stroke: $primary-color!important;
      fill: $primary-color!important;
      fill-opacity: 0.15!important;
    }
  }
  .warning.djs-connection {
    .djs-visual path {
      stroke: $warning-color!important;
      marker-end: url(#sequenceflow-end-white-fail)!important;
    }
  }
  .warning.djs-connection.condition-expression {
    .djs-visual path {
      marker-start: url(#conditional-flow-marker-white-fail)!important;
    }
  }
  .warning.djs-shape {
    .djs-visual rect {
      stroke: $warning-color!important;
      fill: $warning-color!important;
      fill-opacity: 0.15!important;
    }
    .djs-visual polygon {
      stroke: $warning-color!important;
    }
    .djs-visual path:nth-child(2) {
      stroke: $warning-color!important;
      fill: $warning-color!important;
    }
    .djs-visual circle {
      stroke: $warning-color!important;
      fill: $warning-color!important;
      fill-opacity: 0.15!important;
    }
  }
  .danger.djs-shape {
    .djs-visual rect {
      stroke: $danger-color!important;
      fill: $danger-color!important;
      fill-opacity: 0.15!important;
    }
    .djs-visual polygon {
      stroke: $danger-color!important;
    }
    .djs-visual circle {
      stroke: $danger-color!important;
      fill: $danger-color!important;
      fill-opacity: 0.15!important;
    }
  }
  .cancel.djs-shape {
    .djs-visual rect {
      stroke: $cancel-color!important;
      fill: $cancel-color!important;
      fill-opacity: 0.15!important;
    }
    .djs-visual polygon {
      stroke: $cancel-color!important;
    }
    .djs-visual circle {
      stroke: $cancel-color!important;
      fill: $cancel-color!important;
      fill-opacity: 0.15!important;
    }
  }
}
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
  display: none;
}
ruoyi-ui/apps/web-antd/src/package/theme/process-designer.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,155 @@
@import "bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css";
@import "diagram-js-minimap/assets/diagram-js-minimap.css";
// è¾¹æ¡†è¢« token-simulation æ ·å¼è¦†ç›–了
.djs-palette {
  background: var(--palette-background-color);
  border: solid 1px var(--palette-border-color) !important;
  border-radius: 2px;
}
.my-process-designer {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  .my-process-designer__header {
    width: 100%;
    min-height: 36px;
    .el-button {
      text-align: center;
    }
    .el-button-group {
      margin: 4px;
    }
    .el-tooltip__popper {
      .el-button {
        width: 100%;
        text-align: left;
        padding-left: 8px;
        padding-right: 8px;
      }
      .el-button:hover {
        background: rgba(64, 158, 255, 0.8);
        color: #ffffff;
      }
    }
    .align {
      position: relative;
      i {
        &:after {
          content: "|";
          position: absolute;
          transform: rotate(90deg) translate(200%, -10%);
        }
      }
    }
    .align.align-left i {
      transform: rotate(90deg);
    }
    .align.align-right i {
      transform: rotate(-90deg);
    }
    .align.align-top i {
      transform: rotate(180deg);
    }
    .align.align-bottom i {
      transform: rotate(0deg);
    }
    .align.align-center i {
      transform: rotate(90deg);
      &:after {
        transform: rotate(90deg) translate(0, -10%);
      }
    }
    .align.align-middle i {
      transform: rotate(0deg);
      &:after {
        transform: rotate(90deg) translate(0, -10%);
      }
    }
  }
  .my-process-designer__container {
    display: inline-flex;
    width: 100%;
    flex: 1;
    .my-process-designer__canvas {
      flex: 1;
      height: 100%;
      position: relative;
      background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
      repeat !important;
      div.toggle-mode {
        display: none;
      }
    }
    .my-process-designer__property-panel {
      height: 100%;
      overflow: scroll;
      overflow-y: auto;
      z-index: 10;
      * {
        box-sizing: border-box;
      }
    }
    svg {
      width: 100%;
      height: 100%;
      min-height: 100%;
      overflow: hidden;
    }
  }
}
//侧边栏配置
.djs-palette.open {
  .djs-palette-entries {
    div[class^="bpmn-icon-"]:before,
    div[class*="bpmn-icon-"]:before {
      line-height: unset;
    }
    div.entry {
      position: relative;
    }
    div.entry:hover {
      &::after {
        width: max-content;
        content: attr(title);
        vertical-align: text-bottom;
        position: absolute;
        right: -10px;
        top: 0;
        bottom: 0;
        overflow: hidden;
        transform: translateX(100%);
        font-size: 0.5em;
        display: inline-block;
        text-decoration: inherit;
        font-variant: normal;
        text-transform: none;
        background: #fafafa;
        box-shadow: 0 0 6px #eeeeee;
        border: 1px solid #cccccc;
        box-sizing: border-box;
        padding: 0 16px;
        border-radius: 4px;
        z-index: 100;
      }
    }
  }
}
pre {
  margin: 0;
  height: 100%;
  overflow: hidden;
  max-height: calc(80vh - 32px);
  overflow-y: auto;
}
.hljs {
  word-break: break-word;
  white-space: pre-wrap;
}
.hljs * {
  font-family: Consolas, Monaco, monospace;
}
ruoyi-ui/apps/web-antd/src/package/theme/process-panel.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
.process-panel__container {
  box-sizing: border-box;
  padding: 0 8px;
  border-left: 1px solid #eeeeee;
  box-shadow: 0 0 8px #cccccc;
  max-height: 100%;
  overflow-y: scroll;
}
.panel-tab__title {
  font-weight: 600;
  padding: 0 8px;
  font-size: 1.1em;
  line-height: 1.2em;
  i {
    margin-right: 8px;
    font-size: 1.2em;
  }
}
.panel-tab__content {
  width: 100%;
  box-sizing: border-box;
  border-top: 1px solid #eeeeee;
  padding: 8px 16px;
  .panel-tab__content--title {
    display: flex;
    justify-content: space-between;
    padding-bottom: 8px;
    span {
      flex: 1;
      text-align: left;
    }
  }
}
.element-property {
  width: 100%;
  display: flex;
  align-items: flex-start;
  margin: 8px 0;
  .element-property__label {
    display: block;
    width: 90px;
    text-align: right;
    overflow: hidden;
    padding-right: 12px;
    line-height: 32px;
    font-size: 14px;
    box-sizing: border-box;
  }
  .element-property__value {
    flex: 1;
    line-height: 32px;
  }
  .el-form-item {
    width: 100%;
    margin-bottom: 0;
    padding-bottom: 18px;
  }
}
.list-property {
  flex-direction: column;
  .element-listener-item {
    width: 100%;
    display: inline-grid;
    grid-template-columns: 16px auto 32px 32px;
    grid-column-gap: 8px;
  }
  .element-listener-item + .element-listener-item {
    margin-top: 8px;
  }
}
.listener-drawer {
  .el-drawer__header {
    margin: 0PX;
    padding: 10PX;
    border-bottom: 1px solid #eeeeee;
    .el-drawer__title {
      font-size: 18px
    }
  }
  .el-drawer__body {
    .el-form-item__label {
      font-size: 14PX;
      font-weight: 500;
    }
    .input-with-select .el-input-group__prepend {
      background-color: var(--el-fill-color-blank);
      padding: 0;
    }
  }
}
.listener-filed__title {
  display: inline-flex;
  width: 100%;
  justify-content: space-between;
  align-items: center;
  margin-top: 0;
  span {
    // width: 200px;
    text-align: left;
    font-size: 14px;
  }
  i {
    margin-right: 8px;
  }
}
.element-drawer__button {
  margin-top: 8px;
  width: 100%;
  display: inline-flex;
  justify-content: space-around;
}
.element-drawer__button > .el-button {
  width: 100%;
}
.el-collapse-item__content {
  padding-bottom: 0;
}
.el-input.is-disabled .el-input__inner {
  color: #999999;
}
.el-form-item.el-form-item--mini {
  margin-bottom: 0;
  & + .el-form-item {
    margin-top: 16px;
  }
}
ruoyi-ui/apps/web-antd/src/package/utils.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
// åˆ›å»ºç›‘听器实例
export function createListenerObject(options, isTask, prefix) {
  const listenerObj = Object.create(null);
  listenerObj.event = options.event;
  isTask && (listenerObj.id = options.id); // ä»»åŠ¡ç›‘å¬å™¨ç‰¹æœ‰çš„ id å­—段
  switch (options.listenerType) {
    case 'scriptListener':
      listenerObj.script = createScriptObject(options, prefix);
      break;
    case 'expressionListener':
      listenerObj.expression = options.expression;
      break;
    case 'delegateExpressionListener':
      listenerObj.delegateExpression = options.delegateExpression;
      break;
    default:
      listenerObj.class = options.class;
  }
  // æ³¨å…¥å­—段
  if (options.fields) {
    listenerObj.fields = options.fields.map(field => {
      return createFieldObject(field, prefix);
    });
  }
  // ä»»åŠ¡ç›‘å¬å™¨çš„ å®šæ—¶å™¨ è®¾ç½®
  if (isTask && options.event === 'timeout' && !!options.eventDefinitionType) {
    const timeDefinition = window.bpmnInstances.moddle.create('bpmn:FormalExpression', {
      body: options.eventTimeDefinitions
    });
    const TimerEventDefinition = window.bpmnInstances.moddle.create('bpmn:TimerEventDefinition', {
      id: `TimerEventDefinition_${uuid(8)}`,
      [`time${options.eventDefinitionType.replace(/^\S/, s => s.toUpperCase())}`]: timeDefinition
    });
    listenerObj.eventDefinitions = [TimerEventDefinition];
  }
  return window.bpmnInstances.moddle.create(`${prefix}:${isTask ? 'TaskListener' : 'ExecutionListener'}`, listenerObj);
}
// åˆ›å»º ç›‘听器的注入字段 å®žä¾‹
export function createFieldObject(option, prefix) {
  const { name, fieldType, string, expression } = option;
  const fieldConfig = fieldType === 'string' ? { name, string } : { name, expression };
  return window.bpmnInstances.moddle.create(`${prefix}:Field`, fieldConfig);
}
// åˆ›å»ºè„šæœ¬å®žä¾‹
export function createScriptObject(options, prefix) {
  const { scriptType, scriptFormat, value, resource } = options;
  const scriptConfig = scriptType === 'inlineScript' ? { scriptFormat, value } : { scriptFormat, resource };
  return window.bpmnInstances.moddle.create(`${prefix}:Script`, scriptConfig);
}
// æ›´æ–°å…ƒç´ æ‰©å±•属性
export function updateElementExtensions(element, extensionList) {
  const extensions = window.bpmnInstances.moddle.create('bpmn:ExtensionElements', {
    values: extensionList
  });
  window.bpmnInstances.modeling.updateProperties(element, {
    extensionElements: extensions
  });
}
// åˆ›å»ºä¸€ä¸ªid
export function uuid(length = 8, chars) {
  let result = '';
  const charsString = chars || '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  for (let i = length; i > 0; --i) {
    result += charsString[Math.floor(Math.random() * charsString.length)];
  }
  return result;
}
ruoyi-ui/apps/web-antd/src/utils/min-dash.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,694 @@
/* eslint-disable no-func-assign */
/**
 * Flatten array, one level deep.
 *
 * @param {Array<?>} arr
 *
 * @return {Array<?>}
 */
function flatten(arr) {
  return Array.prototype.concat.apply([], arr);
}
var nativeToString = Object.prototype.toString;
var nativeHasOwnProperty = Object.prototype.hasOwnProperty;
function isUndefined(obj) {
  return obj === undefined;
}
function isDefined(obj) {
  return obj !== undefined;
}
function isNil(obj) {
  return obj == null;
}
function isArray(obj) {
  return nativeToString.call(obj) === '[object Array]';
}
function isObject(obj) {
  return nativeToString.call(obj) === '[object Object]';
}
function isNumber(obj) {
  return nativeToString.call(obj) === '[object Number]';
}
function isFunction(obj) {
  var tag = nativeToString.call(obj);
  return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
}
function isString(obj) {
  return nativeToString.call(obj) === '[object String]';
}
/**
 * Ensure collection is an array.
 *
 * @param {Object} obj
 */
function ensureArray(obj) {
  if (isArray(obj)) {
    return;
  }
  throw new Error('must supply array');
}
/**
 * Return true, if target owns a property with the given key.
 *
 * @param {Object} target
 * @param {String} key
 *
 * @return {Boolean}
 */
function has(target, key) {
  return nativeHasOwnProperty.call(target, key);
}
/**
 * Find element in collection.
 *
 * @param  {Array|Object} collection
 * @param  {Function|Object} matcher
 *
 * @return {Object}
 */
function find(collection, matcher) {
  matcher = toMatcher(matcher);
  var match;
  forEach(collection, function(val, key) {
    if (matcher(val, key)) {
      match = val;
      return false;
    }
  });
  return match;
}
/**
 * Find element index in collection.
 *
 * @param  {Array|Object} collection
 * @param  {Function} matcher
 *
 * @return {Object}
 */
function findIndex(collection, matcher) {
  matcher = toMatcher(matcher);
  var idx = isArray(collection) ? -1 : undefined;
  forEach(collection, function(val, key) {
    if (matcher(val, key)) {
      idx = key;
      return false;
    }
  });
  return idx;
}
/**
 * Find element in collection.
 *
 * @param  {Array|Object} collection
 * @param  {Function} matcher
 *
 * @return {Array} result
 */
function filter(collection, matcher) {
  var result = [];
  forEach(collection, function(val, key) {
    if (matcher(val, key)) {
      result.push(val);
    }
  });
  return result;
}
/**
 * Iterate over collection; returning something
 * (non-undefined) will stop iteration.
 *
 * @param  {Array|Object} collection
 * @param  {Function} iterator
 *
 * @return {Object} return result that stopped the iteration
 */
function forEach(collection, iterator) {
  var val, result;
  if (isUndefined(collection)) {
    return;
  }
  var convertKey = isArray(collection) ? toNum : identity;
  for (var key in collection) {
    if (has(collection, key)) {
      val = collection[key];
      result = iterator(val, convertKey(key));
      if (result === false) {
        return val;
      }
    }
  }
}
/**
 * Return collection without element.
 *
 * @param  {Array} arr
 * @param  {Function} matcher
 *
 * @return {Array}
 */
function without(arr, matcher) {
  if (isUndefined(arr)) {
    return [];
  }
  ensureArray(arr);
  matcher = toMatcher(matcher);
  return arr.filter(function(el, idx) {
    return !matcher(el, idx);
  });
}
/**
 * Reduce collection, returning a single result.
 *
 * @param  {Object|Array} collection
 * @param  {Function} iterator
 * @param  {Any} result
 *
 * @return {Any} result returned from last iterator
 */
function reduce(collection, iterator, result) {
  forEach(collection, function(value, idx) {
    result = iterator(result, value, idx);
  });
  return result;
}
/**
 * Return true if every element in the collection
 * matches the criteria.
 *
 * @param  {Object|Array} collection
 * @param  {Function} matcher
 *
 * @return {Boolean}
 */
function every(collection, matcher) {
  return !!reduce(collection, function(matches, val, key) {
    return matches && matcher(val, key);
  }, true);
}
/**
 * Return true if some elements in the collection
 * match the criteria.
 *
 * @param  {Object|Array} collection
 * @param  {Function} matcher
 *
 * @return {Boolean}
 */
function some(collection, matcher) {
  return !!find(collection, matcher);
}
/**
 * Transform a collection into another collection
 * by piping each member through the given fn.
 *
 * @param  {Object|Array}   collection
 * @param  {Function} fn
 *
 * @return {Array} transformed collection
 */
function map(collection, fn) {
  var result = [];
  forEach(collection, function(val, key) {
    result.push(fn(val, key));
  });
  return result;
}
/**
 * Get the collections keys.
 *
 * @param  {Object|Array} collection
 *
 * @return {Array}
 */
function keys(collection) {
  return collection && Object.keys(collection) || [];
}
/**
 * Shorthand for `keys(o).length`.
 *
 * @param  {Object|Array} collection
 *
 * @return {Number}
 */
function size(collection) {
  return keys(collection).length;
}
/**
 * Get the values in the collection.
 *
 * @param  {Object|Array} collection
 *
 * @return {Array}
 */
function values(collection) {
  return map(collection, function(val) {
    return val;
  });
}
/**
 * Group collection members by attribute.
 *
 * @param  {Object|Array} collection
 * @param  {Function} extractor
 *
 * @return {Object} map with { attrValue => [ a, b, c ] }
 */
function groupBy(collection, extractor) {
  var grouped = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  extractor = toExtractor(extractor);
  forEach(collection, function(val) {
    var discriminator = extractor(val) || '_';
    var group = grouped[discriminator];
    if (!group) {
      group = grouped[discriminator] = [];
    }
    group.push(val);
  });
  return grouped;
}
function uniqueBy(extractor) {
  extractor = toExtractor(extractor);
  var grouped = {};
  for (var _len = arguments.length, collections = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    collections[_key - 1] = arguments[_key];
  }
  forEach(collections, function(c) {
    return groupBy(c, extractor, grouped);
  });
  var result = map(grouped, function(val, key) {
    return val[0];
  });
  return result;
}
var unionBy = uniqueBy;
/**
 * Sort collection by criteria.
 *
 * @param  {Object|Array} collection
 * @param  {String|Function} extractor
 *
 * @return {Array}
 */
function sortBy(collection, extractor) {
  extractor = toExtractor(extractor);
  var sorted = [];
  forEach(collection, function(value, key) {
    var disc = extractor(value, key);
    var entry = {
      d: disc,
      v: value
    };
    for (var idx = 0; idx < sorted.length; idx++) {
      var d = sorted[idx].d;
      if (disc < d) {
        sorted.splice(idx, 0, entry);
        return;
      }
    } // not inserted, append (!)
    sorted.push(entry);
  });
  return map(sorted, function(e) {
    return e.v;
  });
}
/**
 * Create an object pattern matcher.
 *
 * @example
 *
 * const matcher = matchPattern({ id: 1 });
 *
 * let element = find(elements, matcher);
 *
 * @param  {Object} pattern
 *
 * @return {Function} matcherFn
 */
function matchPattern(pattern) {
  return function(el) {
    return every(pattern, function(val, key) {
      return el[key] === val;
    });
  };
}
function toExtractor(extractor) {
  return isFunction(extractor) ? extractor : function(e) {
    return e[extractor];
  };
}
function toMatcher(matcher) {
  return isFunction(matcher) ? matcher : function(e) {
    return e === matcher;
  };
}
function identity(arg) {
  return arg;
}
function toNum(arg) {
  return Number(arg);
}
/**
 * Debounce fn, calling it only once if the given time
 * elapsed between calls.
 *
 * Lodash-style the function exposes methods to `#clear`
 * and `#flush` to control internal behavior.
 *
 * @param  {Function} fn
 * @param  {Number} timeout
 *
 * @return {Function} debounced function
 */
function debounce(fn, timeout) {
  var timer;
  var lastArgs;
  var lastThis;
  var lastNow;
  function fire(force) {
    var now = Date.now();
    var scheduledDiff = force ? 0 : lastNow + timeout - now;
    if (scheduledDiff > 0) {
      return schedule(scheduledDiff);
    }
    fn.apply(lastThis, lastArgs);
    clear();
  }
  function schedule(timeout) {
    timer = setTimeout(fire, timeout);
  }
  function clear() {
    if (timer) {
      clearTimeout(timer);
    }
    timer = lastNow = lastArgs = lastThis = undefined;
  }
  function flush() {
    if (timer) {
      fire(true);
    }
    clear();
  }
  function callback() {
    lastNow = Date.now();
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    lastArgs = args;
    lastThis = this; // ensure an execution is scheduled
    if (!timer) {
      schedule(timeout);
    }
  }
  callback.flush = flush;
  callback.cancel = clear;
  return callback;
}
/**
 * Throttle fn, calling at most once
 * in the given interval.
 *
 * @param  {Function} fn
 * @param  {Number} interval
 *
 * @return {Function} throttled function
 */
function throttle(fn, interval) {
  var throttling = false;
  return function() {
    if (throttling) {
      return;
    }
    fn.apply(void 0, arguments);
    throttling = true;
    setTimeout(function() {
      throttling = false;
    }, interval);
  };
}
/**
 * Bind function against target <this>.
 *
 * @param  {Function} fn
 * @param  {Object}   target
 *
 * @return {Function} bound function
 */
function bind(fn, target) {
  return fn.bind(target);
}
function _typeof(obj) {
  '@babel/helpers - typeof';
  if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') {
    _typeof = function(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function(obj) {
      return obj && typeof Symbol === 'function' && obj.constructor === Symbol && obj !== Symbol.prototype ? 'symbol' : typeof obj;
    };
  }
  return _typeof(obj);
}
function _extends() {
  _extends = Object.assign || function(target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];
      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }
    return target;
  };
  return _extends.apply(this, arguments);
}
/**
 * Convenience wrapper for `Object.assign`.
 *
 * @param {Object} target
 * @param {...Object} others
 *
 * @return {Object} the target
 */
function assign(target) {
  for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    others[_key - 1] = arguments[_key];
  }
  return _extends.apply(void 0, [target].concat(others));
}
/**
 * Sets a nested property of a given object to the specified value.
 *
 * This mutates the object and returns it.
 *
 * @param {Object} target The target of the set operation.
 * @param {(string|number)[]} path The path to the nested value.
 * @param {any} value The value to set.
 */
function set(target, path, value) {
  var currentTarget = target;
  forEach(path, function(key, idx) {
    if (typeof key !== 'number' && typeof key !== 'string') {
      throw new Error('illegal key type: ' + _typeof(key) + '. Key should be of type number or string.');
    }
    if (key === 'constructor') {
      throw new Error('illegal key: constructor');
    }
    if (key === '__proto__') {
      throw new Error('illegal key: __proto__');
    }
    var nextKey = path[idx + 1];
    var nextTarget = currentTarget[key];
    if (isDefined(nextKey) && isNil(nextTarget)) {
      nextTarget = currentTarget[key] = isNaN(+nextKey) ? {} : [];
    }
    if (isUndefined(nextKey)) {
      if (isUndefined(value)) {
        delete currentTarget[key];
      } else {
        currentTarget[key] = value;
      }
    } else {
      currentTarget = nextTarget;
    }
  });
  return target;
}
/**
 * Gets a nested property of a given object.
 *
 * @param {Object} target The target of the get operation.
 * @param {(string|number)[]} path The path to the nested value.
 * @param {any} [defaultValue] The value to return if no value exists.
 */
function get(target, path, defaultValue) {
  var currentTarget = target;
  forEach(path, function(key) {
    // accessing nil property yields <undefined>
    if (isNil(currentTarget)) {
      currentTarget = undefined;
      return false;
    }
    currentTarget = currentTarget[key];
  });
  return isUndefined(currentTarget) ? defaultValue : currentTarget;
}
/**
 * Pick given properties from the target object.
 *
 * @param {Object} target
 * @param {Array} properties
 *
 * @return {Object} target
 */
function pick(target, properties) {
  var result = {};
  var obj = Object(target);
  forEach(properties, function(prop) {
    if (prop in obj) {
      result[prop] = target[prop];
    }
  });
  return result;
}
/**
 * Pick all target properties, excluding the given ones.
 *
 * @param {Object} target
 * @param {Array} properties
 *
 * @return {Object} target
 */
function omit(target, properties) {
  var result = {};
  var obj = Object(target);
  forEach(obj, function(prop, key) {
    if (properties.indexOf(key) === -1) {
      result[key] = prop;
    }
  });
  return result;
}
/**
 * Recursively merge `...sources` into given target.
 *
 * Does support merging objects; does not support merging arrays.
 *
 * @param {Object} target
 * @param {...Object} sources
 *
 * @return {Object} the target
 */
function merge(target) {
  for (var _len2 = arguments.length, sources = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
    sources[_key2 - 1] = arguments[_key2];
  }
  if (!sources.length) {
    return target;
  }
  forEach(sources, function(source) {
    // skip non-obj sources, i.e. null
    if (!source || !isObject(source)) {
      return;
    }
    forEach(source, function(sourceVal, key) {
      if (key === '__proto__') {
        return;
      }
      var targetVal = target[key];
      if (isObject(sourceVal)) {
        if (!isObject(targetVal)) {
          // override target[key] with object
          targetVal = {};
        }
        target[key] = merge(targetVal, sourceVal);
      } else {
        target[key] = sourceVal;
      }
    });
  });
  return target;
}
export { assign, bind, debounce, ensureArray, every, filter, find, findIndex, flatten, forEach, get, groupBy, has, isArray, isDefined, isFunction, isNil, isNumber, isObject, isString, isUndefined, keys, map, matchPattern, merge, omit, pick, reduce, set, size, some, sortBy, throttle, unionBy, uniqueBy, values, without };
ruoyi-ui/apps/web-antd/src/views/workflow/category/index.vue
@@ -1,156 +1,203 @@
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :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="code">
            <el-input v-model="queryParams.code" 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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['workflow:category:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['workflow:category:edit']">
              ä¿®æ”¹
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:category:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
import type { VxeGridProps } from '#/adapter/vxe-table';
      <el-table v-loading="loading" :data="categoryList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="分类编号" align="center" prop="categoryId" v-if="true" />
        <el-table-column label="分类名称" align="center" prop="categoryName" />
        <el-table-column label="分类编码" align="center" prop="code" />
        <el-table-column label="备注" align="center" prop="remark" />
        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:category:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:category:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
import { nextTick } from 'vue';
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="categoryFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="分类名称" prop="categoryName">
          <el-input v-model="form.categoryName" placeholder="请输入分类名称" />
        </el-form-item>
        <el-form-item label="分类编码" prop="code">
          <el-input v-model="form.code" placeholder="请输入分类编码" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
import { Page, useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
<script setup name="Category" >
import { listCategory, getCategory, delCategory, addCategory, updateCategory } from "#/api/workflow/category";
import { Popconfirm, Space } from 'ant-design-vue';
const { proxy } = getCurrentInstance();
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { categoryList, categoryRemove } from '#/api/workflow/category';
const categoryList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
import categoryModal from './category-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
  commonConfig: {
    labelWidth: 80,
    componentProps: {
      allowClear: true,
    },
const queryFormRef = ref();
const categoryFormRef = ref();
const dialog = reactive({
  visible: false,
  title: ''
});
const initFormData = {
  categoryId: undefined,
  categoryName: '',
  code: '',
  remark: ''
}
const data = reactive({
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    categoryName: '',
    code: ''
  },
  schema: querySchema(),
  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const gridOptions: VxeGridProps = {
  columns,
  height: 'auto',
  keepSource: true,
  pagerConfig: {
    enabled: false,
  },
  proxyConfig: {
    ajax: {
      query: async (_, formValues = {}) => {
        const resp = await categoryList({
          ...formValues,
        });
        return { rows: resp };
      },
      // é»˜è®¤è¯·æ±‚接口后展开全部 ä¸éœ€è¦å¯ä»¥åˆ é™¤è¿™æ®µ
      querySuccess: () => {
        nextTick(() => {
          expandAll();
        });
      },
    },
  },
  /**
   * è™šæ‹Ÿæ»šåЍ  é»˜è®¤å…³é—­
   */
  scrollY: {
    enabled: false,
    gt: 0,
  },
  rowConfig: {
    keyField: 'categoryId',
  },
  treeConfig: {
    parentField: 'parentId',
    rowField: 'categoryId',
    transform: true,
  },
  // è¡¨æ ¼å…¨å±€å”¯ä¸€è¡¨ç¤º ä¿å­˜åˆ—配置需要用到
  id: 'workflow-category-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
const [CategoryModal, modalApi] = useVbenModal({
  connectedComponent: categoryModal,
  rules: {
    categoryName: [{ required: true, message: "分类名称不能为空", trigger: "blur" }],
    code: [{ required: true, message: "分类编码不能为空", trigger: "blur" }]
  }
});
function handleAdd(row?: Recordable<any>) {
  modalApi.setData({ parentId: row?.categoryId });
  modalApi.open();
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å‚数列表 */
const getList = async () => {
  loading.value = true;
  const res = await listCategory(queryParams.value);
  categoryList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
  categoryFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection) => {
  ids.value = selection.map(item => item.categoryId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  dialog.visible = true;
  dialog.title = "添加参数";
  nextTick(() => {
    reset();
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row) => {
  dialog.visible = true;
  dialog.title = "修改参数";
  const categoryId = row?.categoryId || ids.value[0];
  nextTick(async () => {
    reset();
    const res = await getCategory(categoryId);
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  categoryFormRef.value.validate(async (valid) => {
    if (valid) {
      form.value.categoryId ? await updateCategory(form.value) : await addCategory(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      getList();
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row) => {
  const categoryIds = row?.categoryId || ids.value;
  await proxy?.$modal.confirm('是否确认删除参数编号为"' + categoryIds + '"的数据项?');
  await delCategory(categoryIds);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("workflow/category/export", {
    ...queryParams.value
  }, `category_${new Date().getTime()}.xlsx`);
}
async function handleEdit(row: Recordable<any>) {
  modalApi.setData({ id: row.categoryId });
  modalApi.open();
}
async function handleDelete(row: Recordable<any>) {
  await categoryRemove(row.categoryId);
  await tableApi.query();
}
function expandAll() {
  tableApi.grid?.setAllTreeExpand(true);
}
function collapseAll() {
  tableApi.grid?.setAllTreeExpand(false);
}
onMounted(() => {
  getList();
})
</script>
<template>
  <Page :auto-content-height="true">
    <BasicTable table-title="流程分类列表">
      <template #toolbar-tools>
        <Space>
          <a-button @click="collapseAll">
            {{ $t('pages.common.collapse') }}
          </a-button>
          <a-button @click="expandAll">
            {{ $t('pages.common.expand') }}
          </a-button>
          <a-button
            type="primary"
            v-access:code="['workflow:category:add']"
            @click="handleAdd"
          >
            {{ $t('pages.common.add') }}
          </a-button>
        </Space>
      </template>
      <template #action="{ row }">
        <Space>
          <ghost-button
            v-access:code="['workflow:category:edit']"
            @click.stop="handleEdit(row)"
          >
            {{ $t('pages.common.edit') }}
          </ghost-button>
          <ghost-button
            class="btn-success"
            v-access:code="['workflow:category:edit']"
            @click.stop="handleAdd(row)"
          >
            {{ $t('pages.common.add') }}
          </ghost-button>
          <Popconfirm
            :get-popup-container="getVxePopupContainer"
            placement="left"
            title="确认删除?"
            @confirm="handleDelete(row)"
          >
            <ghost-button
              danger
              v-access:code="['workflow:category:remove']"
              @click.stop=""
            >
              {{ $t('pages.common.delete') }}
            </ghost-button>
          </Popconfirm>
        </Space>
      </template>
    </BasicTable>
    <CategoryModal @reload="tableApi.query()" />
  </Page>
</template>
ruoyi-ui/apps/web-antd/src/views/workflow/deploy/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,236 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="流程标识" prop="processKey">
            <el-input v-model="queryParams.processKey" placeholder="请输入流程标识" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="流程分类"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </el-form-item>
          <el-form-item label="状态" prop="state">
            <el-select v-model="queryParams.state" clearable placeholder="请选择状态"  style="width: 240px">
              <el-option :key="1" label="激活" value="active" />
              <el-option :key="2" label="挂起" value="suspended" />
            </el-select>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:deploy:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" fit :data="deployList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="流程标识" align="center" prop="processKey" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" prop="processName" :show-overflow-tooltip="true" />
        <el-table-column label="流程分类" align="center" prop="categoryName" :formatter="categoryFormat" />
        <el-table-column label="流程版本" align="center">
          <template #default="scope">
            <el-tag size="small">v{{ scope.row.version }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="状态" align="center">
          <template #default="scope">
            <el-tag type="success" v-if="!scope.row.suspended">激活</el-tag>
            <el-tag type="warning" v-if="scope.row.suspended">挂起</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="部署时间" align="center" prop="deploymentTime" width="180" >
          <template #default="scope">
              {{  parseTime(scope.row.deploymentTime, '{y}-{m}-{d}  {h}:{i}:{s}')}}
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="版本管理" placement="top">
              <el-button link type="primary" icon="PriceTag" @click="handlePublish(scope.row)" v-hasPermi="['workflow:deploy:list']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:deploy:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
<!--    &lt;!&ndash; æµç¨‹å›¾å¯¹è¯æ¡† &ndash;&gt;-->
<!--    <el-dialog :title="processDialog.title" v-model="processDialog.visible" width="500px" append-to-body>-->
<!--      <process-viewer :key="`designer-${processView.index}`" :xml="processView.xmlData" :style="{height: '400px'}" />-->
<!--    </el-dialog>-->
    <!-- ç‰ˆæœ¬ç®¡ç† -->
    <el-dialog title="版本管理" v-model="publishDialog.visible" width="700px" append-to-body>
      <el-table v-loading="publishLoading" :data="publishList">
        <el-table-column label="流程标识" align="center" prop="processKey" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" :show-overflow-tooltip="true">
          <template #default="scope">
            <a class="link-type"  @click="handleProcessView(scope.row)">
              <span>{{ scope.row.processName }}</span>
            </a>
          </template>
        </el-table-column>
        <el-table-column label="流程版本" align="center">
          <template #default="scope">
            <el-tag size="small">v{{ scope.row.version }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="状态" align="center">
          <template #default="scope">
            <el-tag type="success" v-if="!scope.row.suspended">激活</el-tag>
            <el-tag type="warning" v-if="scope.row.suspended">挂起</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="挂起" placement="top">
              <el-button link type="primary" icon="VideoPause" @click="handleChangeState(scope.row, 'suspended')" v-hasPermi="['workflow:deploy:status']"></el-button>
            </el-tooltip>
            <el-tooltip content="激活" placement="top">
              <el-button link type="primary" icon="VideoPlay" @click="handleChangeState(scope.row, 'active')" v-hasPermi="['workflow:deploy:status']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:deploy:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="publishTotal > 0" :total="publishTotal" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getPublishList" />
    </el-dialog>
  </div>
</template>
<script setup name="Deploy" lang="js">
import { listAllCategory } from '#/api/workflow/category'
import { listDeploy, listPublish, getBpmnXml, changeState, delDeploy } from '#/api/workflow/deploy'
import { ElForm } from 'element-plus';
const { proxy } = getCurrentInstance() ;
const categoryOptions = ref([]);
const deployList = ref([]);
const loading = ref(true);
const showSearch = ref(true)
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const publishList = ref([]);
const publishLoading = ref(true);
const publishTotal = ref(0);
const queryFormRef = ref();
const processDialog = reactive({
  visible: false,
  title: ''
});
const publishDialog = reactive({
  visible: false,
  title: ''
});
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    processKey: '',
    processName: '',
    category: ''
  },
  rules: {}
});
const { queryParams } = toRefs(data);
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
};
const getList = async () => {
  loading.value = true;
  const res = await listDeploy(queryParams.value);
  deployList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection) => {
  ids.value = selection.map(item => item.deploymentId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row) => {
  const deploymentIds = row.deploymentId || ids.value;
  await proxy.$modal.confirm('是否确认删除参数编号为"' + deploymentIds + '"的数据项?');
  await delDeploy(deploymentIds);
  getList();
  proxy.$modal.msgSuccess("删除成功");
}
const getPublishList = async () => {
  publishLoading.value = true;
  const res = await listPublish(queryParams.value);
  publishList.value = res.rows;
  publishTotal.value = res.total;
  publishLoading.value = false;
}
const handlePublish = (row) => {
  queryParams.value.processKey = row.processKey;
  publishDialog.visible = true;
  getPublishList();
}
const handleProcessView = (row) => {
}
const handleChangeState = async (row, state) => {
  const params = {
    definitionId: row.definitionId,
    state: state
  }
  await changeState(params);
  proxy.$modal.msgSuccess("操作成功");
  getPublishList();
}
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(k) {
        return k.code === row.category;
    });
    return category ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList();
  getList();
})
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/form/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,251 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="表单名称" prop="formName">
            <el-input v-model="queryParams.formName" 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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['workflow:form:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['workflow:form:edit']">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:form:remove']">删除</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="formList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="表单主键" align="center" prop="formId" v-if="false" />
        <el-table-column label="表单名称" align="center" prop="formName" />
        <el-table-column label="备注" align="center" prop="remark" />
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="详情" placement="top">
              <el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['workflow:form:query']"></el-button>
            </el-tooltip>
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:form:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:form:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æµç¨‹è¡¨å•设计器对话框 -->
    <el-dialog :title="designer.title" v-model="designer.visible" fullscreen>
      <div id="form-designer">
        <v-form-designer ref="vfDesignerRef" :resetFormJson="true" :designer-config="designerConfig">
          <!-- è‡ªå®šä¹‰æŒ‰é’®æ’æ§½ -->
          <template #customToolButtons>
            <el-button link type="primary" icon="Finished" @click="dialog.visible = true">保存</el-button>
          </template>
        </v-form-designer>
      </div>
    </el-dialog>
    <!-- é¢„览表单对话框 -->
    <el-dialog :title="render.title" v-model="render.visible" width="60%" append-to-body>
      <v-form-render :form-json="{}" :form-data="{}" ref="vfRenderRef" />
    </el-dialog>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµç¨‹è¡¨å•å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
      <el-form ref="formFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="表单名称" prop="formName">
          <el-input v-model="form.formName" placeholder="请输入表单名称" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Form" >
import { listForm, addForm, updateForm, getForm, delForm } from "#/api/workflow/form";
const { proxy } = getCurrentInstance();
const formList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const vfDesignerRef = ref(null);
const vfRenderRef = ref(null);
const formFormRef = ref();
const queryFormRef = ref();
const designerConfig = reactive({
  externalLink: true,
  toolbarMaxWidth: 510,
  // languageMenu: true,
  //externalLink: false,
  //formTemplates: false,
  //eventCollapse: false,
  //clearDesignerButton: false,
  //previewFormButton: false,
})
const designer = reactive({
  visible: false,
  title: ''
})
const render = reactive({
  visible: false,
  title: ''
})
const dialog = reactive({
  visible: false,
  title: ''
});
const initFormData = {
  formId: undefined,
  formName: '',
  content: '',
  remark: ''
}
const data = reactive({
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    formName: ''
  },
  rules: {
    formName: [{ required: true, message: "表单名称不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å²—位列表 */
const getList = async () => {
  loading.value = true;
  const res = await listForm(queryParams.value);
  formList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection) => {
  ids.value = selection.map(item => item.formId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žè¡¨å•操作 */
const handleAdd = () => {
  designer.visible = true;
  nextTick(() => {
    reset();
    vfDesignerRef.value.clearDesigner();
  })
}
/** ä¿®æ”¹è¡¨å•操作 */
const handleUpdate = (row) => {
  designer.visible = true;
  nextTick(async () => {
    const formId = row?.formId || ids.value[0];
    const res = await getForm(formId);
    form.value = res.data;
    vfDesignerRef.value.setFormJson(form.value.content);
  })
}
/** æŸ¥çœ‹è¡¨å•操作 */
const handleDetail = (row) => {
  render.visible = true;
  render.title = '查看表单详情';
  nextTick(async () => {
    vfRenderRef.value.setFormJson(row.content || {formConfig: {}, widgetList: []});
  });
}
/** æäº¤è¡¨å•操作 */
const submitForm = () => {
  const formJson = vfDesignerRef.value.getFormJson();
  form.value.content = JSON.stringify(formJson);
  nextTick(async () => {
    form.value.formId ? await updateForm(form.value) : await addForm(form.value);
    proxy?.$modal.msgSuccess("操作成功");
    dialog.visible = false;
    designer.visible = false;
    getList();
  })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row) => {
  const formIds = row?.formId || ids.value;
  await proxy?.$modal.confirm('是否确认删除表单编号为"' + formIds + '"的数据项?');
  await delForm(formIds);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("workflow/form/export", {
    ...queryParams.value
  }, `form_${new Date().getTime()}.xlsx`);
}
getList();
</script>
<style lang="scss" scoped>
#form-designer {
  .main-container {
    margin: 0;
  }
  label {
    font-weight: normal;
  }
  :deep(.external-link) {
    display: flex;
    align-items: center;
  }
}
</style>
ruoyi-ui/apps/web-antd/src/views/workflow/listener/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,469 @@
<template>
 <div class="app-container">
    <!-- æŸ¥è¯¢æ¡ä»¶ -->
      <div v-show="showSearch" class="search">
        <el-form ref="queryFormRef" :inline="true" :model="queryParams" label-width="120">
          <el-form-item label="监听类型" prop="listenerType">
            <el-select v-model="queryParams.listenerType" clearable placeholder="请选择监听类型" @change="handleQuery">
              <el-option v-for="dict in sys_listener_type" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="名称" prop="name">
            <el-input v-model="queryParams.name" clearable placeholder="请输入名称" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="事件类型" prop="eventType">
            <el-select v-model="queryParams.eventType" clearable placeholder="请选择事件类型" @change="handleQuery">
              <el-option
                v-for="dict in sys_task_listener_event_type"
                v-if="queryParams.listenerType === 'task'"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
              <el-option
                v-for="dict in sys_execution_listener_event_type"
                v-if="queryParams.listenerType === 'execution'"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
              <el-option
                v-for="dict in sys_task_listener_event_type"
                v-if="!queryParams.listenerType"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
              <el-option
                v-for="dict in sys_execution_listener_event_type"
                v-if="!queryParams.listenerType"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button icon="Search" type="primary" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    <el-card shadow="never">
      <template #header>
        <!-- æ“ä½œæŒ‰é’® -->
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:listener:add']" icon="Plus" plain type="primary" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:listener:edit']" :disabled="single" icon="Edit" plain type="success" @click="handleUpdate()"
              >修改</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:listener:remove']" :disabled="multiple" icon="Delete" plain type="danger" @click="handleDelete()"
              >删除</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:listener:export']" icon="Download" plain type="warning" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <!-- åˆ†é¡µåˆ—表 -->
      <el-table
        v-loading="tableLoading"
        :data="listenerList"
        @selection-change="handleSelectionChange"
        v-fixTableHeight
        highlight-current-row
        show-overflow-tooltip
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand" width="50px">
          <template #default="props">
            <el-table :data="props.row.fields" width="60%">
              <el-table-column v-if="false" label="主键" prop="id" />
              <el-table-column label="字段名称" prop="fieldName" />
              <el-table-column label="字段类型" prop="fieldType">
                <template #default="scope">
                  <dict-tag :options="sys_listener_field_type" :value="scope.row.fieldType" />
                </template>
              </el-table-column>
              <el-table-column label="字段值" prop="fieldValue" />
              <el-table-column align="center" class-name="small-padding fixed-width" label="操作">
                <template #default="scope">
                  <el-tooltip content="修改" placement="top">
                    <el-button
                      v-hasPermi="['workflow:listener:edit']"
                      icon="Edit"
                      link
                      type="primary"
                      @click="handleFieldUpdate(scope.row)"
                    ></el-button>
                  </el-tooltip>
                  <el-tooltip content="删除" placement="top">
                    <el-button
                      v-hasPermi="['workflow:listener:remove']"
                      icon="Delete"
                      link
                      type="danger"
                      @click="handleFieldDelete(scope.row)"
                    ></el-button>
                  </el-tooltip>
                </template>
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <el-table-column v-if="false" align="center" label="主键" prop="id" />
        <el-table-column align="center" label="监听类型" prop="listenerType">
          <template #default="scope">
            <dict-tag :options="sys_listener_type" :value="scope.row.listenerType" />
          </template>
        </el-table-column>
        <el-table-column align="center" label="名称" prop="name" />
        <el-table-column align="center" label="值类型" prop="valueType">
          <template #default="scope">
            <dict-tag :options="sys_listener_value_type" :value="scope.row.valueType" />
          </template>
        </el-table-column>
        <el-table-column align="center" label="值" prop="value" />
        <el-table-column align="center" label="事件类型" prop="eventType">
          <template #default="scope">
            <dict-tag v-if="scope.row.listenerType === 'task'" :options="sys_task_listener_event_type" :value="scope.row.eventType" />
            <dict-tag v-if="scope.row.listenerType === 'execution'" :options="sys_execution_listener_event_type" :value="scope.row.eventType" />
          </template>
        </el-table-column>
        <el-table-column align="center" class-name="small-padding fixed-width" label="操作">
          <template #default="scope">
            <el-tooltip content="添加字段" placement="top">
              <el-button v-hasPermi="['workflow:listener:edit']" icon="Plus" link type="primary" @click="addField(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="修改" placement="top">
              <el-button v-hasPermi="['workflow:listener:edit']" icon="Edit" link type="primary" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button v-hasPermi="['workflow:listener:remove']" icon="Delete" link type="danger" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNum" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµç¨‹è¡¨å•å¯¹è¯æ¡† -->
    <el-dialog v-model="dialog.visible" :close-on-click-modal="false" :title="dialog.title" append-to-body width="600px">
      <el-form ref="listenerFormRef" v-loading="formLoading" :model="form" :rules="rules" label-width="120px">
        <el-form-item label="监听类型" prop="listenerType">
          <el-radio-group v-model="form.listenerType">
            <el-radio v-for="dict in sys_listener_type" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入名称" />
        </el-form-item>
        <el-form-item label="值类型" prop="valueType">
          <el-radio-group v-model="form.valueType">
            <el-radio v-for="dict in sys_listener_value_type" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="值" prop="value">
          <el-input v-model="form.value" placeholder="请输入值" />
        </el-form-item>
        <el-form-item label="事件类型" prop="eventType">
          <el-select v-model="form.eventType" clearable placeholder="请选择事件类型" style="width: 100%">
            <el-option
              v-for="dict in sys_task_listener_event_type"
              v-if="form.listenerType === 'task'"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
            <el-option
              v-for="dict in sys_execution_listener_event_type"
              v-if="form.listenerType === 'execution'"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç›‘听器字段对话框 -->
    <el-dialog v-model="fieldDialog.visible" :close-on-click-modal="false" :title="fieldDialog.title" append-to-body width="600px">
      <el-form ref="listenerFieldFormRef" v-loading="fieldFormLoading" :model="initFieldFormData" :rules="filedRules" label-width="120px">
        <el-form-item label="名称" prop="fieldName">
          <el-input v-model="filedForm.fieldName" placeholder="请输入名称" />
        </el-form-item>
        <el-form-item label="字段类型" prop="fieldType">
          <el-radio-group v-model="filedForm.fieldType">
            <el-radio v-for="dict in sys_listener_field_type" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="值" prop="fieldValue">
          <el-input v-model="filedForm.fieldValue" placeholder="请输入值" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFieldForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script  name="Form" setup>
getList();
import {
  addListener,
  deleteListenerFieldAPI,
  delListener,
  getListener,
  insertListenerFieldAPI,
  queryListenerPage,
  updateListener,
  updateListenerFieldAPI
} from "#/api/workflow/listener";
const { proxy } = getCurrentInstance() ;
const {
  sys_listener_type,
  sys_listener_value_type,
  sys_task_listener_event_type,
  sys_execution_listener_event_type,
  sys_listener_field_type
} = proxy.useDict("sys_listener_type", "sys_listener_value_type", "sys_task_listener_event_type", "sys_execution_listener_event_type", "sys_listener_field_type");
const listenerList = ref([]);
const showSearch = ref(true);
const ids = ref([]);
const names = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const tableLoading = ref(false);
const formLoading = ref(false);
const fieldFormLoading = ref(false);
const listenerFormRef = ref();
const listenerFieldFormRef = ref();
const queryFormRef = ref();
const listenerId = ref('');
const dialog = reactive({
  visible: false,
  title: ''
});
const fieldDialog = reactive({
  visible: false,
  title: ''
});
const initFormData = {
  id: undefined,
  valueType: '',
  eventType: '',
  listenerType: '',
  name: '',
  value: ''
}
const data = reactive({
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    name: '',
    listenerType: '',
    eventType: ''
  },
  rules: {
    listenerType: [{ required: true, message: "监听类型不能为空", trigger: "blur" }],
    name: [{ required: true, message: "名称不能为空", trigger: "blur" }],
    valueType: [{ required: true, message: "值类型不能为空", trigger: "blur" }],
    value: [{ required: true, message: "值不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
const initFieldFormData={
  id: undefined,
  listenerId: undefined,
  fieldName: undefined,
  fieldType: undefined,
  fieldValue: undefined
}
const filedData = reactive({
  filedForm: {...initFieldFormData},
  filedRules: {
    fieldName: [{ required: true, message: "请输入字段名称", trigger: "blur" }],
    fieldType: [{ required: true, message: "请选择字段类型", trigger: "blur" }]
  }
})
const { filedForm, filedRules } = toRefs(filedData);
/** æŸ¥è¯¢åˆ—表 */
const getList = async () => {
  tableLoading.value = true;
  const res = await queryListenerPage(queryParams.value);
  listenerList.value = res.rows;
  total.value = res.total;
  tableLoading.value = false;
}
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
  fieldDialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
  filedForm.value = {...initFieldFormData};
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection) => {
  ids.value = selection.map(item => item.id);
  names.value = selection.map(item => item.name);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žè¡¨å•操作 */
const handleAdd = () => {
  dialog.visible = true;
  nextTick(() => {
    reset();
  })
}
/** ä¿®æ”¹è¡¨å•操作 */
const handleUpdate = (row) => {
  dialog.visible = true;
  const id = row?.id || ids.value[0];
  nextTick(async () => {
    formLoading.value = true;
    reset();
    const res = await getListener(id);
    formLoading.value = false;
    form.value = res.data;
  })
}
/** æäº¤è¡¨å•操作 */
const submitForm = () => {
  listenerFormRef.value.validate(async (valid) => {
    if (valid) {
      dialog.visible = true;
      formLoading.value = true;
      try {
        form.value.id ? await updateListener(form.value) : await addListener(form.value);
      } finally {
        formLoading.value = false;
      }
      dialog.visible = false;
      proxy?.$modal.msgSuccess("操作成功");
      getList();
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row) => {
  const listenerIds = row?.id || ids.value;
  const listenerNames = row?.name || ids.value;
  await proxy?.$modal.confirm('是否确认删除【' + listenerNames + '】的数据项?');
  tableLoading.value = true;
  try {
    await delListener(listenerIds);
  } finally {
    tableLoading.value = false;
  }
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** æ·»åŠ å­—æ®µæ“ä½œ */
const addField = (ro) => {
  fieldDialog.visible = true;
  // listenerField.value = row.fields;
  listenerId.value = row.id;
  nextTick(() => {
    reset();
    filedForm.value.listenerId = row.id;
  })
}
/** ä¿®æ”¹å­—段操作 */
const handleFieldUpdate = (row) => {
  fieldDialog.visible = true;
  filedForm.value.id = row.id;
  filedForm.value.listenerId = row.listenerId;
  filedForm.value.fieldName = row.fieldName;
  filedForm.value.fieldType = row.fieldType;
  filedForm.value.fieldValue = row.fieldValue;
}
/** æäº¤å­—段表单操作 */
const submitFieldForm = () => {
  listenerFieldFormRef.value.validate(async (vali) => {
    if (valid) {
      fieldDialog.visible = true;
      fieldFormLoading.value = true;
      try {
        filedForm.value.id ? await updateListenerFieldAPI(filedForm.value) : await insertListenerFieldAPI(filedForm.value);
      } finally {
        fieldFormLoading.value = false;
      }
      listenerId.value = '';
      fieldDialog.visible = false;
      proxy.$modal.msgSuccess("操作成功");
      getList();
    }
  });
}
/** åˆ é™¤å­—段按钮操作 */
const handleFieldDelete = async (ro) => {
  const fieldIds = row?.id || ids.value;
  const fieldNames = row?.fieldName || ids.value;
  await proxy?.$modal.confirm('是否确认删除【' + fieldNames + '】的数据项?');
  tableLoading.value = true;
  try {
    await deleteListenerFieldAPI(fieldIds);
  } finally {
    tableLoading.value = false;
  }
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("workflow/form/export", {
    ...queryParams.value
  }, `form_${new Date().getTime()}.xlsx`);
}
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/model/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,438 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="模型标识" prop="modelKey">
            <el-input v-model="queryParams.modelKey" placeholder="请输入模型标识" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="模型名称" prop="modelName">
            <el-input v-model="queryParams.modelName" placeholder="请输入模型名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="请选择" @change="handleQuery"  style="width: 240px">
              <el-option
                v-for="item in categoryOptions"
                :key="item.categoryId"
                :label="item.categoryName"
                :value="item.code">
              </el-option>
            </el-select>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['workflow:model:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['workflow:model:remove']">删除</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['workflow:model:export']">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="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 label="模型标识" align="center" prop="modelKey" :show-overflow-tooltip="true" />
        <el-table-column label="模型名称" align="center" :show-overflow-tooltip="true">
          <template #default="scope">
            <a class="link-type" @click="handleProcessView(scope.row)">
              <span>{{ scope.row.modelName }}</span>
            </a>
          </template>
        </el-table-column>
        <el-table-column label="流程分类" align="center" prop="categoryName" :formatter="categoryFormat" />
        <el-table-column label="模型版本" align="center">
          <template #default="scope">
            <el-tag size="small">v{{ scope.row.version }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="描述" align="center" prop="description" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180" >
          <template #default="scope">
              {{  parseTime(scope.row.createTime, '{y}-{m}-{d}  {h}:{i}:{s}')}}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:model:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:model:remove']"></el-button>
            </el-tooltip>
            <el-tooltip content="设计" placement="top">
              <el-button link type="primary" icon="Brush" @click="handleDesigner(scope.row)" v-hasPermi="['workflow:model:designer']"></el-button>
            </el-tooltip>
            <el-tooltip content="部署" placement="top">
              <el-button link type="primary" icon="Promotion" @click="handleDeploy(scope.row)" v-hasPermi="['workflow:model:deploy']"></el-button>
            </el-tooltip>
            <el-tooltip content="历史" placement="top">
              <el-button link type="primary" icon="Discount" @click="handleHistory(scope.row)" v-hasPermi="['workflow:model:list']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!--  æ·»åŠ æˆ–ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯å¯¹è¯æ¡†  -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="modelFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="模型标识" prop="modelKey">
          <el-input v-model="form.modelKey" clearable disabled placeholder="请输入模型标识" />
        </el-form-item>
        <el-form-item label="模型名称" prop="modelName">
          <el-input v-model="form.modelName" clearable :disabled="form.modelId !== undefined" placeholder="请输入模型名称" />
        </el-form-item>
        <el-form-item label="流程分类" prop="category">
          <el-select v-model="form.category" placeholder="请选择" clearable style="width:100%">
            <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
          </el-select>
        </el-form-item>
        <el-form-item label="描述" prop="description">
          <el-input v-model="form.description" type="textarea" placeholder="请输入内容" maxlength="200" show-word-limit />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog :title="designer.title" v-model="designer.visible" append-to-body fullscreen>
      <ProcessDesigner
        :key="`designer-${reloadIndex}`"
        ref="modelDesignerRef"
        v-loading="designerLoading"
        :designer-form="designerForm"
        :bpmn-xml="bpmnXml"
        @save="onSaveDesigner"
      />
    </el-dialog>
    <el-dialog :title="history.title" v-model="history.visible" append-to-body>
      <el-table v-loading="historyLoading" :data="historyList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="模型标识" align="center" prop="modelKey" :show-overflow-tooltip="true" />
        <el-table-column label="模型名称" align="center" prop="modelName" :show-overflow-tooltip="true" />
        <el-table-column label="流程分类" align="center" prop="categoryName" :formatter="categoryFormat" />
        <el-table-column label="模型版本" align="center">
          <template #default="scope">
            <el-tag>v{{ scope.row.version }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="描述" align="center" prop="description" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="部署" placement="top">
              <el-button link type="primary" icon="Promotion" @click="handleDeploy(scope.row)" v-hasPermi="['workflow:model:deploy']"></el-button>
            </el-tooltip>
            <el-tooltip content="设为最新" placement="top">
              <el-button link type="primary" icon="Star" @click="handleLatest(scope.row)" v-hasPermi="['workflow:model:save']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-dialog>
    <!-- æµç¨‹å›¾ -->
    <el-dialog :title="processDialog.title" v-model="processDialog.visible" width="70%">
      <ProcessViewer :key="`designer-${reloadIndex}`" :xml="processXml" :style="{height: '650px'}" />
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { getBpmnXml, listModel, historyModel, latestModel, addModel, updateModel, saveModel, delModel, deployModel, getModel } from "#/api/workflow/model";
import { listAllCategory } from "#/api/workflow/category";
import {getCurrentInstance,ref,reactive,toRefs,onMounted} from "vue";
import {useRouter} from "vue-router";
import { defineAsyncComponent } from "vue";
// å¼‚步加载组件
const ProcessDesigner = defineAsyncComponent(() =>
  import('#/components/ProcessDesigner/index.vue')
);
const ProcessViewer = defineAsyncComponent(() =>
  import('#/components/ProcessViewer/index.vue')
);
const { proxy } = getCurrentInstance() ;
const modelList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const categoryOptions = ref([]);
const designerLoading = ref(true);
const bpmnXml = ref('');
const reloadIndex = ref(0);
const processXml = ref("");
const historyList = ref([]);
const historyLoading = ref(true);
const historyTotal = ref(0);
const modelFormRef = ref();
const queryFormRef = ref();
const modelDesignerRef = ref(null)
const dialog = reactive({
  visible: false,
  title: ''
});
const processDialog = reactive({
  visible: false,
  title: '流程图'
});
const designer = reactive({
  visible: false,
  title: ''
});
const history = reactive({
  visible: false,
  title: ''
});
const initFormData={
  modelId: undefined,
  modelKey: `Process_${new Date().getTime()}`,
  modelName: `业务流程_${new Date().getTime()}`,
  category: '',
  description: '',
  formType: undefined,
  formId: undefined,
  bpmnXml: '',
  newVersion: false
}
const data = reactive({
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    modelKey: '',
    modelName: '',
    category: ''
  },
  rules: {
    modelKey: [{ required: true, message: "岗位名称不能为空", trigger: "blur" }],
    modelName: [{ required: true, message: "岗位编码不能为空", trigger: "blur" }],
  }
});
const designerForm = reactive({
  modelId: '',
  form: {
    processName: '',
    processKey: ''
  }
});
const { queryParams, form, rules } = toRefs(data);
const router = useRouter();
/** æŸ¥è¯¢æ¨¡åž‹åˆ—表 */
const getList = async () => {
  // loading.value = true;
  const res = await listModel(queryParams.value);
  console.log(res)
  modelList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
  modelFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection) => {
  ids.value = selection.map(item => item.modelId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  dialog.visible = true;
  dialog.title = "添加模型";
  nextTick(() => {
    reset();
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row) => {
  dialog.visible = true;
  dialog.title = "修改模型";
  nextTick(async () => {
    reset();
    const modelId = row.modelId || ids.value[0];
    const res = await getModel(modelId);
    form.value = res.data;
  });
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row) => {
  const modelIds = row.modelId || ids.value;
  await proxy?.$modal.confirm('是否确认删除参数编号为"' + modelIds + '"的数据项?');
  await delModel(modelIds);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("workflow/model/export", {
    ...queryParams.value
  }, `model_${new Date().getTime()}.xlsx`);
};
/** æŸ¥çœ‹æµç¨‹å›¾ */
const handleProcessView = async (row) => {
  reloadIndex.value++;
  // å‘送请求,获取xml
  const res = await getBpmnXml(row.modelId);
  processXml.value = res.data;
  processDialog.visible = true;
}
/** è®¾è®¡æŒ‰é’®æ“ä½œ */
const handleDesigner = async (row) => {
  reloadIndex.value++;
  designerForm.modelId = row.modelId;
  const res = await getBpmnXml(row.modelId);
  bpmnXml.value = res.data || '';
  designerLoading.value = false;
  designer.title = "流程设计 - " + row.modelName;
  designer.visible = true;
}
const handleDeploy = (row) => {
  loading.value = true;
  nextTick(async () => {
    await deployModel({ modelId: row?.modelId });
    proxy.$modal.msgSuccess("操作成功");
    router.push({
      name: 'Deploy',
      path: '/workflow/deploy'
    });
    loading.value = false;
  });
}
const handleLatest = async (row) => {
  await proxy.$modal.confirm('是否将此模型保存为新版本?');
  historyLoading.value = true;
  await latestModel({modelId: row.modelId});
  history.visible = false;
  getList();
  proxy?.$modal.msgSuccess("操作成功");
  historyLoading.value = false;
}
/** æŸ¥è¯¢åŽ†å²åˆ—è¡¨ */
const getHistoryList = async () => {
  historyLoading.value = true;
  const res = await historyModel(queryParams.value);
  historyList.value = res.rows;
  historyTotal.value = res.total;
  historyLoading.value = false;
}
const handleHistory = (row) => {
  history.visible = true;
  history.title = "模型历史";
  queryParams.value.modelKey = row?.modelKey;
  getHistoryList();
}
/** æäº¤è¡¨å•操作 */
const submitForm = () => {
  modelFormRef.value.validate(async (valid) => {
    if (valid) {
      form.value.modelId ? await updateModel(form.value) : await addModel(form.value);
      proxy.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      getList();
    }
  })
}
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
}
const onSaveDesigner = async (str) => {
  bpmnXml.value = str;
  let dataBody = {
    modelId: designerForm.modelId,
    bpmnXml: str
  }
  proxy.$modal.confirm('是否将此模型保存为新版本?').then(() => {
    confirmSave(dataBody, true)
  }).catch(action => {
    if (action === 'cancel') {
      confirmSave(dataBody, false)
    }
  })
}
const confirmSave = async (body, newVersion) => {
  designerLoading.value = true;
  console.log(body,"body");
  await saveModel(Object.assign(body, { newVersion: newVersion }));
  getList();
  proxy.$modal.msgSuccess("保存成功");
  designerLoading.value = false;
  designer.visible = false;
}
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(k) {
        return k.code === row.category;
    });
    return category ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList()
  getList();
});
</script>
<style lang="scss" scoped>
.el-dialog__body {
  max-height: calc(100vh) !important;
  overflow-y: auto;
  overflow-x: hidden;
}
</style>
ruoyi-ui/apps/web-antd/src/views/workflow/work/claim.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="请选择"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </el-form-item>
          <el-form-item label="接收时间" style="width: 308px;">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
            ></el-date-picker>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="claimList">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" prop="procDefName" />
        <el-table-column label="任务节点" align="center" prop="taskName" />
        <el-table-column label="流程版本" align="center">
          <template #default="scope">
            <el-tag>v{{scope.row.procDefVersion}}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="流程发起人" align="center" prop="startUserName" />
        <el-table-column label="接收时间" align="center" prop="createTime" width="180" />
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="签收" placement="top">
              <el-button link type="primary" icon="EditPen" @click="handleClaim(scope.row)" v-hasPermi="['workflow:process:claim']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Claim" lang="js">
import { listClaimProcess } from "#/api/workflow/work/process";
import { claimTask } from "#/api/workflow/work/task";
import { listAllCategory } from "#/api/workflow/category";
const router = useRouter();
const { proxy } = getCurrentInstance() ;
const claimList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref(['','']);
const categoryOptions = ref([]);
const queryFormRef = ref();
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  processName: ''
});
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
}
/** æŸ¥è¯¢å¾…办列表 */
const getList = async () => {
  loading.value = true;
  const res = await listClaimProcess(proxy.addDateRange(queryParams.value, dateRange.value));
  claimList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** ç­¾æ”¶æŒ‰é’®æ“ä½œ */
const handleClaim = async (row) => {
  const res = await claimTask({ taskId: row.taskId })
  proxy.$modal.msgSuccess(res.msg);
  router.push({ path: '/work/todo' })
}
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(k) {
        return k.code === row.category;
    });
    return category && category.categoryName ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList();
  getList();
});
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/work/copy.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,122 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="请选择"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </el-form-item>
          <el-form-item label="发起人" prop="originatorName">
            <el-input v-model="queryParams.originatorName" placeholder="请输入发起人" clearable style="width: 200px" @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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="copyList">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="抄送编号" align="center" prop="copyId" />
        <el-table-column label="标题" align="center" prop="title" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" prop="processName" :show-overflow-tooltip="true" />
        <el-table-column label="流程分类" align="center" prop="categoryId" :formatter="categoryFormat" />
        <el-table-column label="发起人" align="center" prop="originatorName" />
        <el-table-column label="创建时间" align="center" prop="createTime">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="详情" placement="top">
              <el-button link type="primary" icon="View" @click="handleDetails(scope.row)" v-hasPermi="['workflow:process:query']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Copy" lang="js">
import { listCopyProcess } from "#/api/workflow/work/process"
const router = useRouter();
import { listAllCategory } from "#/api/workflow/category";
const { proxy } = getCurrentInstance() ;
const copyList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const categoryOptions = ref([]);
const queryFormRef = ref();
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  processName: "",
  originatorName: "",
  category: ''
});
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
}
/** æŸ¥è¯¢å¾…办列表 */
const getList = async () => {
  loading.value = true;
  const res = await listCopyProcess(queryParams.value);
  copyList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** æµç¨‹è¯¦æƒ… */
const handleDetails = (row) => {
   router.push({
     path: '/workflow/process/detail/' + row.instanceId,
     query: {
       processed: false
     }
   })
}
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(find) {
        return find.code === row.categoryId;
    });
    return category && category.categoryName ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList();
  getList();
});
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/work/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,545 @@
<template>
  <div class="app-container">
    <el-tabs tab-position="top" :model-value="processed === true ? 'approval' : 'form'">
      <el-tab-pane label="任务办理" name="approval" v-if="processed === true">
        <el-card class="box-card" shadow="hover" v-if="taskFormOpen">
          <template #header>
            <span>填写表单</span>
          </template>
          <div class="cu-content">
            <v-form-render :form-json="{}" :form-data="{}" ref="vfRenderRef" />
          </div>
        </el-card>
        <el-card class="box-card" shadow="hover">
          <template #header>
            <span>审批流程</span>
          </template>
          <el-row>
            <el-col :span="20" :offset="2">
              <el-form ref="taskFormRef" :model="taskForm" :rules="rules" label-width="120px">
                <el-form-item label="审批意见" prop="comment">
                  <el-input type="textarea" :rows="5" v-model="taskForm.comment" placeholder="请输入 å®¡æ‰¹æ„è§" />
                </el-form-item>
                <el-form-item label="抄送人" prop="copyUserIds">
                  <el-tag :key="index" v-for="(item, index) in copyUser" closable :disable-transitions="false" @close="handleClose('copy', item)">
                    {{ item.nickName }}
                  </el-tag>
                  <el-button class="button-new-tag" type="primary" icon="el-icon-plus" circle @click="onSelectCopyUsers" />
                </el-form-item>
                <el-form-item label="指定审批人" prop="copyUserIds">
                  <el-tag :key="index" v-for="(item, index) in nextUser" closable :disable-transitions="false" @close="handleClose('next', item)">
                    {{ item.nickName }}
                  </el-tag>
                  <el-button class="button-new-tag" type="primary" icon="el-icon-plus" circle @click="onSelectNextUsers" />
                </el-form-item>
              </el-form>
            </el-col>
          </el-row>
          <el-row :gutter="10" type="flex" justify="center">
            <el-col :span="1.5">
              <el-button icon="CircleCheck" type="success" @click="handleComplete">通过</el-button>
            </el-col>
            <el-col :span="1.5">
              <el-button icon="ChatLineSquare" type="primary" @click="handleDelegate">委派</el-button>
            </el-col>
            <el-col :span="1.5">
              <el-button icon="Switch" type="success" @click="handleTransfer">转办</el-button>
            </el-col>
            <el-col :span="1.5">
              <el-button icon="RefreshLeft" type="warning" @click="handleReturn">退回</el-button>
            </el-col>
            <el-col :span="1.5">
              <el-button icon="CircleClose" type="danger" @click="handleReject">拒绝</el-button>
            </el-col>
          </el-row>
        </el-card>
      </el-tab-pane>
      <el-tab-pane label="表单信息" name="form">
        <div v-if="formVisible">
          <el-card class="box-card" shadow="never" v-for="(item, index) in processFormList" :key="index">
            <template #header>
              <span>{{ item.title }}</span>
            </template>
            <!--流程处理表单模块-->
            <div class="cu-content">
              <v-form-render :form-json="item.formModel" :form-data="item.formData" ref="vFormRenderRef" />
            </div>
          </el-card>
        </div>
      </el-tab-pane>
      <el-tab-pane label="流转记录" name="record">
        <el-card class="box-card" shadow="never">
          <el-col :span="20" :offset="2">
            <div class="block">
              <el-timeline>
                <el-timeline-item v-for="(item, index) in historyProcNodeList" :key="index" :type="tagType(item.endTime)">
                  <p style="font-weight: 700">{{ item.activityName }}</p>
                  <el-card v-if="item.activityType === 'startEvent'" class="box-card" shadow="hover">
                    {{ item.assigneeName }} åœ¨ {{ item.createTime }} å‘起流程
                  </el-card>
                  <el-card v-if="item.activityType === 'userTask'" class="box-card" shadow="hover">
                    <el-descriptions :column="5" :labelStyle="{'font-weight': 'bold'}">
                      <el-descriptions-item label="实际办理">{{ item.assigneeName || '-'}}</el-descriptions-item>
                      <el-descriptions-item label="候选办理">{{ item.candidate || '-'}}</el-descriptions-item>
                      <el-descriptions-item label="接收时间">{{ item.createTime || '-'}}</el-descriptions-item>
                      <el-descriptions-item label="办结时间">{{ item.endTime || '-' }}</el-descriptions-item>
                      <el-descriptions-item label="耗时">{{ item.duration || '-'}}</el-descriptions-item>
                    </el-descriptions>
                    <div v-if="item.commentList && item.commentList.length > 0">
                      <div v-for="(comment, index) in item.commentList" :key="index">
                        <el-divider content-position="left">
                          <el-tag :type="approveTypeTag(comment.type)">{{ commentType(comment.type) }}</el-tag>
                          <el-tag type="info" effect="plain">{{  parseTime(comment.time, '{y}-{m}-{d}  {h}:{i}:{s}')}}</el-tag>
                        </el-divider>
                        <span>{{ comment.fullMessage }}</span>
                      </div>
                    </div>
                  </el-card>
                  <el-card v-if="item.activityType === 'endEvent'" class="box-card" shadow="hover">
                    {{ item.createTime }} ç»“束流程
                  </el-card>
                </el-timeline-item>
              </el-timeline>
            </div>
          </el-col>
        </el-card>
      </el-tab-pane>
      <el-tab-pane label="流程跟踪" name="track">
        <el-card class="box-card" shadow="never">
          <ProcessViewer
            :key="`designer-${loadIndex}`"
            :style="'height:' + height"
            :xml="processXml"
            :finishedInfo="finishedInfo"
            :allCommentList="historyProcNodeList"
          />
        </el-card>
      </el-tab-pane>
    </el-tabs>
    <!--退回流程-->
    <el-dialog :title="returnDialog.title" v-model="returnDialog.visible" width="40%" append-to-body>
      <el-radio-group v-model="returnTaskKey">
        <el-radio-button v-for="item in returnTaskList" :key="item.id" :label="item.id">
          {{ item.name }}
        </el-radio-button>
      </el-radio-group>
      <template #footer>
        <el-button @click="returnDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitReturn">ç¡® å®š</el-button>
      </template>
    </el-dialog>
    <el-dialog :title="userSelectDialog.title" v-model="userSelectDialog.visible" width="60%" append-to-body>
      <el-row type="flex" :gutter="20">
        <!--部门数据-->
        <el-col :span="5">
          <el-card shadow="never" style="height: 100%">
            <template #header>
              <span>部门列表</span>
            </template>
            <div class="head-container">
              <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
              <el-tree
                :data="deptOptions"
                :props="{ label: 'label', children: 'children' }"
                :expand-on-click-node="false"
               :filter-node-method="filterNode"
                ref="deptTreeRef"
                default-expand-all
                @node-click="handleNodeClick"
              />
            </div>
          </el-card>
        </el-col>
        <el-col :span="18">
          <el-table
            ref="userTable"
            :key="userSelectType"
            height="500"
            v-loading="userLoading"
            :data="userList"
            highlight-current-row
            @current-change="changeCurrentUser"
            @selection-change="handleSelectionChange"
          >
            <el-table-column v-if="userSelectType === 'copy' || userSelectType === 'next'" width="55" type="selection" />
            <el-table-column v-else width="30">
              <template #default="scope">
                <el-radio :label="scope.row.userId" v-model="currentUserId">{{''}}</el-radio>
              </template>
            </el-table-column>
            <el-table-column label="用户名称" align="center" prop="userName" />
            <el-table-column label="用户昵称" align="center" prop="nickName" />
            <el-table-column label="手机" align="center" prop="phonenumber" />
          </el-table>
          <pagination :total="userTotal" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList(selectDeptId)" />
        </el-col>
      </el-row>
      <template #footer>
        <el-button @click="userSelectDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitUserData">ç¡® å®š</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Detail" lang="js">
import { detailProcess } from "#/api/workflow/work/process";
import { complete, delegate, transfer, rejectTask, returnList, returnTask } from "#/api/workflow/work/task";
import { deptTreeSelect, selectUser } from "#/api/workflow/identity";
import ProcessViewer from "#/components/ProcessViewer";
import {useRoute} from "vue-router";
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance() ;
const userList = ref([]);
const processed = ref(false);
const taskFormOpen = ref(false)
const userMultipleSelection = ref([]);
const userSelectType = ref();
const currentUserId = ref();
const userLoading = ref(false);
const userTotal = ref(0);
const loadIndex = ref(0);
const height = ref(document.documentElement.clientHeight - 205 + 'px;');
const processXml = ref('');
const taskFormVisible = ref(false);
const processFormList = ref([]);
const taskFormData = ref([]);
const historyProcNodeList = ref();
const formVisible = ref(false);
const finishedInfo = ref({});
const deptName = ref('');
const selectDeptId = ref('');
const deptOptions = ref([]);
const returnTaskList = ref();
const returnTaskKey = ref();
const copyUser = ref([]);
const nextUser = ref([]);
const taskFormRef = ref();
const vFormRenderRef = ref(null);
const deptTreeRef = ref(null);
const returnDialog = reactive({
  visible: false,
  title: '退回流程'
});
const userSelectDialog = reactive({
  visible: false,
  title: ''
});
const taskForm = reactive({
  comment: '',
  procInsId: '',
  taskId: '',
  userId: '',
  copyUserIds: '',
  nextUserIds: '',
  vars: '',
  targetKey: ''
});
const rules = ref({
  comment: [{ required: true, message: '请输入审批意见', trigger: 'blur' }]
});
const queryParams = ref({
  pageNum: 1,
  pageSize: 10
});
const tagType = (val) => {
  if (val) {
      return "success";
  } else {
      return "info";
  }
}
const commentType = (val) => {
  switch (val) {
    case '1': return '通过'
    case '2': return '退回'
    case '3': return '驳回'
    case '4': return '委派'
    case '5': return '转办'
    case '6': return '终止'
    case '7': return '撤回'
  }
}
const approveTypeTag = (val) => {
  switch (val) {
    case '1': return 'success'
    case '2': return 'warning'
    case '3': return 'danger'
    case '4': return 'primary'
    case '5': return 'success'
    case '6': return 'danger'
    case '7': return 'info'
  }
}
const initData = () => {
  taskForm.procInsId = route.params && route.params.procInsId ;
  taskForm.taskId  = route.query && route.query.taskId ;
  processed.value = route.query && (route.query.processed || false) === "true";
  // æµç¨‹ä»»åŠ¡é‡èŽ·å–å˜é‡è¡¨å•
  getProcessDetails(taskForm.procInsId, taskForm.taskId);
  loadIndex.value++;
};
/** é€šè¿‡æ¡ä»¶è¿‡æ»¤èŠ‚ç‚¹  */
const filterNode = (value, data) => {
  if (!value) return true
  return data.label.includes(value)
}
 /** æ ¹æ®åç§°ç­›é€‰éƒ¨é—¨æ ‘ */
watch(deptName, (val) => {
  deptTreeRef.value.filter(val)
})
// èŠ‚ç‚¹å•å‡»äº‹ä»¶
const handleNodeClick = (data) => {
  selectDeptId.value=data.id;
  getList(data.id);
}
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
const getTreeSelect = async () => {
  const res = await deptTreeSelect();
  deptOptions.value = res.data;
};
/** æŸ¥è¯¢ç”¨æˆ·åˆ—表 */
const getList = async (deptId) => {
  userLoading.value = true;
  const res = await selectUser(
    {
     deptId: deptId,
     pageNum:queryParams.value.pageNum,
     pageSize:queryParams.value.pageSize
    });
  userLoading.value = false;
  userList.value = res.rows;
  userTotal.value = res.total;
}
const getProcessDetails = async (procInsId, taskId) => {
  const params = {procInsId: procInsId, taskId: taskId}
  const res = await detailProcess(params);
  const data = res.data;
  processXml.value = data.bpmnXml;
  processFormList.value = data.processFormList;
  taskFormVisible.value = data.existTaskForm;
  if (taskFormVisible.value) {
    taskFormData.value = data.taskFormData;
  }
  historyProcNodeList.value = data.historyProcNodeList;
  finishedInfo.value = data.flowViewer;
  formVisible.value = true;
  nextTick(() => {
    processFormList.value.forEach((item, index) => {
      if (item.disabled) {
        vFormRenderRef.value[index].disableForm();
      }
    })
  })
}
const onSelectCopyUsers = () => {
  userMultipleSelection.value = copyUser.value;
  onSelectUsers('添加抄送人', 'copy')
}
const onSelectNextUsers = () => {
  userMultipleSelection.value = nextUser;
  onSelectUsers('指定审批人', 'next')
}
const onSelectUsers = (title, type) => {
  userSelectType.value = type;
  userSelectDialog.title = title;
  userSelectDialog.visible = true;
  getTreeSelect();
  getList()
}
/** é€šè¿‡ä»»åŠ¡ */
const handleComplete = () => {
  // æ ¡éªŒè¡¨å•
  taskFormRef.value.validate(async (valid) => {
    if (valid) {
      const res = await complete(taskForm)
      proxy.$modal.msgSuccess(res.msg);
      goBack();
    }
  });
}
/** å§”派任务 */
const handleDelegate = () => {
  userSelectType.value = 'delegate';
  userSelectDialog.title = '委派任务'
  userSelectDialog.visible = true;
  getTreeSelect();
}
/** è½¬åŠžä»»åŠ¡ */
const handleTransfer = () => {
  userSelectType.value = 'transfer';
  userSelectDialog.title = '转办任务';
  userSelectDialog.visible = true;
  getTreeSelect();
}
/** é€€å›žä»»åŠ¡ */
const handleReturn = async () => {
  // æ ¡éªŒè¡¨å•
  taskFormRef.value.validate(async (valid) => {
    if (valid) {
      const res = await returnList(taskForm);
      returnTaskList.value = res.data;
      returnDialog.visible = true;
    }
  });
}
/** æ‹’绝任务 */
const handleReject = async () => {
  await proxy.$modal.confirm('拒绝审批单流程会终止,是否继续?');
  await rejectTask(taskForm);
  proxy?.$modal.msgSuccess("操作成功");
  goBack();
}
/** è¿”回页面 */
const goBack = () => {
  // å…³é—­å½“前标签页并返回上个页面
  proxy?.$tab.closePage(route);
  router.back()
}
// å…³é—­æ ‡ç­¾
const handleClose = (type, tag) => {
  let userObj = userMultipleSelection.value.find(item => item.userId === tag.id);
  userMultipleSelection.value.splice(userMultipleSelection.value.indexOf(userObj), 1);
  if (type === 'copy') {
    copyUser.value = userMultipleSelection.value;
    // è®¾ç½®æŠ„送人ID
    if (copyUser.value && copyUser.value.length > 0) {
      const val = copyUser.value.map(item => item.id);
      taskForm.copyUserIds = val instanceof Array ? val.join(',') : val;
    } else {
      taskForm.copyUserIds = '';
    }
  } else if (type === 'next') {
    nextUser.value = userMultipleSelection.value;
    // è®¾ç½®æŠ„送人ID
    if (nextUser.value && nextUser.value.length > 0) {
      const val = nextUser.value.map(item => item.id);
      taskForm.nextUserIds = val instanceof Array ? val.join(',') : val;
    } else {
      taskForm.nextUserIds = '';
    }
  }
}
const changeCurrentUser = (val) => {
  // currentUserId = val.userId
}
const handleSelectionChange = (val) => {
  userMultipleSelection.value=val;
}
const submitReturn = () => {
  // æ ¡éªŒè¡¨å•
  taskFormRef.value.validate(async (valid) => {
    if (valid) {
      if (!returnTaskKey) {
        proxy.$modal.msgError("请选择退回节点!");
      }
      taskForm.targetKey = returnTaskKey.value;
      const res = await returnTask(taskForm);
      proxy.$modal.msgSuccess(res.msg);
      goBack()
    }
  });
  console.log("taskForm => ", taskForm.targetKey);
}
const submitUserData = () => {
  let type = userSelectType.value;
  if (type === 'copy' || type === 'next') {
    if (!userMultipleSelection || userMultipleSelection.value.length <= 0) {
      proxy.$modal.msgError("请选择用户");
      return false;
    }
    let userIds = userMultipleSelection.value.map(k => k.userId);
    if (type === 'copy') {
      // è®¾ç½®æŠ„送人ID信息
      copyUser.value = userMultipleSelection.value;
      taskForm.copyUserIds = userIds instanceof Array ? userIds.join(',') : userIds;
    } else if (type === 'next') {
      // è®¾ç½®ä¸‹ä¸€çº§å®¡æ‰¹äººID信息
      nextUser.value = userMultipleSelection.value;
      taskForm.nextUserIds = userIds instanceof Array ? userIds.join(',') : userIds;
    }
    userSelectDialog.visible = false;
  } else {
    if (!taskForm.comment) {
      proxy?.$modal.msgError("请输入审批意见");
      return false;
    }
    if (!currentUserId.value) {
      proxy?.$modal.msgError("请选择用户");
      return false;
    }
    taskForm.userId = currentUserId.value;
    if (type === 'delegate') {
      delegate(taskForm).then(res => {
        proxy?.$modal.msgSuccess(res.msg);
        goBack();
      });
    }
    if (type === 'transfer') {
      transfer(taskForm).then(res => {
        proxy?.$modal.msgSuccess(res.msg);
        goBack();
      });
    }
  }
}
onMounted(() => {
  initData();
});
</script>
<style lang="scss" scoped>
.clearfix:before,
.clearfix:after {
  display: table;
  content: "";
}
.clearfix:after {
  clear: both
}
.box-card {
  width: 100%;
  margin-bottom: 20px;
}
.el-tag + .el-tag {
  margin-left: 10px;
}
.el-row {
  margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0;
  }
}
.el-col {
  border-radius: 4px;
}
.button-new-tag {
  margin-left: 10px;
}
</style>
ruoyi-ui/apps/web-antd/src/views/workflow/work/finished.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,140 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="请选择"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </el-form-item>
          <el-form-item label="审批时间" style="width: 308px;">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
            ></el-date-picker>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="finishedList">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" prop="procDefName" :show-overflow-tooltip="true" />
        <el-table-column label="流程分类" align="center" prop="category" :formatter="categoryFormat" />
        <el-table-column label="任务节点" align="center" prop="taskName" />
        <el-table-column label="流程发起人" align="center" prop="startUserName" />
        <el-table-column label="接收时间" align="center" prop="createTime" width="180" />
        <el-table-column label="审批时间" align="center" prop="finishTime" width="180" />
        <el-table-column label="耗时" align="center" prop="duration" width="180" />
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="详情" placement="top">
              <el-button link type="primary" icon="View" @click="handleDetails(scope.row)" v-hasPermi="['workflow:process:query']"></el-button>
            </el-tooltip>
            <el-tooltip content="撤回" placement="top">
              <el-button link type="primary" icon="RefreshLeft" @click="handleRevoke(scope.row)" v-hasPermi="['workflow:process:revoke']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Finished" lang="js">
import { listFinishedProcess } from "#/api/workflow/work/process";
import { revokeProcess } from "#/api/workflow/work/task";
import { listAllCategory } from "#/api/workflow/category";
const router = useRouter();
const { proxy } = getCurrentInstance() ;
const finishedList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref(['','']);
const categoryOptions = ref([]);
const queryFormRef = ref();
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  processName: '',
  category: ''
});
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
}
/** æŸ¥è¯¢å¾…办列表 */
const getList = async () => {
  loading.value = true;
  const res = await listFinishedProcess(proxy.addDateRange(queryParams.value, dateRange.value));
  finishedList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** æµç¨‹æµè½¬è®°å½• */
const handleDetails = (row) => {
  router.push({
    path: '/workflow/process/detail/' + row.procInsId,
    query: {
      processed: false
    }
  })
}
/** æ’¤å›žä»»åŠ¡ */
const handleRevoke = async (row) => {
  const params = {
    procInsId: row.procInsId,
    taskId: row.taskId
  };
  const res = await revokeProcess(params);
  proxy.$modal.msgSuccess(res.msg);
  getList();
}
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(find) {
        return find.code === row.category;
    });
    return category && category.categoryName ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList();
  getList();
});
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/work/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="流程标识" prop="processKey">
            <el-input v-model="queryParams.processKey" placeholder="请输入流程标识" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="请选择"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="processList">
        <el-table-column label="流程标识" align="center" prop="processKey" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" :show-overflow-tooltip="true">
          <template #default="scope">
            <a  @click="handleProcessView(scope.row)" class="link-type">
              <span>{{ scope.row.processName }}</span>
            </a>
          </template>
        </el-table-column>
        <el-table-column label="流程分类" align="center" prop="categoryName" :formatter="categoryFormat" />
        <el-table-column label="流程版本" align="center">
          <template #default="scope">
            <el-tag>v{{ scope.row.version }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="状态" align="center">
          <template #default="scope">
            <el-tag type="success" v-if="!scope.row.suspended">激活</el-tag>
            <el-tag type="warning" v-if="scope.row.suspended">挂起</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="部署时间" align="center" prop="deploymentTime" width="180" >
          <template #default="scope">
              {{  parseTime(scope.row.deploymentTime, '{y}-{m}-{d}  {h}:{i}:{s}')}}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="发起" placement="top">
              <el-button link type="primary" icon="VideoPlay" @click="handleStart(scope.row)" v-hasPermi="['workflow:process:start']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æµç¨‹å›¾ -->
    <el-dialog :title="processDialog.title" v-model="processDialog.visible" width="70%">
      <ProcessViewer :key="`designer-${reloadIndex}`" :xml="processXml" :style="{height: '650px'}" />
    </el-dialog>
  </div>
</template>
<script setup name="WorkProcess" lang="js">
import { listProcess, getBpmnXml } from "#/api/workflow/work/process";
import { listAllCategory } from "#/api/workflow/category";
import ProcessViewer from "#/components/ProcessViewer/index.vue";
const router = useRouter();
const { proxy } = getCurrentInstance() ;
const processList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const reloadIndex = ref(0);
const processXml = ref("");
const categoryOptions = ref([]);
const queryFormRef = ref();
const processDialog = reactive({
  visible: false,
  title: '流程图'
});
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  processKey: '',
  processName: '',
  category: ''
});
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
}
/** æŸ¥è¯¢æµç¨‹åˆ—表 */
const getList = async () => {
  loading.value = true;
  const res = await listProcess(queryParams.value);
  processList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** æŸ¥çœ‹æµç¨‹å›¾ */
const handleProcessView = async (row) => {
  reloadIndex.value++;
  // å‘送请求,获取xml
  const res = await getBpmnXml(row.definitionId);
  console.log(res.data);
  processXml.value = res.data;
  processDialog.visible = true;
}
/** å‘起流程 */
const handleStart = (row) => {
  router.push({
    path: '/workflow/process/start/' + row.deploymentId,
    query: {
      definitionId: row.definitionId,
    }
  })
};
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(k) {
        return k.code === row.category;
    });
    return category ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList();
  getList();
});
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/work/own.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="流程标识" prop="processKey">
            <el-input v-model="queryParams.processKey" placeholder="请输入流程标识" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="流程分类"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </el-form-item>
          <el-form-item label="提交时间" style="width: 308px;">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
            ></el-date-picker>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:process:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="ownProcessList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="流程编号" align="center" prop="procInsId" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" prop="procDefName" :show-overflow-tooltip="true" />
        <el-table-column label="流程类别" align="center" prop="category" :formatter="categoryFormat" />
        <el-table-column label="流程版本" align="center" width="80px">
          <template #default="scope">
            <el-tag>v{{ scope.row.procDefVersion }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="当前节点" align="center" prop="taskName" />
        <el-table-column label="提交时间" align="center" prop="createTime" width="180" />
        <el-table-column label="流程状态" align="center" width="100">
          <template #default="scope">
            <dict-tag :options="wf_process_status" :value="scope.row.processStatus" />
          </template>
        </el-table-column>
        <el-table-column label="耗时" align="center" prop="duration" width="180" />
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="详情" placement="top">
              <el-button link type="primary" icon="View" @click="handleDetails(scope.row)" v-hasPermi="['workflow:process:query']"></el-button>
            </el-tooltip>
            <el-tooltip content="取消" placement="top">
              <el-button link type="primary" icon="CircleClose" @click="handleStop(scope.row)" v-hasPermi="['workflow:process:cancel']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:process:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Own" lang="js">
import { listOwnProcess, stopProcess, delProcess } from "#/api/workflow/work/process";
import { listAllCategory } from "#/api/workflow/category";
const router = useRouter();
const { proxy } = getCurrentInstance() ;
const { wf_process_status } = proxy.useDict("wf_process_status");
const categoryOptions = ref([]);
const ownProcessList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref(['','']);
const queryFormRef = ref();
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  processKey: '',
  processName: '',
  category: ''
});
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
};
/** æŸ¥è¯¢æˆ‘的流程列表 */
const getList = async () => {
  loading.value = true;
  const res = await listOwnProcess(proxy.addDateRange(queryParams.value, dateRange.value));
  ownProcessList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection) => {
  ids.value = selection.map(item => item.procInsId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æµç¨‹è¯¦æƒ… */
const handleDetails = (row) => {
  router.push({
    path: '/workflow/process/detail/' + row.procInsId,
    query: {
      processed: false
    }
  })
}
/** å–消流程申请 */
const handleStop = async (row) => {
  await stopProcess( { procInsId: row.procInsId });
  proxy?.$modal.msgSuccess("操作成功");
  getList();
}
/** å†æ¬¡å‘起流程 */
const handleAgain = (row) => {
  // router.push({
  //   path: '/workflow/process/start/' + row.deployId,
  //   query: {
  //     definitionId: row.procDefId,
  //     procInsId: row.procInsId
  //   }
  // })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row) => {
  const procInsIds = row.procInsId || ids.value;
  await proxy.$modal.confirm('是否确认删除流程定义编号为"' + procInsIds + '"的数据项?');
  await delProcess(procInsIds);
  getList();
  proxy.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy.download("workflow/process/ownExport", {
    ...queryParams.value
  }, `own_process_${new Date().getTime()}.xlsx`);
}
const categoryFormat = (row) => {
  return categoryOptions.value.find(k => k.code === row.category).categoryName ?categoryOptions.value.find(k => k.code === row.category).categoryName: '';
}
onMounted(() => {
  getCategoryList();
  getList();
});
</script>
ruoyi-ui/apps/web-antd/src/views/workflow/work/start.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <template #header>
        <span>发起流程</span>
      </template>
      <div class="form-conf" v-if="dialog.visible">
        <v-form-render :form-json="formModel" :form-data="formData" ref="vfRenderRef"></v-form-render>
        <div class="cu-submit">
          <el-button type="primary" @click="submit">提交</el-button>
          <el-button @click="reset">重置</el-button>
        </div>
      </div>
    </el-card>
  </div>
</template>
<script setup name="WorkStart" lang="js">
import { getProcessForm, startProcess } from '#/api/workflow/work/process';
const route = useRoute();
const { proxy } = getCurrentInstance() ;
const vfRenderRef = ref(null);
const deployId = ref();
const definitionId = ref();
const formModel = ref({});
const formData = ref({});
const dialog = reactive({
  visible: false,
  title: ''
});
const initData = async () => {
  deployId.value = route.params && route.params.deployId;
  definitionId.value = route.query && route.query.definitionId;
  const res = await getProcessForm({ definitionId: definitionId.value, deployId: deployId.value });
  formModel.value = res.data.formModel;
  dialog.visible = true;
  nextTick(async () => {
    vfRenderRef.value.setFormJson(formModel.value || {formConfig: {}, widgetList: []});
  });
}
const submit = async () => {
  const data = await vfRenderRef.value.getFormData();
  if (definitionId.value) {
    const res = await startProcess(definitionId.value, JSON.stringify(data));
    proxy.$modal.msgSuccess(res.msg);
    // const obj = { path: "/work/own" };
    // proxy?.$tab.closeOpenPage(obj);
    proxy.$tab.closePage();
    proxy.$router.back();
  }
}
const reset = () => {
  vfRenderRef.value.resetForm();
}
onMounted(() => {
  initData();
});
</script>
<style lang="scss" scoped>
.form-conf {
  margin: 15px auto;
  width: 80%;
  padding: 15px;
}
</style>
ruoyi-ui/apps/web-antd/src/views/workflow/work/todo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
<template>
 <div class="app-container">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form-item label="流程名称" prop="processName">
            <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="流程分类" prop="category">
            <el-select v-model="queryParams.category" clearable placeholder="请选择"  style="width: 240px">
              <el-option v-for="item in categoryOptions" :key="item.categoryId" :label="item.categoryName" :value="item.code" />
            </el-select>
          </el-form-item>
          <el-form-item label="接收时间" style="width: 308px;">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
            ></el-date-picker>
          </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>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="todoList">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true" />
        <el-table-column label="流程名称" align="center" prop="procDefName" />
        <el-table-column label="任务节点" align="center" prop="taskName" />
        <el-table-column label="流程分类" align="center" prop="category" :formatter="categoryFormat" />
        <el-table-column label="流程版本" align="center">
          <template #default="scope">
            <el-tag>v{{scope.row.procDefVersion}}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="流程发起人" align="center" prop="startUserName" />
        <el-table-column label="接收时间" align="center" prop="createTime" width="180" />
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="办理" placement="top">
              <el-button link type="primary" icon="EditPen" @click="handleProcess(scope.row)" v-hasPermi="['workflow:process:approval']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Todo" lang="js">
import { listTodoProcess } from '#/api/workflow/work/process';
import { listAllCategory } from "#/api/workflow/category";
const router = useRouter();
const { proxy } = getCurrentInstance() ;
const todoList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref(['','']);
const categoryOptions = ref([]);
const queryFormRef = ref();
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  processName: '',
  category: ''
});
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表 */
const getCategoryList = async () => {
  const res = await listAllCategory();
  categoryOptions.value = res.data;
}
/** æŸ¥è¯¢å¾…办列表 */
const getList = async () => {
  loading.value = true;
  const res = await listTodoProcess(proxy.addDateRange(queryParams.value, dateRange.value));
  todoList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
}
/** è·³è½¬åˆ°å¤„理页面 */
const handleProcess = (row) => {
  router.push({
    path: '/workflow/process/detail/' + row.procInsId,
    query: {
      taskId: row.taskId,
      processed: true
    }
  })
}
const categoryFormat = (row) => {
  var category = categoryOptions.value.find(function(k) {
        return k.code === row.category;
    });
    return category && category.categoryName ? category.categoryName : '';
}
onMounted(() => {
  getCategoryList();
  getList();
});
</script>