兰宝车间质量管理系统-前端
baoshiwei
2025-03-12 6b988bd582bfcd17fee48c476a5a6e5cc172b0d5
dev-2
已添加52个文件
7553 ■■■■■ 文件已修改
src/api/workflow/definitionConfig/index.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/definitionConfig/types.ts 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/formManage/index.ts 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/formManage/types.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/model/index.ts 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/model/types.ts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/nodeConfig/types.ts 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processDefinition/index.ts 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processDefinition/types.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processInstance/index.ts 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processInstance/types.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/defaultXML.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/lang/zh.ts 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/moddle/flowable.ts 1250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/ContextPad/CustomContextPadProvider.ts 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/Palette/CustomPaletteProvider.ts 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/Renderer/CustomRenderer.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/Translate/index.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/index.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/showConfig.ts 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/style/index.scss 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/hooks/usePanel.ts 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/hooks/useParseElement.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/index.vue 496 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/GatewayPanel.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/ParticipantPanel.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/ProcessPanel.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/SequenceFlowPanel.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/StartEndPanel.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/SubProcessPanel.vue 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/TaskPanel.vue 491 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/index.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/DueDate.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/ExecutionListener.vue 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/ListenerParam.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/TaskListener.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BpmnDesign/index.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BpmnView/index.vue 411 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Process/multiInstanceUser.vue 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/SettingTypeEnum.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/bpmn/IndexEnums.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/layout/LayoutEnum.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/en_US.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/zh_CN.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/modeler.ts 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/editor/global.d.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/index.d.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/moddle.d.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/panel.d.ts 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/formManage/index.vue 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/model/index.vue 383 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/processDefinition/components/processPreview.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/definitionConfig/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DefinitionConfigVO, DefinitionConfigForm } from '@/api/workflow/definitionConfig/types';
/**
 * æŸ¥è¯¢è¡¨å•配置详细
 * @param definitionId
 */
export const getByDefId = (definitionId: string | number): AxiosPromise<DefinitionConfigVO> => {
  return request({
    url: '/workflow/definitionConfig/getByDefId/' + definitionId,
    method: 'get'
  });
};
/**
 * æ–°å¢žè¡¨å•配置
 * @param data
 */
export const saveOrUpdate = (data: DefinitionConfigForm) => {
  return request({
    url: '/workflow/definitionConfig/saveOrUpdate',
    method: 'post',
    data: data
  });
};
/**
 * åˆ é™¤è¡¨å•配置
 * @param id
 */
export const deldefinitionConfig = (id: string | number | Array<string | number>) => {
  return request({
    url: '/workflow/definitionConfig/' + id,
    method: 'delete'
  });
};
/**
 * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®æŽ’除当前查询的流程定义
 * @param tableName
 * @param definitionId
 */
export const getByTableNameNotDefId = (tableName: string, definitionId: string | number) => {
  return request({
    url: `/workflow/definitionConfig/getByTableNameNotDefId/${tableName}/${definitionId}`,
    method: 'get'
  });
};
src/api/workflow/definitionConfig/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
import { FormManageVO } from '@/api/workflow/formManage/types';
export interface DefinitionConfigVO {
  /**
   * ä¸»é”®
   */
  id: string | number;
  /**
   * è¡¨å
   */
  tableName?: string;
  /**
   * æµç¨‹å®šä¹‰ID
   */
  definitionId: string | number;
  /**
   * æµç¨‹KEY
   */
  processKey: string;
  /**
   * æµç¨‹ç‰ˆæœ¬
   */
  version?: string | number;
  /**
   * å¤‡æ³¨
   */
  remark: string;
  /**
   * è¡¨å•管理
   */
  wfFormManageVo: FormManageVO;
}
export interface DefinitionConfigForm extends BaseEntity {
  /**
   * ä¸»é”®
   */
  id?: string | number;
  /**
   * è¡¨å
   */
  tableName?: string;
  /**
   * æµç¨‹å®šä¹‰ID
   */
  definitionId?: string | number;
  /**
   * æµç¨‹KEY
   */
  processKey?: string;
  /**
   * æµç¨‹ç‰ˆæœ¬
   */
  version?: string | number;
  /**
   * å¤‡æ³¨
   */
  remark?: string;
  /**
   * è¡¨å•管理
   */
  wfFormManageVo?: FormManageVO;
}
export interface DefinitionConfigQuery extends PageQuery {
  /**
   * è¡¨å
   */
  tableName?: string;
  /**
   * æµç¨‹å®šä¹‰ID
   */
  definitionId?: string | number;
  /**
   * æµç¨‹KEY
   */
  processKey?: string;
  /**
   * æµç¨‹ç‰ˆæœ¬
   */
  version?: string | number;
  /**
   * è¡¨å•管理
   */
  wfFormManageVo: FormManageVO;
}
src/api/workflow/formManage/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FormManageVO, FormManageForm, FormManageQuery } from '@/api/workflow/formManage/types';
/**
 * æŸ¥è¯¢è¡¨å•管理列表
 * @param query
 * @returns {*}
 */
export const listFormManage = (query?: FormManageQuery): AxiosPromise<FormManageVO[]> => {
  return request({
    url: '/workflow/formManage/list',
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢è¡¨å•管理列表
 * @param query
 * @returns {*}
 */
export const selectListFormManage = (): AxiosPromise<FormManageVO[]> => {
  return request({
    url: '/workflow/formManage/list/selectList',
    method: 'get'
  });
};
/**
 * æŸ¥è¯¢è¡¨å•管理详细
 * @param id
 */
export const getFormManage = (id: string | number): AxiosPromise<FormManageVO> => {
  return request({
    url: '/workflow/formManage/' + id,
    method: 'get'
  });
};
/**
 * æ–°å¢žè¡¨å•管理
 * @param data
 */
export const addFormManage = (data: FormManageForm) => {
  return request({
    url: '/workflow/formManage',
    method: 'post',
    data: data
  });
};
/**
 * ä¿®æ”¹è¡¨å•管理
 * @param data
 */
export const updateFormManage = (data: FormManageForm) => {
  return request({
    url: '/workflow/formManage',
    method: 'put',
    data: data
  });
};
/**
 * åˆ é™¤è¡¨å•管理
 * @param id
 */
export const delFormManage = (id: string | number | Array<string | number>) => {
  return request({
    url: '/workflow/formManage/' + id,
    method: 'delete'
  });
};
src/api/workflow/formManage/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
export interface FormManageVO {
  /**
   * ä¸»é”®
   */
  id: string | number;
  /**
   * è¡¨å•名称
   */
  formName: string;
  /**
   * è¡¨å•类型
   */
  formType: string;
  /**
   * è¡¨å•类型名称
   */
  formTypeName: string;
  /**
   * è·¯ç”±åœ°å€/表单ID
   */
  router: string;
  /**
   * å¤‡æ³¨
   */
  remark: string;
}
export interface FormManageForm extends BaseEntity {
  /**
   * ä¸»é”®
   */
  id?: string | number;
  /**
   * è¡¨å•名称
   */
  formName?: string;
  /**
   * è¡¨å•类型
   */
  formType?: string;
  /**
   * è·¯ç”±åœ°å€/表单ID
   */
  router?: string;
  /**
   * å¤‡æ³¨
   */
  remark?: string;
}
export interface FormManageQuery extends PageQuery {
  /**
   * è¡¨å•名称
   */
  formName?: string;
  /**
   * è¡¨å•类型
   */
  formType?: string;
}
src/api/workflow/model/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ModelForm, ModelQuery, ModelVO } from '@/api/workflow/model/types';
/**
 * æŸ¥è¯¢æ¨¡åž‹åˆ—表
 * @param query
 * @returns {*}
 */
export const listModel = (query: ModelQuery): AxiosPromise<ModelVO[]> => {
  return request({
    url: '/workflow/model/list',
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢æ¨¡åž‹ä¿¡æ¯
 * @param query
 * @returns {*}
 */
export const getInfo = (id: string): AxiosPromise<ModelForm> => {
  return request({
    url: '/workflow/model/getInfo/' + id,
    method: 'get'
  });
};
/**
 * æ–°å¢žæ¨¡åž‹
 * @param data
 * @returns {*}
 */
export const addModel = (data: ModelForm): AxiosPromise<void> => {
  return request({
    url: '/workflow/model/save',
    method: 'post',
    data: data
  });
};
/**
 * ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
 * @param data
 * @returns {*}
 */
export function update(data: ModelForm): AxiosPromise<void> {
  return request({
    url: '/workflow/model/update',
    method: 'put',
    data: data
  });
}
/**
 * ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
 * @param data
 * @returns {*}
 */
export function editModelXml(data: ModelForm): AxiosPromise<void> {
  return request({
    url: '/workflow/model/editModelXml',
    method: 'put',
    data: data
  });
}
/**
 * æŒ‰id删除模型
 * @returns {*}
 * @param id æ¨¡åž‹id
 */
export function delModel(id: string | string[]): AxiosPromise<void> {
  return request({
    url: '/workflow/model/' + id,
    method: 'delete'
  });
}
/**
 * æ¨¡åž‹éƒ¨ç½²
 * @returns {*}
 * @param id æ¨¡åž‹id
 */
export const modelDeploy = (id: string): AxiosPromise<void> => {
  return request({
    url: `/workflow/model/modelDeploy/${id}`,
    method: 'post'
  });
};
/**
 * å¤åˆ¶æ¨¡åž‹
 * @param data
 * @returns {*}
 */
export const copyModel = (data: ModelForm): AxiosPromise<void> => {
  return request({
    url: '/workflow/model/copyModel',
    method: 'post',
    data: data
  });
};
src/api/workflow/model/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
export interface ModelForm {
  id: string;
  name: string;
  key: string;
  categoryCode: string;
  xml: string;
  svg: string;
  description: string;
}
export interface ModelQuery extends PageQuery {
  name?: string;
  key?: string;
  categoryCode?: string;
}
export interface OriginalPersistentState {
  metaInfo: string;
  editorSourceValueId: string;
  createTime: string;
  deploymentId?: string;
  name: string;
  tenantId: string;
  category?: string;
  version: number;
  editorSourceExtraValueId?: string;
  key: string;
  lastUpdateTime: string;
}
export interface PersistentState {
  metaInfo: string;
  editorSourceValueId: string;
  createTime: string;
  deploymentId?: string;
  name: string;
  tenantId: string;
  category?: string;
  version: number;
  editorSourceExtraValueId?: string;
  key: string;
  lastUpdateTime: string;
}
export interface ModelVO {
  id: string;
  revision: number;
  originalPersistentState: OriginalPersistentState;
  name: string;
  key: string;
  category?: string;
  createTime: string;
  lastUpdateTime: string;
  version: number;
  metaInfo: string;
  deploymentId?: string;
  editorSourceValueId: string;
  editorSourceExtraValueId?: string;
  tenantId: string;
  persistentState: PersistentState;
  revisionNext: number;
  idPrefix: string;
  inserted: boolean;
  updated: boolean;
  deleted: boolean;
}
src/api/workflow/nodeConfig/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
import { FormManageVO } from '@/api/workflow/formManage/types';
export interface NodeConfigVO {
  /**
   * ä¸»é”®
   */
  id: string | number;
  /**
   * è¡¨å•id
   */
  formId: string | number;
  /**
   * è¡¨å•类型
   */
  formType: string;
  /**
   * èŠ‚ç‚¹åç§°
   */
  nodeName: string;
  /**
   * èŠ‚ç‚¹id
   */
  nodeId: string | number;
  /**
   * æµç¨‹å®šä¹‰id
   */
  definitionId: string | number;
  /**
   * è¡¨å•管理
   */
  wfFormManageVo: FormManageVO;
}
src/api/workflow/processDefinition/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
import request from '@/utils/request';
import { ProcessDefinitionQuery, ProcessDefinitionVO, definitionXmlVO } from '@/api/workflow/processDefinition/types';
import { AxiosPromise } from 'axios';
/**
 * èŽ·å–æµç¨‹å®šä¹‰åˆ—è¡¨
 * @param query æµç¨‹å®žä¾‹id
 * @returns
 */
export const listProcessDefinition = (query: ProcessDefinitionQuery): AxiosPromise<ProcessDefinitionVO[]> => {
  return request({
    url: `/workflow/processDefinition/list`,
    method: 'get',
    params: query
  });
};
/**
 * æŒ‰ç…§æµç¨‹å®šä¹‰key获取流程定义
 * @param processInstanceId æµç¨‹å®žä¾‹id
 * @returns
 */
export const getListByKey = (key: string) => {
  return request({
    url: `/workflow/processDefinition/getListByKey/${key}`,
    method: 'get'
  });
};
/**
 * é€šè¿‡æµç¨‹å®šä¹‰id获取流程图
 */
export const definitionImage = (processDefinitionId: string): AxiosPromise<any> => {
  return request({
    url: `/workflow/processDefinition/definitionImage/${processDefinitionId}` + '?t' + Math.random(),
    method: 'get'
  });
};
/**
 * é€šè¿‡æµç¨‹å®šä¹‰id获取xml
 * @param processDefinitionId æµç¨‹å®šä¹‰id
 * @returns
 */
export const definitionXml = (processDefinitionId: string): AxiosPromise<definitionXmlVO> => {
  return request({
    url: `/workflow/processDefinition/definitionXml/${processDefinitionId}`,
    method: 'get'
  });
};
/**
 * åˆ é™¤æµç¨‹å®šä¹‰
 * @param deploymentId éƒ¨ç½²id
 * @param processDefinitionId æµç¨‹å®šä¹‰id
 * @returns
 */
export const deleteProcessDefinition = (deploymentId: string | string[], processDefinitionId: string | string[]) => {
  return request({
    url: `/workflow/processDefinition/${deploymentId}/${processDefinitionId}`,
    method: 'delete'
  });
};
/**
 * æŒ‚èµ·/激活
 * @param processDefinitionId æµç¨‹å®šä¹‰id
 * @returns
 */
export const updateDefinitionState = (processDefinitionId: string) => {
  return request({
    url: `/workflow/processDefinition/updateDefinitionState/${processDefinitionId}`,
    method: 'put'
  });
};
/**
 * æµç¨‹å®šä¹‰è½¬æ¢ä¸ºæ¨¡åž‹
 * @param processDefinitionId æµç¨‹å®šä¹‰id
 * @returns
 */
export const convertToModel = (processDefinitionId: string) => {
  return request({
    url: `/workflow/processDefinition/convertToModel/${processDefinitionId}`,
    method: 'put'
  });
};
/**
 * é€šè¿‡zip或xml部署流程定义
 * @returns
 */
export function deployProcessFile(data: any) {
  return request({
    url: '/workflow/processDefinition/deployByFile',
    method: 'post',
    data: data,
    headers: {
      repeatSubmit: false
    }
  });
}
/**
 * è¿ç§»æµç¨‹
 * @param currentProcessDefinitionId
 * @param fromProcessDefinitionId
 * @returns
 */
export const migrationDefinition = (currentProcessDefinitionId: string, fromProcessDefinitionId: string) => {
  return request({
    url: `/workflow/processDefinition/migrationDefinition/${currentProcessDefinitionId}/${fromProcessDefinitionId}`,
    method: 'put'
  });
};
src/api/workflow/processDefinition/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
import { DefinitionConfigVO } from '@/api/workflow/definitionConfig/types';
export interface ProcessDefinitionQuery extends PageQuery {
  key?: string;
  name?: string;
  categoryCode?: string;
}
export interface ProcessDefinitionVO extends BaseEntity {
  id: string;
  name: string;
  key: string;
  version: number;
  suspensionState: number;
  resourceName: string;
  diagramResourceName: string;
  deploymentId: string;
  deploymentTime: string;
  wfDefinitionConfigVo: DefinitionConfigVO;
}
export interface definitionXmlVO {
  xml: string[];
  xmlStr: string;
}
src/api/workflow/processInstance/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,136 @@
import request from '@/utils/request';
import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types';
import { AxiosPromise } from 'axios';
/**
 * æŸ¥è¯¢è¿è¡Œä¸­å®žä¾‹åˆ—表
 * @param query
 * @returns {*}
 */
export const getPageByRunning = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => {
  return request({
    url: '/workflow/processInstance/getPageByRunning',
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢å·²å®Œæˆå®žä¾‹åˆ—表
 * @param query
 * @returns {*}
 */
export const getPageByFinish = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => {
  return request({
    url: '/workflow/processInstance/getPageByFinish',
    method: 'get',
    params: query
  });
};
/**
 * é€šè¿‡ä¸šåŠ¡id获取历史流程图
 */
export const getHistoryImage = (businessKey: string) => {
  return request({
    url: `/workflow/processInstance/getHistoryImage/${businessKey}` + '?t' + Math.random(),
    method: 'get'
  });
};
/**
 * é€šè¿‡ä¸šåŠ¡id获取历史流程图运行中,历史等节点
 */
export const getHistoryList = (businessKey: string): AxiosPromise<Record<string, any>> => {
  return request({
    url: `/workflow/processInstance/getHistoryList/${businessKey}` + '?t' + Math.random(),
    method: 'get'
  });
};
/**
 * èŽ·å–å®¡æ‰¹è®°å½•
 * @param businessKey ä¸šåŠ¡id
 * @returns
 */
export const getHistoryRecord = (businessKey: string | number) => {
  return request({
    url: `/workflow/processInstance/getHistoryRecord/${businessKey}`,
    method: 'get'
  });
};
/**
 * ä½œåºŸ
 * @param data å‚æ•°
 * @returns
 */
export const deleteRunInstance = (data: object) => {
  return request({
    url: `/workflow/processInstance/deleteRunInstance`,
    method: 'post',
    data: data
  });
};
/**
 * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
 * @param businessKey ä¸šåŠ¡id
 * @returns
 */
export const deleteRunAndHisInstance = (businessKey: string | string[]) => {
  return request({
    url: `/workflow/processInstance/deleteRunAndHisInstance/${businessKey}`,
    method: 'delete'
  });
};
/**
 * å·²å®Œæˆçš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
 * @param businessKey ä¸šåŠ¡id
 * @returns
 */
export const deleteFinishAndHisInstance = (businessKey: string | string[]) => {
  return request({
    url: `/workflow/processInstance/deleteFinishAndHisInstance/${businessKey}`,
    method: 'delete'
  });
};
/**
 * åˆ†é¡µæŸ¥è¯¢å½“前登录人单据
 * @param query
 * @returns {*}
 */
export const getPageByCurrent = (query: ProcessInstanceQuery): AxiosPromise<ProcessInstanceVO[]> => {
  return request({
    url: '/workflow/processInstance/getPageByCurrent',
    method: 'get',
    params: query
  });
};
/**
 * æ’¤é”€æµç¨‹
 * @param businessKey ä¸šåŠ¡id
 * @returns
 */
export const cancelProcessApply = (businessKey: string) => {
  return request({
    url: `/workflow/processInstance/cancelProcessApply/${businessKey}`,
    method: 'post'
  });
};
export default {
  getPageByRunning,
  getPageByFinish,
  getHistoryImage,
  getHistoryList,
  getHistoryRecord,
  deleteRunInstance,
  deleteRunAndHisInstance,
  deleteFinishAndHisInstance,
  getPageByCurrent,
  cancelProcessApply
};
src/api/workflow/processInstance/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import { TaskVO } from '@/api/workflow/task/types';
export interface ProcessInstanceQuery extends PageQuery {
  categoryCode?: string;
  name?: string;
  key?: string;
  startUserId?: string;
  businessKey?: string;
}
export interface ProcessInstanceVO extends BaseEntity {
  id: string;
  processDefinitionId: string;
  processDefinitionName: string;
  processDefinitionKey: string;
  processDefinitionVersion: string;
  deploymentId: string;
  businessKey: string;
  isSuspended?: any;
  tenantId: string;
  startTime: string;
  endTime?: string;
  startUserId: string;
  businessStatus: string;
  businessStatusName: string;
  taskVoList: TaskVO[];
}
src/bpmn/assets/defaultXML.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
function generateRandomValue() {
  // ç”Ÿæˆä¸€ä¸ªéšæœºæ•°
  const randomValue = Math.random().toString(36).slice(2, 12);
  return `Process_${randomValue}`;
}
const cartage: string = 'default';
export default `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef">
  <process id="process_${generateRandomValue()}" name="name_${generateRandomValue()}">
    <startEvent id="startNode1" name="开始" />
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_flow">
    <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c">
      <bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke="">
        <omgdc:Bounds x="240" y="200" width="30" height="30" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="242" y="237" width="23" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>`;
src/bpmn/assets/lang/zh.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,126 @@
export const NodeName = {
  'bpmn:Process': '流程',
  'bpmn:StartEvent': '开始事件',
  'bpmn:IntermediateThrowEvent': '中间事件',
  'bpmn:Task': '任务',
  'bpmn:SendTask': '发送任务',
  'bpmn:ReceiveTask': '接收任务',
  'bpmn:UserTask': '用户任务',
  'bpmn:ManualTask': '手工任务',
  'bpmn:BusinessRuleTask': '业务规则任务',
  'bpmn:ServiceTask': '服务任务',
  'bpmn:ScriptTask': '脚本任务',
  'bpmn:EndEvent': '结束事件',
  'bpmn:SequenceFlow': '流程线',
  'bpmn:ExclusiveGateway': '互斥网关',
  'bpmn:ParallelGateway': '并行网关',
  'bpmn:InclusiveGateway': '相容网关',
  'bpmn:ComplexGateway': '复杂网关',
  'bpmn:EventBasedGateway': '事件网关',
  'bpmn:Participant': 'æ± /参与者',
  'bpmn:SubProcess': '子流程',
  'bpmn:DataObjectReference': '数据对象引用',
  'bpmn:DataStoreReference': '数据存储引用',
  'bpmn:Group': '组'
};
export default {
  'Activate hand tool': '启动手动工具',
  'Activate lasso tool': '启动 Lasso å·¥å…·',
  'Activate create/remove space tool': '启动创建/删除空间工具',
  'Activate global connect tool': '启动全局连接工具',
  'Ad-hoc': 'Ad-hoc',
  'Add lane above': '在上方添加泳道',
  'Add lane below': '在下方添加泳道',
  'Business rule task': '规则任务',
  'Call activity': '引用流程',
  'Compensation end event': '结束补偿事件',
  'Compensation intermediate throw event': '中间补偿抛出事件',
  'Complex gateway': '复杂网关',
  'Conditional intermediate catch event': '中间条件捕获事件',
  'Conditional start event (non-interrupting)': '条件启动事件 (非中断)',
  'Conditional start event': '条件启动事件',
  'Connect using association': '文本关联',
  'Connect using sequence/message flow or association': '消息关联',
  'Change element': '更改元素',
  'Change type': '更改类型',
  'Create data object reference': '创建数据对象引用',
  'Create data store reference': '创建数据存储引用',
  'Create expanded sub-process': '创建可折叠子流程',
  'Create pool/participant': '创建池/参与者',
  'Collection': '集合',
  'Connect using data input association': '数据输入关联',
  'Data store reference': '数据存储引用',
  'Data object reference': '数据对象引用',
  'Divide into two lanes': '分成两个泳道',
  'Divide into three lanes': '分成三个泳道',
  'End event': '结束事件',
  'Error end event': '结束错误事件',
  'Escalation end event': '结束升级事件',
  'Escalation intermediate throw event': '中间升级抛出事件',
  'Event sub-process': '事件子流程',
  'Event-based gateway': '事件网关',
  'Exclusive gateway': '互斥网关',
  'Empty pool/participant (removes content)': '清空池/参与者 (删除内容)',
  'Empty pool/participant': '清空池/参与者',
  'Expanded pool/participant': '展开池/参与者',
  'Inclusive gateway': '相容网关',
  'Intermediate throw event': '中间抛出事件',
  'Loop': '循环',
  'Link intermediate catch event': '中间链接捕获事件',
  'Link intermediate throw event': '中间链接抛出事件',
  'Manual task': '手动任务',
  'Message end event': '结束消息事件',
  'Message intermediate catch event': '中间消息捕获事件',
  'Message intermediate throw event': '中间消息抛出事件',
  'Message start event': '消息启动事件',
  'Parallel gateway': '并行网关',
  'Parallel multi-instance': '并行多实例',
  'Participant multiplicity': '参与者多重性',
  'Receive task': '接受任务',
  'Remove': '移除',
  'Script task': '脚本任务',
  'Send task': '发送任务',
  'Sequential multi-instance': '串行多实例',
  'Service task': '服务任务',
  'Signal end event': '结束信号事件',
  'Signal intermediate catch event': '中间信号捕获事件',
  'Signal intermediate throw event': '中间信号抛出事件',
  'Signal start event (non-interrupting)': '信号启动事件 (非中断)',
  'Signal start event': '信号启动事件',
  'Start event': '开始事件',
  'Sub-process (collapsed)': '可折叠子流程',
  'Sub-process (expanded)': '可展开子流程',
  'Sub rocess': '子流程',
  'Task': '任务',
  'Transaction': '事务',
  'Terminate end event': '终止边界事件',
  'Timer intermediate catch event': '中间定时捕获事件',
  'Timer start event (non-interrupting)': '定时启动事件 (非中断)',
  'Timer start event': '定时启动事件',
  'User task': '用户任务',
  'Create start event': '创建开始事件',
  'Create gateway': '创建网关',
  'Create intermediate/boundary event': '创建中间/边界事件',
  'Create end event': '创建结束事件',
  'Create group': '创建组',
  'Create startEvent': '开始节点',
  'Create endEvent': '结束节点',
  'Create exclusiveGateway': '互斥网关',
  'Create parallelGateway': '并行网关',
  'Create task': '任务节点',
  'Create userTask': '用户任务节点',
  'Condition type': '条件类型',
  'Append end event': '追加结束事件节点',
  'Append gateway': '追加网关节点',
  'Append task': '追加任务',
  'Append user task': '追加用户任务节点',
  'Append text annotation': '追加文本注释',
  'Append intermediate/boundary event': '追加中间或边界事件',
  'Append receive task': '追加接收任务节点',
  'Append message intermediate catch event': '追加中间消息捕获事件',
  'Append timer intermediate catch event': '追加中间定时捕获事件',
  'Append conditional intermediate catch event': '追加中间条件捕获事件',
  'Append signal intermediate catch event': '追加中间信号捕获事件',
  'flow elements must be children of pools/participants': '流程元素必须是池/参与者的子元素'
};
src/bpmn/assets/moddle/flowable.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1250 @@
export default {
  'name': 'Flowable',
  'uri': 'http://flowable.org/bpmn',
  'prefix': 'flowable',
  'xml': {
    'tagAlias': 'lowerCase'
  },
  'associations': [],
  'types': [
    {
      'name': 'flowable:extCandidateUsers',
      'isAbstract': true,
      'extends': [],
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['*']
      },
      'properties': [
        {
          'name': 'body',
          'type': 'String',
          'isBody': true
        }
      ]
    },
    {
      'name': 'flowable:extAssignee',
      'isAbstract': true,
      'extends': [],
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['*']
      },
      'properties': [
        {
          'name': 'body',
          'type': 'String',
          'isBody': true
        }
      ]
    },
    {
      'name': 'flowable:property',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'id',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'name',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'value',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'flowable:properties',
      'isAbstract': true,
      'extends': [],
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['*']
      },
      'properties': [
        {
          'name': 'values',
          'type': 'flowable:property',
          'isMany': true
        }
      ]
    },
    {
      'name': 'InOutBinding',
      'superClass': ['Element'],
      'isAbstract': true,
      'properties': [
        {
          'name': 'source',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'sourceExpression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'target',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'businessKey',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'local',
          'isAttr': true,
          'type': 'Boolean',
          'default': false
        },
        {
          'name': 'variables',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'In',
      'superClass': ['InOutBinding'],
      'meta': {
        'allowedIn': ['bpmn:CallActivity']
      }
    },
    {
      'name': 'Out',
      'superClass': ['InOutBinding'],
      'meta': {
        'allowedIn': ['bpmn:CallActivity']
      }
    },
    {
      'name': 'AsyncCapable',
      'isAbstract': true,
      'extends': ['bpmn:Activity', 'bpmn:Gateway', 'bpmn:Event'],
      'properties': [
        {
          'name': 'async',
          'isAttr': true,
          'type': 'Boolean',
          'default': false
        },
        {
          'name': 'asyncBefore',
          'isAttr': true,
          'type': 'Boolean',
          'default': false
        },
        {
          'name': 'asyncAfter',
          'isAttr': true,
          'type': 'Boolean',
          'default': false
        },
        {
          'name': 'exclusive',
          'isAttr': true,
          'type': 'Boolean',
          'default': true
        }
      ]
    },
    {
      'name': 'flowable:in',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'source',
          'type': 'string',
          'isAttr': true
        },
        {
          'name': 'target',
          'type': 'string',
          'isAttr': true
        }
      ]
    },
    {
      'name': 'flowable:out',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'source',
          'type': 'string',
          'isAttr': true
        },
        {
          'name': 'target',
          'type': 'string',
          'isAttr': true
        }
      ]
    },
    {
      'name': 'BoundaryEvent',
      'superClass': ['CatchEvent'],
      'properties': [
        {
          'name': 'cancelActivity',
          'default': true,
          'isAttr': true,
          'type': 'Boolean'
        },
        {
          'name': 'attachedToRef',
          'type': 'Activity',
          'isAttr': true,
          'isReference': true
        }
      ]
    },
    {
      'name': 'JobPriorized',
      'isAbstract': true,
      'extends': ['bpmn:Process', 'flowable:AsyncCapable'],
      'properties': [
        {
          'name': 'jobPriority',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'SignalEventDefinition',
      'isAbstract': true,
      'extends': ['bpmn:SignalEventDefinition'],
      'properties': [
        {
          'name': 'async',
          'isAttr': true,
          'type': 'Boolean',
          'default': false
        }
      ]
    },
    {
      'name': 'ErrorEventDefinition',
      'isAbstract': true,
      'extends': ['bpmn:ErrorEventDefinition'],
      'properties': [
        {
          'name': 'errorCodeVariable',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'errorMessageVariable',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Error',
      'isAbstract': true,
      'extends': ['bpmn:Error'],
      'properties': [
        {
          'name': 'flowable:errorMessage',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'PotentialStarter',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'resourceAssignmentExpression',
          'type': 'bpmn:ResourceAssignmentExpression'
        }
      ]
    },
    {
      'name': 'UserTask',
      'isAbstract': true,
      'extends': ['bpmn:UserTask'],
      'properties': [
        {
          'name': 'timerEventDefinition',
          'type': 'Expression'
        },
        {
          'name': 'multiInstanceLoopCharacteristics',
          'type': 'MultiInstanceLoopCharacteristics'
        }
      ]
    },
    {
      'name': 'StartEvent',
      'isAbstract': true,
      'extends': ['bpmn:StartEvent'],
      'properties': [
        {
          'name': 'timerEventDefinition',
          'type': 'Expression'
        }
      ]
    },
    {
      'name': 'FormSupported',
      'isAbstract': true,
      'extends': ['bpmn:StartEvent', 'bpmn:UserTask'],
      'properties': [
        {
          'name': 'formHandlerClass',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'formKey',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'TemplateSupported',
      'isAbstract': true,
      'extends': ['bpmn:Process', 'bpmn:FlowElement'],
      'properties': [
        {
          'name': 'modelerTemplate',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Initiator',
      'isAbstract': true,
      'extends': ['bpmn:StartEvent'],
      'properties': [
        {
          'name': 'initiator',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'ScriptTask',
      'isAbstract': true,
      'extends': ['bpmn:ScriptTask'],
      'properties': [
        {
          'name': 'resultVariable',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'resource',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Process',
      'isAbstract': true,
      'extends': ['bpmn:Process'],
      'properties': [
        {
          'name': 'candidateStarterGroups',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'candidateStarterUsers',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'versionTag',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'historyTimeToLive',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'isStartableInTasklist',
          'isAttr': true,
          'type': 'Boolean',
          'default': true
        }
      ]
    },
    {
      'name': 'EscalationEventDefinition',
      'isAbstract': true,
      'extends': ['bpmn:EscalationEventDefinition'],
      'properties': [
        {
          'name': 'escalationCodeVariable',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'FormalExpression',
      'isAbstract': true,
      'extends': ['bpmn:FormalExpression'],
      'properties': [
        {
          'name': 'resource',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Assignable',
      'extends': ['bpmn:UserTask'],
      'properties': [
        {
          'name': 'candidateGroups',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'dueDate',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'followUpDate',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'priority',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'CallActivity',
      'extends': ['bpmn:CallActivity'],
      'properties': [
        {
          'name': 'calledElementBinding',
          'isAttr': true,
          'type': 'String',
          'default': 'latest'
        },
        {
          'name': 'calledElementVersion',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'calledElementVersionTag',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'calledElementTenantId',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'caseRef',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'caseBinding',
          'isAttr': true,
          'type': 'String',
          'default': 'latest'
        },
        {
          'name': 'caseVersion',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'caseTenantId',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'variableMappingClass',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'variableMappingDelegateExpression',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'ServiceTaskLike',
      'extends': ['bpmn:ServiceTask', 'bpmn:BusinessRuleTask', 'bpmn:SendTask', 'bpmn:MessageEventDefinition'],
      'properties': [
        {
          'name': 'expression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'class',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'delegateExpression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'resultVariable',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'ExclusiveGateway',
      'isAbstract': true,
      'extends': ['bpmn:ExclusiveGateway'],
      'properties': [
        {
          'name': 'serviceClass',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'DmnCapable',
      'extends': ['bpmn:BusinessRuleTask'],
      'properties': [
        {
          'name': 'decisionRef',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'decisionRefBinding',
          'isAttr': true,
          'type': 'String',
          'default': 'latest'
        },
        {
          'name': 'decisionRefVersion',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'mapDecisionResult',
          'isAttr': true,
          'type': 'String',
          'default': 'resultList'
        },
        {
          'name': 'decisionRefTenantId',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'ExternalCapable',
      'extends': ['flowable:ServiceTaskLike'],
      'properties': [
        {
          'name': 'type',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'topic',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'TaskPriorized',
      'extends': ['bpmn:Process', 'flowable:ExternalCapable'],
      'properties': [
        {
          'name': 'taskPriority',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Properties',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['*']
      },
      'properties': [
        {
          'name': 'values',
          'type': 'Property',
          'isMany': true
        }
      ]
    },
    {
      'name': 'Property',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'id',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'name',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'value',
          'type': 'String',
          'isAttr': true
        }
      ]
    },
    {
      'name': 'Connector',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['flowable:ServiceTaskLike']
      },
      'properties': [
        {
          'name': 'inputOutput',
          'type': 'InputOutput'
        },
        {
          'name': 'connectorId',
          'type': 'String'
        }
      ]
    },
    {
      'name': 'InputOutput',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['bpmn:FlowNode', 'flowable:Connector']
      },
      'properties': [
        {
          'name': 'inputOutput',
          'type': 'InputOutput'
        },
        {
          'name': 'connectorId',
          'type': 'String'
        },
        {
          'name': 'inputParameters',
          'isMany': true,
          'type': 'InputParameter'
        },
        {
          'name': 'outputParameters',
          'isMany': true,
          'type': 'OutputParameter'
        }
      ]
    },
    {
      'name': 'InputOutputParameter',
      'properties': [
        {
          'name': 'name',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'value',
          'isBody': true,
          'type': 'String'
        },
        {
          'name': 'definition',
          'type': 'InputOutputParameterDefinition'
        }
      ]
    },
    {
      'name': 'InputOutputParameterDefinition',
      'isAbstract': true
    },
    {
      'name': 'List',
      'superClass': ['InputOutputParameterDefinition'],
      'properties': [
        {
          'name': 'items',
          'isMany': true,
          'type': 'InputOutputParameterDefinition'
        }
      ]
    },
    {
      'name': 'Map',
      'superClass': ['InputOutputParameterDefinition'],
      'properties': [
        {
          'name': 'entries',
          'isMany': true,
          'type': 'Entry'
        }
      ]
    },
    {
      'name': 'Entry',
      'properties': [
        {
          'name': 'key',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'value',
          'isBody': true,
          'type': 'String'
        },
        {
          'name': 'definition',
          'type': 'InputOutputParameterDefinition'
        }
      ]
    },
    {
      'name': 'Value',
      'superClass': ['InputOutputParameterDefinition'],
      'properties': [
        {
          'name': 'id',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'name',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'value',
          'isBody': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Script',
      'superClass': ['InputOutputParameterDefinition'],
      'properties': [
        {
          'name': 'scriptFormat',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'resource',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'value',
          'isBody': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'Field',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['flowable:ServiceTaskLike', 'flowable:ExecutionListener', 'flowable:TaskListener']
      },
      'properties': [
        {
          'name': 'name',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'expression',
          'isAttr': true,
          'type': 'expression'
        },
        {
          'name': 'string',
          'type': 'string'
        },
        {
          'name': 'stringValue',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'string',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['flowable:Field']
      },
      'properties': [
        {
          'name': 'body',
          'isBody': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'expression',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['flowable:Field']
      },
      'properties': [
        {
          'name': 'body',
          'isBody': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'InputParameter',
      'superClass': ['InputOutputParameter']
    },
    {
      'name': 'OutputParameter',
      'superClass': ['InputOutputParameter']
    },
    {
      'name': 'Collectable',
      'isAbstract': true,
      'extends': ['bpmn:MultiInstanceLoopCharacteristics'],
      'superClass': ['flowable:AsyncCapable'],
      'properties': [
        {
          'name': 'collection',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'elementVariable',
          'isAttr': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'SequenceFlow',
      'superClass': ['FlowElement'],
      'properties': [
        {
          'name': 'isImmediate',
          'isAttr': true,
          'type': 'Boolean'
        },
        {
          'name': 'conditionExpression',
          'type': 'Expression'
        },
        {
          'name': 'sourceRef',
          'type': 'FlowNode',
          'isAttr': true,
          'isReference': true
        },
        {
          'name': 'targetRef',
          'type': 'FlowNode',
          'isAttr': true,
          'isReference': true
        }
      ]
    },
    {
      'name': 'MultiInstanceLoopCharacteristics',
      'superClass': ['LoopCharacteristics'],
      'properties': [
        {
          'name': 'isSequential',
          'default': false,
          'isAttr': true,
          'type': 'Boolean'
        },
        {
          'name': 'behavior',
          'type': 'MultiInstanceBehavior',
          'default': 'All',
          'isAttr': true
        },
        {
          'name': 'loopCardinality',
          'type': 'Expression',
          'xml': {
            'serialize': 'xsi:type'
          }
        },
        {
          'name': 'loopDataInputRef',
          'type': 'ItemAwareElement',
          'isReference': true
        },
        {
          'name': 'loopDataOutputRef',
          'type': 'ItemAwareElement',
          'isReference': true
        },
        {
          'name': 'inputDataItem',
          'type': 'DataInput',
          'xml': {
            'serialize': 'property'
          }
        },
        {
          'name': 'outputDataItem',
          'type': 'DataOutput',
          'xml': {
            'serialize': 'property'
          }
        },
        {
          'name': 'complexBehaviorDefinition',
          'type': 'ComplexBehaviorDefinition',
          'isMany': true
        },
        {
          'name': 'completionCondition',
          'type': 'Expression',
          'xml': {
            'serialize': 'xsi:type'
          }
        },
        {
          'name': 'oneBehaviorEventRef',
          'type': 'EventDefinition',
          'isAttr': true,
          'isReference': true
        },
        {
          'name': 'noneBehaviorEventRef',
          'type': 'EventDefinition',
          'isAttr': true,
          'isReference': true
        }
      ]
    },
    {
      'name': 'FailedJobRetryTimeCycle',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['flowable:AsyncCapable', 'bpmn:MultiInstanceLoopCharacteristics']
      },
      'properties': [
        {
          'name': 'body',
          'isBody': true,
          'type': 'String'
        }
      ]
    },
    {
      'name': 'ExecutionListener',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': [
          'bpmn:Task',
          'bpmn:ServiceTask',
          'bpmn:UserTask',
          'bpmn:BusinessRuleTask',
          'bpmn:ScriptTask',
          'bpmn:ReceiveTask',
          'bpmn:ManualTask',
          'bpmn:ExclusiveGateway',
          'bpmn:SequenceFlow',
          'bpmn:ParallelGateway',
          'bpmn:InclusiveGateway',
          'bpmn:EventBasedGateway',
          'bpmn:StartEvent',
          'bpmn:IntermediateCatchEvent',
          'bpmn:IntermediateThrowEvent',
          'bpmn:EndEvent',
          'bpmn:BoundaryEvent',
          'bpmn:CallActivity',
          'bpmn:SubProcess',
          'bpmn:Process'
        ]
      },
      'properties': [
        {
          'name': 'expression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'class',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'delegateExpression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'event',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'script',
          'type': 'Script'
        },
        {
          'name': 'fields',
          'type': 'Field',
          'isMany': true
        }
      ]
    },
    {
      'name': 'TaskListener',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['bpmn:UserTask']
      },
      'properties': [
        {
          'name': 'expression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'class',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'delegateExpression',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'event',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'script',
          'type': 'Script'
        },
        {
          'name': 'fields',
          'type': 'Field',
          'isMany': true
        }
      ]
    },
    {
      'name': 'FormProperty',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['bpmn:StartEvent', 'bpmn:UserTask']
      },
      'properties': [
        {
          'name': 'id',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'name',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'type',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'required',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'readable',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'writable',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'variable',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'expression',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'datePattern',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'default',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'values',
          'type': 'Value',
          'isMany': true
        }
      ]
    },
    {
      'name': 'FormData',
      'superClass': ['Element'],
      'meta': {
        'allowedIn': ['bpmn:StartEvent', 'bpmn:UserTask']
      },
      'properties': [
        {
          'name': 'fields',
          'type': 'FormField',
          'isMany': true
        },
        {
          'name': 'businessKey',
          'type': 'String',
          'isAttr': true
        }
      ]
    },
    {
      'name': 'FormField',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'id',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'label',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'type',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'datePattern',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'defaultValue',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'properties',
          'type': 'Properties'
        },
        {
          'name': 'validation',
          'type': 'Validation'
        },
        {
          'name': 'values',
          'type': 'Value',
          'isMany': true
        }
      ]
    },
    {
      'name': 'Validation',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'constraints',
          'type': 'Constraint',
          'isMany': true
        }
      ]
    },
    {
      'name': 'Constraint',
      'superClass': ['Element'],
      'properties': [
        {
          'name': 'name',
          'type': 'String',
          'isAttr': true
        },
        {
          'name': 'config',
          'type': 'String',
          'isAttr': true
        }
      ]
    },
    {
      'name': 'ConditionalEventDefinition',
      'isAbstract': true,
      'extends': ['bpmn:ConditionalEventDefinition'],
      'properties': [
        {
          'name': 'variableName',
          'isAttr': true,
          'type': 'String'
        },
        {
          'name': 'variableEvent',
          'isAttr': true,
          'type': 'String'
        }
      ]
    }
  ],
  'emumerations': []
};
src/bpmn/assets/module/ContextPad/CustomContextPadProvider.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,138 @@
import ContextPadProvider from 'bpmn-js/lib/features/context-pad/ContextPadProvider';
import { Injector } from 'didi';
import EventBus from 'diagram-js/lib/core/EventBus';
import ContextPad from 'diagram-js/lib/features/context-pad/ContextPad';
import Modeling from 'bpmn-js/lib/features/modeling/Modeling.js';
import ElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory';
import Connect from 'diagram-js/lib/features/connect/Connect';
import Create from 'diagram-js/lib/features/create/Create';
import PopupMenu from 'diagram-js/lib/features/popup-menu/PopupMenu';
import Canvas from 'diagram-js/lib/core/Canvas';
import Rules from 'diagram-js/lib/features/rules/Rules';
import { Element, Shape } from 'diagram-js/lib/model/Types';
import BpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory';
import modeler from '@/store/modules/modeler';
// @Description: å¢žå¼ºå…ƒç´ è¿žçº¿äº‹ä»¶
class CustomContextPadProvider extends ContextPadProvider {
  private _contextPad: ContextPad;
  private _modeling: Modeling;
  private _elementFactory: ElementFactory;
  private _autoPlace: any;
  private _connect: Connect;
  private _create: Create;
  private _popupMenu: PopupMenu;
  private _canvas: Canvas;
  private _rules: Rules;
  constructor(
    config: any,
    injector: Injector,
    eventBus: EventBus,
    contextPad: ContextPad,
    modeling: Modeling,
    elementFactory: ElementFactory,
    connect: Connect,
    create: Create,
    popupMenu: PopupMenu,
    canvas: Canvas,
    rules: Rules,
    translate
  ) {
    // @ts-expect-error å¿½ç•¥å¼‚常
    super(config, injector, eventBus, contextPad, modeling, elementFactory, connect, create, popupMenu, canvas, rules, translate);
    this._contextPad = contextPad;
    this._modeling = modeling;
    this._elementFactory = elementFactory;
    this._connect = connect;
    this._create = create;
    this._popupMenu = popupMenu;
    this._canvas = canvas;
    this._rules = rules;
    this._autoPlace = injector.get('autoPlace', false);
  }
  getContextPadEntries(element: Element) {
    const actions: Record<string, any> = {};
    const appendUserTask = (event: Event, element: Shape) => {
      const shape = this._elementFactory.createShape({ type: 'bpmn:UserTask' });
      this._create.start(event, shape, {
        source: element
      });
    };
    const appendMultiInstanceUserTask = (event: Event, element: Shape) => {
      const store = modeler();
      const bpmnFactory = store.getModeler().get('bpmnFactory') as BpmnFactory;
      const businessObject = bpmnFactory.create('bpmn:UserTask', {
        // name: '多实例用户任务',
        isForCompensation: false
      });
      businessObject.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
      // åˆ›å»º Shape
      const shape = this._elementFactory.createShape({
        type: 'bpmn:UserTask',
        businessObject: businessObject
      });
      this._create.start(event, shape, { source: element });
    };
    const appendTask = this._autoPlace
      ? (event, element) => {
          const bpmnFactory: BpmnFactory | undefined = modeler().getModeler().get('bpmnFactory');
          const businessObject = bpmnFactory.create('bpmn:UserTask', {
            // name: '多实例用户任务',// å³é”®åˆ›å»ºæ˜¾ç¤º
            isForCompensation: false
          });
          // åˆ›å»ºå¤šå®žä¾‹å±žæ€§å¹¶åˆ†é…ç»™ç”¨æˆ·ä»»åŠ¡çš„ loopCharacteristics
          businessObject.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
          // åˆ›å»º Shape
          const shape = this._elementFactory.createShape({
            type: 'bpmn:UserTask',
            businessObject: businessObject
          });
          this._autoPlace.append(element, shape);
        }
      : appendMultiInstanceUserTask;
    const append = this._autoPlace
      ? (event: Event, element: Shape) => {
          const shape = this._elementFactory.createShape({ type: 'bpmn:UserTask' });
          this._autoPlace.append(element, shape);
        }
      : appendUserTask;
    // // æ·»åŠ åˆ›å»ºç”¨æˆ·ä»»åŠ¡æŒ‰é’®
    actions['append.append-user-task'] = {
      group: 'model',
      className: 'bpmn-icon-user-task',
      title: '用户任务',
      action: {
        dragstart: appendUserTask,
        click: append
      }
    };
    // æ·»åŠ åˆ›å»ºå¤šå®žä¾‹ç”¨æˆ·ä»»åŠ¡æŒ‰é’®
    actions['append.append-multi-instance-user-task'] = {
      group: 'model',
      className: 'bpmn-icon-user', // ä½ å¯ä»¥ä½¿ç”¨å¤šå®žä¾‹ç”¨æˆ·ä»»åŠ¡çš„å›¾æ ‡  bpmn-icon-user   bpmn-icon-user-task
      title: '多实例用户任务',
      action: {
        dragstart: appendMultiInstanceUserTask,
        click: appendTask
      }
    };
    return actions;
  }
}
export default CustomContextPadProvider;
src/bpmn/assets/module/Palette/CustomPaletteProvider.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
import { assign } from 'min-dash';
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
import ElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory';
import Create from 'diagram-js/lib/features/create/Create';
import SpaceTool from 'diagram-js/lib/features/space-tool/SpaceTool';
import LassoTool from 'diagram-js/lib/features/lasso-tool/LassoTool';
import HandTool from 'diagram-js/lib/features/hand-tool/HandTool';
import GlobalConnect from 'diagram-js/lib/features/global-connect/GlobalConnect';
import Palette from 'diagram-js/lib/features/palette/Palette';
import modeler from '@/store/modules/modeler';
import BpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory';
// @Description: å¢žå¼ºå·¦ä¾§é¢æ¿
class CustomPaletteProvider extends PaletteProvider {
  private readonly _palette: Palette;
  private readonly _create: Create;
  private readonly _elementFactory: ElementFactory;
  private readonly _spaceTool: SpaceTool;
  private readonly _lassoTool: LassoTool;
  private readonly _handTool: HandTool;
  private readonly _globalConnect: GlobalConnect;
  private readonly _translate: any;
  constructor(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) {
    super(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate);
    this._palette = palette;
    this._create = create;
    this._elementFactory = elementFactory;
    this._spaceTool = spaceTool;
    this._lassoTool = lassoTool;
    this._handTool = handTool;
    this._globalConnect = globalConnect;
    this._translate = translate;
  }
  getPaletteEntries() {
    const actions = {},
      create = this._create,
      elementFactory = this._elementFactory,
      translate = this._translate;
    function createAction(type: string, group: string, className: string, title: string, options?: object) {
      function createListener(event) {
        const shape = elementFactory.createShape(assign({ type: type }, options));
        if (options) {
          !shape.businessObject.di && (shape.businessObject.di = {});
          shape.businessObject.di.isExpanded = (options as { [key: string]: any }).isExpanded;
        }
        create.start(event, shape, null);
      }
      const shortType = type.replace(/^bpmn:/, '');
      return {
        group: group,
        className: className,
        title: title || translate('Create {type}', { type: shortType }),
        action: {
          dragstart: createListener,
          click: createListener
        }
      };
    }
    function createMultiInstanceUserTask(event) {
      const bpmnFactory: BpmnFactory | undefined = modeler().getBpmnFactory();
      // åˆ›å»ºä¸€ä¸ª bpmn:UserTask
      const userTask = bpmnFactory.create('bpmn:UserTask', {
        // name: '多实例用户任务', // åœ¨ç”»æ¿ä¸­æ˜¾ç¤ºå­—段
        isForCompensation: false
      });
      // å°†å¤šå®žä¾‹å±žæ€§åˆ†é…ç»™ bpmn:UserTask çš„ loopCharacteristics
      userTask.loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
      const customUserTask = elementFactory.createShape({
        type: 'bpmn:UserTask',
        businessObject: userTask // åˆ†é…åˆ›å»ºçš„ userTask åˆ° businessObject
      });
      create.start(event, customUserTask, {});
    }
    assign(actions, {
      'create.parallel-gateway': createAction('bpmn:ParallelGateway', 'gateway', 'bpmn-icon-gateway-parallel', '并行网关'),
      'create.event-base-gateway': createAction('bpmn:EventBasedGateway', 'gateway', 'bpmn-icon-gateway-eventbased', '事件网关'),
      // åˆ†ç»„线
      'gateway-separator': {
        group: 'gateway',
        separator: true
      },
      'create.user-task': createAction('bpmn:UserTask', 'activity', 'bpmn-icon-user-task', '创建用户任务'),
      'create.multi-instance-user-task': {
        group: 'activity',
        type: 'bpmn:UserTask',
        className: 'bpmn-icon-user task-multi-instance',
        title: '创建多实例用户任务',
        action: {
          click: createMultiInstanceUserTask,
          dragstart: createMultiInstanceUserTask
        }
      },
      'task-separator': {
        group: 'activity',
        separator: true
      }
    });
    return actions;
  }
}
CustomPaletteProvider['$inject'] = ['palette', 'create', 'elementFactory', 'spaceTool', 'lassoTool', 'handTool', 'globalConnect', 'translate'];
export default CustomPaletteProvider;
src/bpmn/assets/module/Renderer/CustomRenderer.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import {
  append as svgAppend,
  attr as svgAttr,
  create as svgCreate,
  select as svgSelect,
  selectAll as svgSelectAll,
  clone as svgClone,
  clear as svgClear,
  remove as svgRemove
} from 'tiny-svg';
const HIGH_PRIORITY = 1500;
export default class CustomRenderer extends BaseRenderer {
  bpmnRenderer: BaseRenderer;
  modeling: any;
  constructor(eventBus, bpmnRenderer, modeling) {
    super(eventBus, HIGH_PRIORITY);
    this.bpmnRenderer = bpmnRenderer;
    this.modeling = modeling;
  }
  canRender(element) {
    // ignore labels
    return !element.labelTarget;
  }
  /**
   * è‡ªå®šä¹‰èŠ‚ç‚¹å›¾å½¢
   * @param {*} parentNode å½“前元素的svgNode
   * @param {*} element
   * @returns
   */
  drawShape(parentNode, element) {
    const shape = this.bpmnRenderer.drawShape(parentNode, element);
    const { type, width, height } = element;
    // å¼€å§‹ å¡«å……绿色
    if (type === 'bpmn:StartEvent') {
      svgAttr(shape, { fill: '#77DF6D' });
      return shape;
    }
    if (type === 'bpmn:EndEvent') {
      svgAttr(shape, { fill: '#EE7B77' });
      return shape;
    }
    if (type === 'bpmn:UserTask') {
      svgAttr(shape, { fill: '#A9C4F8' });
      return shape;
    }
    return shape;
  }
  getShapePath(shape) {
    return this.bpmnRenderer.getShapePath(shape);
  }
}
CustomRenderer['$inject'] = ['eventBus', 'bpmnRenderer'];
src/bpmn/assets/module/Translate/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
import zh from '../../lang/zh';
const customTranslate = (template: any, replacements: any) => {
  replacements = replacements || {};
  template = zh[template] || template;
  return template.replace(/{([^}]+)}/g, function (_: any, key: any) {
    return replacements[key] || '{' + key + '}';
  });
};
export const translateModule = {
  translate: ['value', customTranslate]
};
export default translateModule;
src/bpmn/assets/module/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
// ç¿»è¯‘模块
import TranslationModule from './Translate';
import { ModuleDeclaration } from 'didi';
import CustomPaletteProvider from './Palette/CustomPaletteProvider';
import CustomRenderer from './Renderer/CustomRenderer';
import CustomContextPadProvider from './ContextPad/CustomContextPadProvider';
const Module: ModuleDeclaration[] = [
  {
    __init__: ['customPaletteProvider', 'customContextPadProvider', 'customRenderer'],
    customPaletteProvider: ['type', CustomPaletteProvider],
    customRenderer: ['type', CustomRenderer],
    customContextPadProvider: ['type', CustomContextPadProvider]
  },
  TranslationModule
];
export default Module;
src/bpmn/assets/showConfig.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
export default {
  'bpmn:EndEvent': {},
  'bpmn:StartEvent': {
    initiator: true,
    formKey: true
  },
  'bpmn:UserTask': {
    allocationType: true,
    specifyDesc: true,
    multipleUserAuditType: true,
    async: true,
    priority: true,
    skipExpression: true,
    dueDate: true,
    taskListener: true,
    executionListener: true
  },
  'bpmn:ServiceTask': {
    async: true,
    skipExpression: true,
    isForCompensation: true,
    triggerable: true,
    class: true
  },
  'bpmn:ScriptTask': {
    async: true,
    isForCompensation: true,
    autoStoreVariables: true
  },
  'bpmn:ManualTask': {
    async: true,
    isForCompensation: true
  },
  'bpmn:ReceiveTask': {
    async: true,
    isForCompensation: true
  },
  'bpmn:SendTask': {
    async: true,
    isForCompensation: true
  },
  'bpmn:BusinessRuleTask': {
    async: true,
    isForCompensation: true,
    ruleVariablesInput: true,
    rules: true,
    resultVariable: true,
    exclude: true
  }
};
src/bpmn/assets/style/index.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,284 @@
.djs-palette {
  width: 300px;
  .bpmn-icon-hand-tool:hover {
    &:after {
      content: '启动手动工具';
      position: absolute;
      left: 45px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-lasso-tool:hover {
    &:after {
      content: '启动套索工具';
      position: absolute;
      left: 100px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-space-tool:hover {
    &:after {
      content: '启动创建/删除空间工具';
      position: absolute;
      left: 45px;
      width: 170px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-connection-multi:hover {
    &:after {
      content: '启动全局连接工具';
      position: absolute;
      left: 100px;
      width: 140px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-start-event-none:hover {
    &:after {
      content: '创建开始事件';
      position: absolute;
      left: 45px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-intermediate-event-none:hover {
    &:after {
      content: '创建中间/边界事件';
      position: absolute;
      left: 100px;
      width: 140px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-end-event-none:hover {
    &:after {
      content: '创建结束事件';
      position: absolute;
      left: 45px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-gateway-none:hover {
    &:after {
      content: '创建网关';
      position: absolute;
      left: 100px;
      width: 90px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-gateway-parallel:hover {
    &:after {
      content: '创建并行网关';
      position: absolute;
      left: 45px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-gateway-eventbased:hover {
    &:after {
      content: '创建事件网关';
      position: absolute;
      left: 100px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-task:hover {
    &:after {
      content: '创建任务';
      position: absolute;
      left: 45px;
      width: 80px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-subprocess-expanded:hover {
    &:after {
      content: '创建可折叠子流程';
      position: absolute;
      left: 100px;
      width: 140px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-user-task:hover {
    &:after {
      content: '创建用户任务';
      position: absolute;
      left: 45px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .task-multi-instance:hover {
    &:after {
      content: '创建多实例用户任务';
      position: absolute;
      left: 100px;
      width: 160px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-participant:hover {
    &:after {
      content: '创建泳池/泳道';
      position: absolute;
      left: 45px;
      width: 120px;
      font-size: 15px;
      font-weight: bold;
      color: #3a84de;
      border-radius: 2px;
      border: 1px solid #cccccc;
      background-color: #fafafa;
      opacity: 0.8;
    }
  }
  .bpmn-icon-data-object {
    display: none;
    &:hover {
      &:after {
        content: '创建数据对象';
        position: absolute;
        left: 45px;
        width: 120px;
        font-size: 15px;
        font-weight: bold;
        color: #3a84de;
        border-radius: 2px;
        border: 1px solid #cccccc;
        background-color: #fafafa;
        opacity: 0.8;
      }
    }
  }
  .bpmn-icon-data-store {
    display: none;
    &:hover {
      &:after {
        content: '创建数据存储';
        position: absolute;
        left: 100px;
        width: 120px;
        font-size: 15px;
        font-weight: bold;
        color: #3a84de;
        border-radius: 2px;
        border: 1px solid #cccccc;
        background-color: #fafafa;
        opacity: 0.8;
      }
    }
  }
  .bpmn-icon-group {
    display: none;
    &:hover {
      &:after {
        content: '创建分组';
        position: absolute;
        left: 100px;
        width: 100px;
        font-size: 15px;
        font-weight: bold;
        color: #3a84de;
        border-radius: 2px;
        border: 1px solid #cccccc;
        background-color: #fafafa;
        opacity: 0.8;
      }
    }
  }
}
src/bpmn/hooks/usePanel.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,145 @@
import showConfig from '../assets/showConfig';
import type { ModdleElement } from 'bpmn';
import useModelerStore from '@/store/modules/modeler';
import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
interface Options {
  element: ModdleElement;
}
export default (ops: Options) => {
  const { element } = ops;
  const { getModeling, getModdle } = useModelerStore();
  const modeling = getModeling();
  const moddle = getModdle();
  /**
   * å½“前节点类型
   */
  const elementType = computed(() => {
    const bizObj = element.businessObject;
    return bizObj.eventDefinitions ? bizObj.eventDefinitions[0].$type : bizObj.$type;
  });
  /**
   * ç”¨äºŽæŽ§åˆ¶é¢æ¿å­—段显示与隐藏的配置
   */
  const config = computed(() => showConfig[elementType.value] || {});
  /**
   * åˆ›å»ºä¸€ä¸ªèŠ‚ç‚¹
   * @param elementType èŠ‚ç‚¹ç±»åž‹
   * @param properties å±žæ€§
   * @param parent çˆ¶èŠ‚ç‚¹
   */
  const createModdleElement = (elementType: string, properties: any, parent: ModdleElement) => {
    const element = moddle.create(elementType, properties);
    parent && (element.$parent = parent);
    return element;
  };
  /**
   * èŽ·å–æ‰©å±•å±žæ€§ï¼Œå¦‚æžœä¸å­˜åœ¨ä¼šè‡ªåŠ¨åˆ›å»º
   */
  const getExtensionElements = (create = true) => {
    let extensionElements = element.businessObject.get<ModdleElement>('extensionElements');
    if (!extensionElements && create) {
      extensionElements = createModdleElement('bpmn:ExtensionElements', { values: [] }, element.businessObject);
      modeling.updateModdleProperties(element, element.businessObject, { extensionElements });
    }
    return extensionElements;
  };
  /**
   * èŽ·å–extensionElements下的properties
   * @param extensionElements å¯é€‰å‚数,默认获取当前Element下的extensionElements下的Properties
   */
  const getPropertiesElements = (extensionElements?: ModdleElement) => {
    if (!extensionElements) {
      extensionElements = getExtensionElements();
    }
    let propertiesElements = extensionElements.values.find((item) => item.$type === 'flowable:properties');
    if (!propertiesElements) {
      propertiesElements = createModdleElement('flowable:properties', { values: [] }, extensionElements);
      modeling.updateModdleProperties(element, extensionElements, {
        values: [...extensionElements.get<[]>('values'), propertiesElements]
      });
    }
    return propertiesElements;
  };
  /**
   * æ›´æ–°èŠ‚ç‚¹å±žæ€§
   * @param properties å±žæ€§å€¼
   */
  const updateProperties = (properties: any) => {
    modeling.updateProperties(element, properties);
  };
  /**
   * æ›´æ–°èŠ‚ç‚¹ä¿¡æ¯
   * @param updateElement éœ€è¦æ›´æ–°çš„节点
   * @param properties å±žæ€§
   */
  const updateModdleProperties = (updateElement, properties: any) => {
    modeling.updateModdleProperties(element, updateElement, properties);
  };
  /**
   * æ›´æ–°Property属性
   * @param name key值
   * @param value å€¼
   */
  const updateProperty = (name: string, value: string) => {
    const propertiesElements = getPropertiesElements();
    let propertyElements = propertiesElements.values.find((item) => item.name === name);
    if (!propertyElements) {
      propertyElements = createModdleElement('flowable:property', { name: name, value: value }, propertiesElements);
      modeling.updateModdleProperties(element, propertiesElements, {
        values: [...propertiesElements.get('values'), propertyElements]
      });
    } else {
      propertyElements.name = name;
      propertyElements.value = value;
    }
    return propertyElements;
  };
  const idChange = (newVal: string) => {
    if (newVal) {
      updateProperties({ id: newVal });
    }
  };
  const nameChange = (newVal: string) => {
    if (newVal) {
      updateProperties({ name: newVal });
    }
  };
  const formKeyChange = (newVal: string) => {
    updateProperties({ formKey: newVal });
  };
  const constant = {
    MultiInstanceType: [
      { id: '373d4b81-a0d1-4eb8-8685-0d2fb1b468e2', label: '无', value: MultiInstanceTypeEnum.NONE },
      { id: 'b5acea7c-b7e5-46b0-8778-390db091bdab', label: '串行', value: MultiInstanceTypeEnum.SERIAL },
      { id: 'b4f0c683-1ccc-43c4-8380-e1b998986caf', label: '并行', value: MultiInstanceTypeEnum.PARALLEL }
    ]
  };
  return {
    elementType,
    constant,
    showConfig: config,
    updateProperties,
    updateProperty,
    updateModdleProperties,
    createModdleElement,
    idChange,
    nameChange,
    formKeyChange,
    getExtensionElements,
    getPropertiesElements
  };
};
src/bpmn/hooks/useParseElement.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
import type { ModdleElement } from 'bpmn';
interface Options {
  element: ModdleElement;
}
interface Data {
  id: string;
}
export default (ops: Options) => {
  const { element } = ops;
  const parseData = <T>(): T => {
    const result = {
      ...element.businessObject,
      ...element.businessObject.$attrs
    };
    // ç§»é™¤flowable前缀,格式化数组
    for (const key in result) {
      if (key.indexOf('flowable:') === 0) {
        const newKey = key.replace('flowable:', '');
        result[newKey] = result[key];
        delete result[key];
      }
    }
    return { ...result } as T;
  };
  return {
    parseData
  };
};
src/bpmn/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,496 @@
<template>
  <div class="containers-bpmn">
    <!-- dark模式下 è¿žæŽ¥çº¿çš„箭头样式 -->
    <svg width="0" height="0" style="position: absolute">
      <defs>
        <marker id="markerArrow-dark-mode" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
          <path d="M 1 5 L 11 10 L 1 15 Z" class="arrow-dark" />
        </marker>
      </defs>
    </svg>
    <div v-loading="loading" class="app-containers-bpmn">
      <el-container class="h-full">
        <el-container style="align-items: stretch">
          <el-header>
            <div class="process-toolbar">
              <el-space wrap :size="10">
                <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
                  <el-button size="small" icon="Rank" @click="fitViewport" />
                </el-tooltip>
                <el-tooltip effect="dark" content="放大" placement="bottom">
                  <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
                </el-tooltip>
                <el-tooltip effect="dark" content="缩小" placement="bottom">
                  <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
                </el-tooltip>
                <el-tooltip effect="dark" content="后退" placement="bottom">
                  <el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" />
                </el-tooltip>
                <el-tooltip effect="dark" content="前进" placement="bottom">
                  <el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" />
                </el-tooltip>
              </el-space>
              <el-space wrap :size="10" style="float: right; padding-right: 10px">
                <el-button size="small" type="primary" @click="saveXml">保 å­˜</el-button>
                <el-dropdown size="small">
                  <el-button size="small" type="primary"> é¢„ è§ˆ </el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item icon="Document" @click="previewXML">XML预览</el-dropdown-item>
                      <el-dropdown-item icon="View" @click="previewSVG"> SVG预览</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
                <el-dropdown size="small">
                  <el-button size="small" type="primary"> ä¸‹ è½½ </el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item icon="Download" @click="downloadXML">下载XML</el-dropdown-item>
                      <el-dropdown-item icon="Download" @click="downloadSVG"> ä¸‹è½½SVG</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </el-space>
            </div>
          </el-header>
          <div ref="canvas" class="canvas" />
        </el-container>
        <div :class="{ 'process-panel': true, 'hide': panelFlag }">
          <div class="process-panel-bar" @click="panelBarClick">
            <div class="open-bar">
              <el-link type="default" :underline="false">
                <svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon>
              </el-link>
            </div>
          </div>
          <transition enter-active-class="animate__animated animate__fadeIn">
            <div v-show="showPanel" v-if="bpmnModeler" class="panel-content">
              <PropertyPanel :modeler="bpmnModeler" />
            </div>
          </transition>
        </div>
      </el-container>
    </div>
  </div>
  <div>
    <el-dialog v-model="perviewXMLShow" title="XML预览" width="80%" append-to-body>
      <highlightjs :code="xmlStr" language="XML" />
    </el-dialog>
  </div>
  <div>
    <el-dialog v-model="perviewSVGShow" title="SVG预览" width="80%" append-to-body>
      <div style="text-align: center" v-html="svgData" />
    </el-dialog>
  </div>
</template>
<script lang="ts" setup name="BpmnDesign">
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import './assets/style/index.scss';
import type { Canvas, Modeler } from 'bpmn';
import PropertyPanel from './panel/index.vue';
import BpmnModeler from 'bpmn-js/lib/Modeler.js';
import defaultXML from './assets/defaultXML';
import flowableModdle from './assets/moddle/flowable';
import Modules from './assets/module/index';
import useModelerStore from '@/store/modules/modeler';
import useDialog from '@/hooks/useDialog';
const emit = defineEmits(['closeCallBack', 'saveCallBack']);
const { visible, title, openDialog, closeDialog } = useDialog({
  title: '编辑流程'
});
const modelerStore = useModelerStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const panelFlag = ref(false);
const showPanel = ref(true);
const canvas = ref<HTMLDivElement>();
const panel = ref<HTMLDivElement>();
const bpmnModeler = ref<Modeler>();
const zoom = ref(1);
const perviewXMLShow = ref(false);
const perviewSVGShow = ref(false);
const xmlStr = ref('');
const svgData = ref('');
const loading = ref(false);
const panelBarClick = () => {
  // å»¶è¿Ÿæ‰§è¡Œï¼Œå¦åˆ™ä¼šå¯¼è‡´é¢æ¿æ”¶èµ·æ—¶ï¼Œå±žæ€§é¢æ¿ä¸æ˜¾ç¤º
  panelFlag.value = !panelFlag.value;
  setTimeout(() => {
    showPanel.value = !panelFlag.value;
  }, 100);
};
/**
 * åˆå§‹åŒ–Canvas
 */
const initCanvas = () => {
  bpmnModeler.value = new BpmnModeler({
    container: canvas.value,
    // é”®ç›˜
    keyboard: {
      bindTo: window // æˆ–者window,注意与外部表单的键盘监听事件是否冲突
    },
    propertiesPanel: {
      parent: panel.value
    },
    additionalModules: Modules,
    moddleExtensions: {
      flowable: flowableModdle
    }
  });
};
/**
 * åˆå§‹åŒ–Model
 */
const initModel = () => {
  if (modelerStore.getModeler()) {
    modelerStore.getModeler().destroy();
    modelerStore.setModeler(undefined);
  }
  modelerStore.setModeler(bpmnModeler.value);
};
/**
 * æ–°å»º
 */
const newDiagram = async () => {
  await proxy?.$modal.confirm('是否确认新建');
  initDiagram();
};
/**
 * åˆå§‹åŒ–
 */
const initDiagram = (xml?: string) => {
  if (!xml) xml = defaultXML;
  bpmnModeler.value.importXML(xml);
};
/**
 * è‡ªé€‚应屏幕
 */
const fitViewport = () => {
  zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom('fit-viewport');
  const bbox = document.querySelector<SVGGElement>('.app-containers-bpmn .viewport').getBBox();
  const currentViewBox = bpmnModeler.value.get<Canvas>('canvas').viewbox();
  const elementMid = {
    x: bbox.x + bbox.width / 2 - 65,
    y: bbox.y + bbox.height / 2
  };
  bpmnModeler.value.get<Canvas>('canvas').viewbox({
    x: elementMid.x - currentViewBox.width / 2,
    y: elementMid.y - currentViewBox.height / 2,
    width: currentViewBox.width,
    height: currentViewBox.height
  });
  zoom.value = (bbox.width / currentViewBox.width) * 1.8;
};
/**
 * æ”¾å¤§æˆ–者缩小
 * @param zoomIn true æ”¾å¤§ | false ç¼©å°
 */
const zoomViewport = (zoomIn = true) => {
  zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom();
  zoom.value += zoomIn ? 0.1 : -0.1;
  bpmnModeler.value.get<Canvas>('canvas').zoom(zoom.value);
};
/**
 * ä¸‹è½½XML
 */
const downloadXML = async () => {
  try {
    const { xml } = await bpmnModeler.value.saveXML({ format: true });
    downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml');
  } catch (e) {
    proxy?.$modal.msgError(e);
  }
};
/**
 * ä¸‹è½½SVG
 */
const downloadSVG = async () => {
  try {
    const { svg } = await bpmnModeler.value.saveSVG();
    downloadFile(getProcessElement().name, svg, 'image/svg+xml');
  } catch (e) {
    proxy?.$modal.msgError(e);
  }
};
/**
 * XML预览
 */
const previewXML = async () => {
  try {
    const { xml } = await bpmnModeler.value.saveXML({ format: true });
    xmlStr.value = xml;
    perviewXMLShow.value = true;
  } catch (e) {
    proxy?.$modal.msgError(e);
  }
};
/**
 * SVG预览
 */
const previewSVG = async () => {
  try {
    const { svg } = await bpmnModeler.value.saveSVG();
    svgData.value = svg;
    perviewSVGShow.value = true;
  } catch (e) {
    proxy?.$modal.msgError(e);
  }
};
const curNodeInfo = reactive({
  curType: '', // ä»»åŠ¡ç±»åž‹ ç”¨æˆ·ä»»åŠ¡
  curNode: '',
  expValue: '' //多用户和部门角色实现
});
const downloadFile = (fileName: string, data: any, type: string) => {
  const a = document.createElement('a');
  const url = window.URL.createObjectURL(new Blob([data], { type: type }));
  a.href = url;
  a.download = fileName;
  a.click();
  window.URL.revokeObjectURL(url);
};
const getProcessElement = () => {
  const rootElements = bpmnModeler.value?.getDefinitions().rootElements;
  for (let i = 0; i < rootElements.length; i++) {
    if (rootElements[i].$type === 'bpmn:Process') return rootElements[i];
  }
};
const getProcess = () => {
  const element = getProcessElement();
  return {
    id: element.id,
    name: element.name
  };
};
const saveXml = async () => {
  const { xml } = await bpmnModeler.value.saveXML({ format: true });
  const { svg } = await bpmnModeler.value.saveSVG();
  const process = getProcess();
  let data = {
    xml: xml,
    svg: svg,
    key: process.id,
    name: process.name,
    loading: loading
  };
  emit('saveCallBack', data);
};
const open = (xml?: string) => {
  openDialog();
  nextTick(() => {
    initDiagram(xml);
  });
};
const close = () => {
  closeDialog();
};
onMounted(() => {
  nextTick(() => {
    initCanvas();
    initModel();
  });
});
/**
 * å¯¹å¤–暴露子组件方法
 */
defineExpose({
  initDiagram,
  saveXml,
  open,
  close
});
</script>
<style lang="scss">
/** å¤œé—´æ¨¡å¼ çº¿æ¡çš„颜色 */
$stroke-color-dark: white;
$bpmn-font-size: 12px;
/** æ—¥é—´æ¨¡å¼ å­—体颜色 */
$bpmn-font-color-dark: white;
/** å¤œé—´æ¨¡å¼ å­—体颜色 */
$bpmn-font-color-light: #222;
/* èƒŒæ™¯ç½‘æ ¼ */
@mixin djs-container {
  background-image: linear-gradient(90deg, hsl(0deg 0% 78.4% / 15%) 10%, transparent 0), linear-gradient(hsl(0deg 0% 78.4% / 15%) 10%, transparent 0) !important;
  background-size: 10px 10px !important;
}
html[class='light'] {
  /** ä»Žå·¦ä¾§æ‹–动时的背景图 */
  svg.new-parent {
    @include djs-container;
  }
  /** åŒå‡»ç¼–辑元素时样式保持一致 */
  div.djs-direct-editing-parent {
    border-radius: 10px;
    background-color: transparent !important;
    color: $bpmn-font-color-light;
  }
  g.djs-visual {
    .djs-label {
      fill: $bpmn-font-color-light !important;
      font-size: $bpmn-font-size !important;
    }
  }
}
html[class='dark'] {
  /** dark模式下 è¿žæŽ¥çº¿çš„箭头样式 */
  .arrow-dark {
    stroke-width: 1px;
    stroke-linecap: round;
    stroke: $stroke-color-dark;
    fill: $stroke-color-dark;
    stroke-linejoin: round;
  }
  /** ä»Žå·¦ä¾§æ‹–动时的背景图 */
  svg.new-parent {
    background-color: black !important;
    @include djs-container;
  }
  /** åŒå‡»ç¼–辑元素时样式保持一致 */
  div.djs-direct-editing-parent {
    border-radius: 10px;
    background-color: transparent !important;
    color: $bpmn-font-color-dark;
  }
  /** å…ƒç´ ç›¸å…³è®¾ç½® */
  g.djs-visual {
    /** å…ƒç´ è¾¹æ¡† éœ€è¦åŽ»é™¤æ–‡å­—(.djs-label) */
    & > *:first-child:not(.djs-label) {
      stroke: $stroke-color-dark !important;
    }
    /** å­—体颜色 */
    .djs-label {
      fill: $bpmn-font-color-dark !important;
      font-size: $bpmn-font-size !important;
    }
    /* è¿žæŽ¥çº¿æ ·å¼ */
    path[data-corner-radius] {
      stroke: $stroke-color-dark !important;
      marker-end: url('#markerArrow-dark-mode') !important;
    }
  }
}
.containers-bpmn {
  height: 100%;
  .app-containers-bpmn {
    width: 100%;
    height: 100%;
    .canvas {
      width: 100%;
      height: 100%;
      @include djs-container;
    }
    .el-header {
      height: 35px;
      padding: 0;
    }
    .process-panel {
      transition: width 0.25s ease-in;
      .process-panel-bar {
        width: 34px;
        height: 40px;
        .open-bar {
          width: 34px;
          line-height: 40px;
        }
      }
      // æ”¶èµ·é¢æ¿æ ·å¼
      &.hide {
        width: 34px;
        overflow: hidden;
        padding: 0;
        .process-panel-bar {
          width: 34px;
          height: 100%;
          box-sizing: border-box;
          display: block;
          text-align: left;
          line-height: 34px;
        }
        .process-panel-bar:hover {
          background-color: var(--bpmn-panel-bar-background-color);
        }
      }
    }
  }
}
pre {
  margin: 0;
  height: 100%;
  max-height: calc(80vh - 32px);
  overflow-x: hidden;
  overflow-y: auto;
  .hljs {
    word-break: break-word;
    white-space: pre-wrap;
    padding: 0.5em;
  }
}
.open-bar {
  font-size: 20px;
  cursor: pointer;
  text-align: center;
}
.process-panel {
  box-sizing: border-box;
  padding: 0 8px 0 8px;
  border-left: 1px solid var(--bpmn-panel-border);
  box-shadow: var(--bpmn-panel-box-shadow) 0 0 8px;
  max-height: 100%;
  width: 25%;
  height: calc(100vh - 100px);
  .el-collapse {
    height: calc(100vh - 182px);
    overflow: auto;
  }
}
// ä»»åŠ¡æ  é€æ˜Žåº¦
//:deep(.djs-palette) {
//  opacity: 0.3;
//  transition: all 1s;
//}
//
//:deep(.djs-palette:hover) {
//  opacity: 1;
//  transition: all 1s;
//}
</style>
src/bpmn/panel/GatewayPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
<template>
  <div>
    <el-collapse v-model="currentCollapseItem">
      <el-collapse-item name="1">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <InfoFilled />
            </el-icon>
            å¸¸è§„
          </div>
        </template>
        <div>
          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
            <el-form-item prop="id" label="节点 ID">
              <el-input v-model="formData.id" @change="idChange"> </el-input>
            </el-form-item>
            <el-form-item prop="name" label="节点名称">
              <el-input v-model="formData.name" @change="nameChange"> </el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-collapse-item>
      <el-collapse-item name="2">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <BellFilled />
            </el-icon>
            æ‰§è¡Œç›‘听器
          </div>
        </template>
        <div>
          <ExecutionListener :element="element"></ExecutionListener>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script setup lang="ts">
import useParseElement from '../hooks/useParseElement';
import usePanel from '../hooks/usePanel';
import type { Modeler, ModdleElement } from 'bpmn';
import type { GatewayPanel } from 'bpmnDesign';
import ExecutionListener from './property/ExecutionListener.vue';
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { nameChange, idChange } = usePanel({
  element: toRaw(props.element)
});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const currentCollapseItem = ref(['1', '2']);
const formData = ref(parseData<GatewayPanel>());
const formRules = ref<ElFormRules>({
  processCategory: [{ required: true, message: '请选择', trigger: 'blur' }],
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
</script>
<style lang="scss" scoped></style>
src/bpmn/panel/ParticipantPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
<template>
  <div>
    <el-collapse v-model="currentCollapseItem">
      <el-collapse-item name="1">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <InfoFilled />
            </el-icon>
            å¸¸è§„
          </div>
        </template>
        <div>
          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px">
            <el-form-item prop="id" label="节点 ID">
              <el-input v-model="formData.id" @change="idChange"></el-input>
            </el-form-item>
            <el-form-item prop="name" label="节点名称">
              <el-input v-model="formData.name" @change="nameChange"></el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-collapse-item>
      <el-collapse-item name="2">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <BellFilled />
            </el-icon>
            æ‰§è¡Œç›‘听器
          </div>
        </template>
        <div>
          <ExecutionListener :element="element"></ExecutionListener>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script setup lang="ts">
import useParseElement from '../hooks/useParseElement';
import usePanel from '../hooks/usePanel';
import ExecutionListener from './property/ExecutionListener.vue';
import type { ModdleElement } from 'bpmn';
import type { ParticipantPanel } from 'bpmnDesign';
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { nameChange, idChange } = usePanel({
  element: toRaw(props.element)
});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const formData = ref(parseData<ParticipantPanel>());
const currentCollapseItem = ref(['1', '2']);
const formRules = ref<ElFormRules>({
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
</script>
<style lang="scss" scoped></style>
src/bpmn/panel/ProcessPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
<template>
  <div>
    <el-collapse v-model="currentCollapseItem">
      <el-collapse-item name="1">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <InfoFilled />
            </el-icon>
            å¸¸è§„
          </div>
        </template>
        <div>
          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
            <el-form-item label="流程标识" prop="id">
              <el-input v-model="formData.id" @change="idChange"></el-input>
            </el-form-item>
            <el-form-item label="流程名称" prop="name">
              <el-input v-model="formData.name" @change="nameChange"></el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-collapse-item>
      <el-collapse-item name="2">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <BellFilled />
            </el-icon>
            æ‰§è¡Œç›‘听器
          </div>
        </template>
        <div>
          <ExecutionListener :element="element"></ExecutionListener>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script setup lang="ts">
import ExecutionListener from './property/ExecutionListener.vue';
import useParseElement from '../hooks/useParseElement';
import usePanel from '../hooks/usePanel';
import type { Modeler, ModdleElement } from 'bpmn';
import type { ProcessPanel } from 'bpmnDesign';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const { idChange, nameChange } = usePanel({
  element: toRaw(props.element)
});
const currentCollapseItem = ref(['1', '2']);
const formData = ref<ProcessPanel>(parseData<ProcessPanel>());
const formRules = ref<ElFormRules>({
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
</script>
<style scoped lang="scss"></style>
src/bpmn/panel/SequenceFlowPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<template>
  <div>
    <el-collapse v-model="currentCollapseItem">
      <el-collapse-item name="1">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <InfoFilled />
            </el-icon>
            å¸¸è§„
          </div>
        </template>
        <div>
          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px">
            <el-form-item prop="id" label="节点 ID">
              <el-input v-model="formData.id" @change="idChange"> </el-input>
            </el-form-item>
            <el-form-item prop="name" label="节点名称">
              <el-input v-model="formData.name" @change="nameChange"> </el-input>
            </el-form-item>
            <el-form-item prop="conditionExpression" label="跳转条件">
              <el-input v-model="formData.conditionExpressionValue" @change="conditionExpressionChange"> </el-input>
            </el-form-item>
            <el-form-item prop="skipExpression" label="跳过表达式">
              <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-collapse-item>
      <el-collapse-item name="2">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <BellFilled />
            </el-icon>
            æ‰§è¡Œç›‘听器
          </div>
        </template>
        <div>
          <ExecutionListener :element="element"></ExecutionListener>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script setup lang="ts">
import useParseElement from '../hooks/useParseElement';
import useModelerStore from '@/store/modules/modeler';
import usePanel from '../hooks/usePanel';
import ExecutionListener from './property/ExecutionListener.vue';
import type { Modeler, ModdleElement } from 'bpmn';
import type { SequenceFlowPanel } from 'bpmnDesign';
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { nameChange, idChange, updateProperties } = usePanel({
  element: toRaw(props.element)
});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const moddle = useModelerStore().getModdle();
const currentCollapseItem = ref(['1', '2']);
const formData = ref(parseData<SequenceFlowPanel>());
const formRules = ref<ElFormRules>({
  processCategory: [{ required: true, message: '请选择', trigger: 'blur' }],
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
const conditionExpressionChange = (val: string) => {
  if (val) {
    const newCondition = moddle.create('bpmn:FormalExpression', { body: val });
    updateProperties({ conditionExpression: newCondition });
  } else {
    updateProperties({ conditionExpression: null });
  }
};
const skipExpressionChange = (val: string) => {
  updateProperties({ 'flowable:skipExpression': val });
};
onBeforeMount(() => {
  if (formData.value.conditionExpression) {
    formData.value.conditionExpressionValue = formData.value.conditionExpression.body;
  }
});
</script>
<style lang="scss" scoped></style>
src/bpmn/panel/StartEndPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
<template>
  <div>
    <el-collapse v-model="currentCollapseItem">
      <el-collapse-item name="1">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <InfoFilled />
            </el-icon>
            å¸¸è§„
          </div>
        </template>
        <div>
          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px">
            <el-form-item prop="id" label="节点 ID">
              <el-input v-model="formData.id" @change="idChange"> </el-input>
            </el-form-item>
            <el-form-item prop="name" label="节点名称">
              <el-input v-model="formData.name" @change="nameChange"> </el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-collapse-item>
      <el-collapse-item name="2">
        <template #title>
          <div class="collapse__title">
            <el-icon>
              <BellFilled />
            </el-icon>
            æ‰§è¡Œç›‘听器
          </div>
        </template>
        <div>
          <ExecutionListener :element="element"></ExecutionListener>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script setup lang="ts">
import ExecutionListener from './property/ExecutionListener.vue';
import useParseElement from '../hooks/useParseElement';
import usePanel from '../hooks/usePanel';
import type { Modeler, ModdleElement } from 'bpmn';
import type { StartEndPanel } from 'bpmnDesign';
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { nameChange, idChange } = usePanel({
  element: toRaw(props.element)
});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const formData = ref(parseData<StartEndPanel>());
const currentCollapseItem = ref(['1', '2']);
const formRules = ref<ElFormRules>({
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
</script>
<style lang="scss" scoped></style>
src/bpmn/panel/SubProcessPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
<template>
  <div>
    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px">
      <el-collapse v-model="currentCollapseItem">
        <el-collapse-item name="1">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <InfoFilled />
              </el-icon>
              å¸¸è§„
            </div>
          </template>
          <div>
            <el-form-item prop="id" label="节点 ID">
              <el-input v-model="formData.id" @change="idChange"> </el-input>
            </el-form-item>
            <el-form-item prop="name" label="节点名称">
              <el-input v-model="formData.name" @change="nameChange"> </el-input>
            </el-form-item>
          </div>
        </el-collapse-item>
        <el-collapse-item name="2">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <BellFilled />
              </el-icon>
              æ‰§è¡Œç›‘听器
            </div>
          </template>
          <div>
            <ExecutionListener :element="element"></ExecutionListener>
          </div>
        </el-collapse-item>
        <el-collapse-item name="3">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <HelpFilled />
              </el-icon>
              å¤šå®žä¾‹
            </div>
          </template>
          <div>
            <el-form-item label="多实例类型">
              <el-select v-model="formData.multiInstanceType" @change="multiInstanceTypeChange">
                <el-option v-for="item in constant.MultiInstanceType" :key="item.id" :value="item.value" :label="item.label"> </el-option>
              </el-select>
            </el-form-item>
            <div v-if="formData.multiInstanceType !== MultiInstanceTypeEnum.NONE">
              <el-form-item label="集合">
                <template #label>
                  <span>
                    é›†åˆ
                    <el-tooltip placement="top">
                      <el-icon><QuestionFilled /></el-icon>
                      <template #content>
                        å±žæ€§ä¼šä½œä¸ºè¡¨è¾¾å¼è¿›è¡Œè§£æžã€‚如果表达式解析为字符串而不是一个集合,<br />
                        ä¸è®ºæ˜¯å› ä¸ºæœ¬èº«é…ç½®çš„就是静态字符串值,还是表达式计算结果为字符串,<br />
                        è¿™ä¸ªå­—符串都会被当做变量名,并从流程变量中用于获取实际的集合。
                      </template>
                    </el-tooltip>
                  </span>
                </template>
                <el-input v-model="formData.collection" @change="collectionChange"></el-input>
              </el-form-item>
              <el-form-item label="元素变量">
                <template #label>
                  <span>
                    å…ƒç´ å˜é‡
                    <el-tooltip placement="top">
                      <el-icon><QuestionFilled /></el-icon>
                      <template #content>
                        æ¯åˆ›å»ºä¸€ä¸ªç”¨æˆ·ä»»åŠ¡å‰ï¼Œå…ˆä»¥è¯¥å…ƒç´ å˜é‡ä¸ºlabel,集合中的一项为value,<br />
                        åˆ›å»ºï¼ˆå±€éƒ¨ï¼‰æµç¨‹å˜é‡ï¼Œè¯¥å±€éƒ¨æµç¨‹å˜é‡è¢«ç”¨äºŽæŒ‡æ´¾ç”¨æˆ·ä»»åŠ¡ã€‚<br />
                        ä¸€èˆ¬æ¥è¯´ï¼Œè¯¥å­—符串应与指定人员变量相同。
                      </template>
                    </el-tooltip>
                  </span>
                </template>
                <el-input v-model="formData.elementVariable" @change="elementVariableChange"> </el-input>
              </el-form-item>
              <el-form-item label="完成条件">
                <template #label>
                  <span>
                    å®Œæˆæ¡ä»¶
                    <el-tooltip placement="top">
                      <el-icon><QuestionFilled /></el-icon>
                      <template #content>
                        å¤šå®žä¾‹æ´»åŠ¨åœ¨æ‰€æœ‰å®žä¾‹éƒ½å®Œæˆæ—¶ç»“æŸï¼Œç„¶è€Œä¹Ÿå¯ä»¥æŒ‡å®šä¸€ä¸ªè¡¨è¾¾å¼ï¼Œåœ¨æ¯ä¸ªå®žä¾‹<br />
                        ç»“束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例<br />
                        æ´»åŠ¨ï¼Œç»§ç»­æ‰§è¡Œæµç¨‹ã€‚ä¾‹å¦‚ ${nrOfCompletedInstances/nrOfInstances >= 0.6 },<br />
                        è¡¨ç¤ºå½“任务完成60%时,该节点就算完成
                      </template>
                    </el-tooltip>
                  </span>
                </template>
                <el-input v-model="formData.completionCondition" @change="completionConditionChange"> </el-input>
              </el-form-item>
            </div>
          </div>
        </el-collapse-item>
      </el-collapse>
    </el-form>
  </div>
</template>
<script setup lang="ts">
import ExecutionListener from './property/ExecutionListener.vue';
import useParseElement from '../hooks/useParseElement';
import usePanel from '../hooks/usePanel';
import type { ModdleElement } from 'bpmn';
import type { SubProcessPanel } from 'bpmnDesign';
import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { nameChange, idChange, updateProperties, createModdleElement, constant } = usePanel({
  element: toRaw(props.element)
});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const formData = ref(parseData<SubProcessPanel>());
const currentCollapseItem = ref(['1', '2', '3']);
const multiInstanceTypeChange = (newVal) => {
  if (newVal !== MultiInstanceTypeEnum.NONE) {
    let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
    if (!loopCharacteristics) {
      loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
    }
    loopCharacteristics.isSequential = newVal === MultiInstanceTypeEnum.SERIAL;
    updateProperties({ loopCharacteristics: loopCharacteristics });
  } else {
    updateProperties({ loopCharacteristics: undefined });
  }
};
const collectionChange = (newVal) => {
  let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
  if (!loopCharacteristics) {
    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
  }
  loopCharacteristics.collection = newVal && newVal.length > 0 ? newVal : undefined;
  updateProperties({ loopCharacteristics: loopCharacteristics });
};
const elementVariableChange = (newVal) => {
  let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
  if (!loopCharacteristics) {
    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
  }
  loopCharacteristics.elementVariable = newVal && newVal.length > 0 ? newVal : undefined;
  updateProperties({ loopCharacteristics: loopCharacteristics });
};
const completionConditionChange = (newVal) => {
  let loopCharacteristics = props.element.businessObject.get<ModdleElement>('loopCharacteristics');
  if (!loopCharacteristics) {
    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
  }
  if (newVal && newVal.length > 0) {
    if (!loopCharacteristics.completionCondition) {
      loopCharacteristics.completionCondition = createModdleElement('bpmn:Expression', { body: newVal }, loopCharacteristics);
    } else {
      loopCharacteristics.completionCondition.body = newVal;
    }
  } else {
    loopCharacteristics.completionCondition = undefined;
  }
  updateProperties({ loopCharacteristics: loopCharacteristics });
};
onBeforeMount(() => {
  if (formData.value.loopCharacteristics) {
    const loopCharacteristics = formData.value.loopCharacteristics;
    formData.value.collection = loopCharacteristics.collection || '';
    formData.value.elementVariable = loopCharacteristics.elementVariable || '';
    formData.value.completionCondition = loopCharacteristics.completionCondition?.body || '';
    formData.value.multiInstanceType = loopCharacteristics.isSequential ? MultiInstanceTypeEnum.SERIAL : MultiInstanceTypeEnum.PARALLEL;
  }
});
const formRules = ref<ElFormRules>({
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
</script>
<style lang="scss" scoped></style>
src/bpmn/panel/TaskPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,491 @@
<template>
  <div>
    <el-form ref="formRef" size="default" :model="formData" :rules="formRules" label-width="100px">
      <el-collapse v-model="currentCollapseItem">
        <el-collapse-item name="1">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <InfoFilled />
              </el-icon>
              å¸¸è§„
            </div>
          </template>
          <div>
            <el-form-item prop="id" label="节点 ID">
              <el-input v-model="formData.id" @change="idChange"> </el-input>
            </el-form-item>
            <el-form-item prop="name" label="节点名称">
              <el-input v-model="formData.name" @change="nameChange"> </el-input>
            </el-form-item>
            <el-form-item v-if="showConfig.skipExpression" prop="skipExpression" label="跳过表达式">
              <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input>
            </el-form-item>
            <el-form-item v-loading="formManageListLoading" prop="formKey" label="表单地址">
              <el-select v-model="formData.formKey" clearable filterable placeholder="请选择表单" style="width: 260px" @change="formKeyChange">
                <el-option
                  v-for="item in formManageList"
                  :key="item.id"
                  :label="item.formTypeName + ':' + item.formName"
                  :value="item.formType + ':' + item.id"
                />
              </el-select>
            </el-form-item>
          </div>
        </el-collapse-item>
        <el-collapse-item name="2">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <Checked />
              </el-icon>
              ä»»åŠ¡
            </div>
          </template>
          <div>
            <el-form-item v-if="showConfig.async" prop="sync" label="是否异步">
              <el-switch v-model="formData.async" inline-prompt active-text="是" inactive-text="否" @change="syncChange" />
            </el-form-item>
            <el-tabs tab-position="left" class="demo-tabs">
              <el-tab-pane label="身份存储">
                <el-form-item label="分配人员">
                  <el-input v-model="formData.assignee" @blur="blurAssignee(formData.assignee)">
                    <template #append>
                      <el-button icon="Search" type="primary" @click="openSingleUserSelect" />
                    </template>
                  </el-input>
                </el-form-item>
                <el-form-item label="候选人员">
                  <el-badge :value="selectUserLength" :max="99">
                    <el-button size="small" type="primary" @click="openUserSelect">选择人员</el-button>
                  </el-badge>
                </el-form-item>
                <el-form-item label="候选组">
                  <el-badge :value="selectRoleLength" :max="99">
                    <el-button size="small" type="primary" @click="openRoleSelect">选择组</el-button>
                  </el-badge>
                </el-form-item>
              </el-tab-pane>
              <!-- <el-tab-pane label="固定值">
                <el-form-item prop="auditUserType" label="分配类型">
                  <el-select v-model="formData.allocationType">
                    <el-option v-for="item in AllocationTypeSelect" :key="item.id" :value="item.value" :label="item.label"> </el-option>
                  </el-select>
                </el-form-item>
                <el-form-item v-if="formData.allocationType === AllocationTypeEnum.USER" label="分配人员">
                  <el-input v-model="formData.assignee">
                    <template #append>
                      <el-button icon="Search" type="primary" @click="openSingleUserSelect" />
                    </template>
                  </el-input>
                </el-form-item>
                <div v-if="formData.allocationType === AllocationTypeEnum.CANDIDATE">
                  <el-form-item label="候选人员">
                    <el-badge :value="selectUserLength" :max="99">
                      <el-button size="small" type="primary" @click="openUserSelect">选择人员</el-button>
                    </el-badge>
                  </el-form-item>
                  <el-form-item label="候选组">
                    <el-badge :value="selectRoleLength" :max="99">
                      <el-button size="small" type="primary" @click="openRoleSelect">选择组</el-button>
                    </el-badge>
                  </el-form-item>
                </div>
                <el-form-item v-if="formData.allocationType === AllocationTypeEnum.SPECIFY && showConfig.specifyDesc" style="">
                  <el-radio-group v-model="formData.specifyDesc" class="ml-4">
                    <el-radio v-for="item in SpecifyDesc" :key="item.id" :value="item.value" size="large">{{ item.label }}</el-radio>
                  </el-radio-group>
                </el-form-item>
              </el-tab-pane> -->
            </el-tabs>
            <el-form-item v-if="showConfig.dueDate" prop="dueDate" label="到期时间">
              <el-input v-model="formData.dueDate" clearable @change="dueDateChange" @click="openDueDate">
                <template #append>
                  <el-button icon="Search" type="primary" @click="openDueDate" />
                </template>
              </el-input>
            </el-form-item>
            <el-form-item v-if="showConfig.priority" prop="priority" label="优先级">
              <el-input-number v-model="formData.priority" :min="0" @change="priorityChange"> </el-input-number>
            </el-form-item>
          </div>
        </el-collapse-item>
        <el-collapse-item name="3">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <HelpFilled />
              </el-icon>
              å¤šå®žä¾‹
            </div>
          </template>
          <div>
            <el-form-item label="多实例类型">
              <el-select v-model="formData.multiInstanceType" @change="multiInstanceTypeChange">
                <el-option v-for="item in constant.MultiInstanceType" :key="item.id" :value="item.value" :label="item.label"> </el-option>
              </el-select>
            </el-form-item>
            <div v-if="formData.multiInstanceType !== MultiInstanceTypeEnum.NONE">
              <el-form-item label="集合">
                <template #label>
                  <span>
                    é›†åˆ
                    <el-tooltip placement="top">
                      <el-icon><QuestionFilled /></el-icon>
                      <template #content>
                        å±žæ€§ä¼šä½œä¸ºè¡¨è¾¾å¼è¿›è¡Œè§£æžã€‚如果表达式解析为字符串而不是一个集合,<br />
                        ä¸è®ºæ˜¯å› ä¸ºæœ¬èº«é…ç½®çš„就是静态字符串值,还是表达式计算结果为字符串,<br />
                        è¿™ä¸ªå­—符串都会被当做变量名,并从流程变量中用于获取实际的集合。
                      </template>
                    </el-tooltip>
                  </span>
                </template>
                <el-input v-model="formData.collection" @change="collectionChange"></el-input>
              </el-form-item>
              <el-form-item label="元素变量">
                <template #label>
                  <span>
                    å…ƒç´ å˜é‡
                    <el-tooltip placement="top">
                      <el-icon><QuestionFilled /></el-icon>
                      <template #content>
                        æ¯åˆ›å»ºä¸€ä¸ªç”¨æˆ·ä»»åŠ¡å‰ï¼Œå…ˆä»¥è¯¥å…ƒç´ å˜é‡ä¸ºlabel,集合中的一项为value,<br />
                        åˆ›å»ºï¼ˆå±€éƒ¨ï¼‰æµç¨‹å˜é‡ï¼Œè¯¥å±€éƒ¨æµç¨‹å˜é‡è¢«ç”¨äºŽæŒ‡æ´¾ç”¨æˆ·ä»»åŠ¡ã€‚<br />
                        ä¸€èˆ¬æ¥è¯´ï¼Œè¯¥å­—符串应与指定人员变量相同。
                      </template>
                    </el-tooltip>
                  </span>
                </template>
                <el-input v-model="formData.elementVariable" @change="elementVariableChange"> </el-input>
              </el-form-item>
              <el-form-item label="完成条件">
                <template #label>
                  <span>
                    å®Œæˆæ¡ä»¶
                    <el-tooltip placement="top">
                      <el-icon><QuestionFilled /></el-icon>
                      <template #content>
                        å¤šå®žä¾‹æ´»åŠ¨åœ¨æ‰€æœ‰å®žä¾‹éƒ½å®Œæˆæ—¶ç»“æŸï¼Œç„¶è€Œä¹Ÿå¯ä»¥æŒ‡å®šä¸€ä¸ªè¡¨è¾¾å¼ï¼Œåœ¨æ¯ä¸ªå®žä¾‹<br />
                        ç»“束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例<br />
                        æ´»åŠ¨ï¼Œç»§ç»­æ‰§è¡Œæµç¨‹ã€‚ä¾‹å¦‚ ${nrOfCompletedInstances/nrOfInstances >= 0.6 },<br />
                        è¡¨ç¤ºå½“任务完成60%时,该节点就算完成
                      </template>
                    </el-tooltip>
                  </span>
                </template>
                <el-input v-model="formData.completionCondition" @change="completionConditionChange"> </el-input>
              </el-form-item>
            </div>
          </div>
        </el-collapse-item>
        <el-collapse-item v-if="showConfig.taskListener" name="4">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <BellFilled />
              </el-icon>
              ä»»åŠ¡ç›‘å¬å™¨
            </div>
          </template>
          <div>
            <TaskListener v-if="showConfig.taskListener" :element="element"></TaskListener>
          </div>
        </el-collapse-item>
        <el-collapse-item v-if="showConfig.executionListener" name="5">
          <template #title>
            <div class="collapse__title">
              <el-icon>
                <BellFilled />
              </el-icon>
              æ‰§è¡Œç›‘听器
            </div>
          </template>
          <div>
            <ExecutionListener v-if="showConfig.executionListener" :element="element"></ExecutionListener>
          </div>
        </el-collapse-item>
        <el-form-item v-if="showConfig.isForCompensation" prop="isForCompensation" label="是否为补偿">
          <el-switch v-model="formData.isForCompensation" inline-prompt active-text="是" inactive-text="否" />
        </el-form-item>
        <el-form-item v-if="showConfig.triggerServiceTask" prop="triggerServiceTask" label="服务任务可触发">
          <el-switch v-model="formData.triggerServiceTask" inline-prompt active-text="是" inactive-text="否" />
        </el-form-item>
        <el-form-item v-if="showConfig.autoStoreVariables" prop="autoStoreVariables" label="自动存储变量">
          <el-switch v-model="formData.autoStoreVariables" inline-prompt active-text="是" inactive-text="否" />
        </el-form-item>
        <el-form-item v-if="showConfig.ruleVariablesInput" prop="skipExpression" label="输入变量">
          <el-input v-model="formData.ruleVariablesInput"> </el-input>
        </el-form-item>
        <el-form-item v-if="showConfig.exclude" prop="exclude" label="排除">
          <el-switch v-model="formData.exclude" inline-prompt active-text="是" inactive-text="否" />
        </el-form-item>
        <el-form-item v-if="showConfig.class" prop="class" label="ç±»">
          <el-input v-model="formData.class"> </el-input>
        </el-form-item>
      </el-collapse>
    </el-form>
    <UserSelect ref="userSelectRef" :data="formData.candidateUsers" @confirm-call-back="userSelectCallBack"></UserSelect>
    <UserSelect ref="singleUserSelectRef" :data="formData.assignee" :multiple="false" @confirm-call-back="singleUserSelectCallBack"></UserSelect>
    <RoleSelect ref="roleSelectRef" :data="formData.candidateGroups" @confirm-call-back="roleSelectCallBack"></RoleSelect>
    <DueDate ref="dueDateRef" v-model="formData.dueDate" :data="formData.dueDate" @confirm-call-back="dueDateCallBack"></DueDate>
  </div>
</template>
<script setup lang="ts">
import useParseElement from '../hooks/useParseElement';
import usePanel from '../hooks/usePanel';
import UserSelect from '@/components/UserSelect';
import RoleSelect from '@/components/RoleSelect';
import ExecutionListener from './property/ExecutionListener.vue';
import TaskListener from './property/TaskListener.vue';
import DueDate from './property/DueDate.vue';
import type { ModdleElement } from 'bpmn';
import type { TaskPanel } from 'bpmnDesign';
import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums';
import { UserVO } from '@/api/system/user/types';
import { RoleVO } from '@/api/system/role/types';
import { selectListFormManage } from '@/api/workflow/formManage';
import { FormManageVO } from '@/api/workflow/formManage/types';
const formManageList = ref<FormManageVO[]>([]);
const formManageListLoading = ref(false);
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { showConfig, nameChange, formKeyChange, idChange, updateProperties, getExtensionElements, createModdleElement, constant } = usePanel({
  element: toRaw(props.element)
});
const { parseData } = useParseElement({
  element: toRaw(props.element)
});
const initFormData = {
  id: '',
  name: '',
  dueDate: '',
  multiInstanceType: MultiInstanceTypeEnum.NONE,
  allocationType: AllocationTypeEnum.USER,
  specifyDesc: SpecifyDescEnum.SPECIFY_SINGLE
};
const formData = ref({ ...initFormData, ...parseData<TaskPanel>() });
const assignee = ref<Partial<UserVO>>({
  userName: ''
});
const currentCollapseItem = ref(['1', '2']);
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
const singleUserSelectRef = ref<InstanceType<typeof UserSelect>>();
const roleSelectRef = ref<InstanceType<typeof RoleSelect>>();
const dueDateRef = ref<InstanceType<typeof DueDate>>();
const openUserSelect = () => {
  userSelectRef.value.open();
};
const openSingleUserSelect = () => {
  if (formData.value.assignee?.includes('$')) {
    formData.value.assignee = '';
  }
  singleUserSelectRef.value.open();
};
const openRoleSelect = () => {
  roleSelectRef.value.open();
};
const openDueDate = (e) => {
  dueDateRef.value.openDialog();
};
const blurAssignee = (assignee) => {
  updateProperties({ 'flowable:assignee': assignee ? assignee : undefined });
};
const singleUserSelectCallBack = (data: UserVO[]) => {
  const user: UserVO = data.length !== 0 ? data[0] : undefined;
  updateProperties({ 'flowable:assignee': user?.userId });
  assignee.value = user ? user : { userName: '' };
  formData.value.assignee = String(user?.userId);
  let extensionElements = getExtensionElements();
  extensionElements.values = extensionElements.get('values').filter((item) => item.$type !== 'flowable:extAssignee');
  if (user) {
    const extAssigneeElement = createModdleElement('flowable:extAssignee', { body: '' }, extensionElements);
    extensionElements.get('values').push(extAssigneeElement);
    extAssigneeElement.body = JSON.stringify({ userName: user.userName, userId: user.userId });
  }
  if (extensionElements.values.length === 0) {
    extensionElements = undefined;
  }
  updateProperties({ extensionElements: extensionElements });
};
const userSelectCallBack = (data: UserVO[]) => {
  let extensionElements = getExtensionElements();
  extensionElements.values = extensionElements.values.filter((item) => item.$type !== 'flowable:extCandidateUsers');
  if (data.length === 0) {
    formData.value.candidateUsers = undefined;
    updateProperties({ 'flowable:candidateUsers': undefined });
  } else {
    const userIds = data.map((item) => item.userId).join(',');
    formData.value.candidateUsers = userIds;
    updateProperties({ 'flowable:candidateUsers': userIds });
    const extCandidateUsersElement = createModdleElement('flowable:extCandidateUsers', { body: '' }, extensionElements);
    extensionElements.values.push(extCandidateUsersElement);
    const users = data.map((item) => {
      return {
        userId: item.userId,
        userName: item.userName
      };
    });
    extCandidateUsersElement.body = JSON.stringify(users);
  }
  if (extensionElements.values.length === 0) {
    extensionElements = undefined;
  }
  updateProperties({ extensionElements: extensionElements });
};
const roleSelectCallBack = (data: RoleVO[]) => {
  if (data.length === 0) {
    formData.value.candidateGroups = '';
    updateProperties({ 'flowable:candidateGroups': undefined });
  } else {
    const roleIds = data.map((item) => item.roleId).join(',');
    formData.value.candidateGroups = roleIds;
    updateProperties({ 'flowable:candidateGroups': roleIds });
  }
};
const dueDateCallBack = (data: string) => {
  updateProperties({ 'flowable:dueDate': data });
};
const taskTabClick = (e) => {
  formData.value.candidateGroups = '';
  formData.value.candidateUsers = '';
  formData.value.assignee = '';
  // formData.value.fixedAssignee = '';
  assignee.value = {};
};
const syncChange = (newVal) => {
  updateProperties({ 'flowable:async': newVal });
};
const skipExpressionChange = (newVal) => {
  updateProperties({ 'flowable:skipExpression': newVal && newVal.length > 0 ? newVal : undefined });
};
const priorityChange = (newVal) => {
  updateProperties({ 'flowable:priority': newVal });
};
const fixedAssigneeChange = (newVal) => {
  updateProperties({ 'flowable:assignee': newVal && newVal.length > 0 ? newVal : undefined });
};
const multiInstanceTypeChange = (newVal) => {
  if (newVal !== MultiInstanceTypeEnum.NONE) {
    let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
    if (!loopCharacteristics) {
      loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
    }
    loopCharacteristics.isSequential = newVal === MultiInstanceTypeEnum.SERIAL;
    updateProperties({ loopCharacteristics: loopCharacteristics });
  } else {
    updateProperties({ loopCharacteristics: undefined });
  }
};
const collectionChange = (newVal) => {
  let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
  if (!loopCharacteristics) {
    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
  }
  loopCharacteristics.collection = newVal && newVal.length > 0 ? newVal : undefined;
  updateProperties({ loopCharacteristics: loopCharacteristics });
};
const elementVariableChange = (newVal) => {
  let loopCharacteristics = props.element.businessObject.get('loopCharacteristics');
  if (!loopCharacteristics) {
    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
  }
  loopCharacteristics.elementVariable = newVal && newVal.length > 0 ? newVal : undefined;
  updateProperties({ loopCharacteristics: loopCharacteristics });
};
const completionConditionChange = (newVal) => {
  let loopCharacteristics = props.element.businessObject.get<ModdleElement>('loopCharacteristics');
  if (!loopCharacteristics) {
    loopCharacteristics = createModdleElement('bpmn:MultiInstanceLoopCharacteristics', {}, props.element.businessObject);
  }
  if (newVal && newVal.length > 0) {
    if (!loopCharacteristics.completionCondition) {
      loopCharacteristics.completionCondition = createModdleElement('bpmn:Expression', { body: newVal }, loopCharacteristics);
    } else {
      loopCharacteristics.completionCondition.body = newVal;
    }
  } else {
    loopCharacteristics.completionCondition = undefined;
  }
  updateProperties({ loopCharacteristics: loopCharacteristics });
};
const dueDateChange = (newVal) => {
  updateProperties({ 'flowable:dueDate': newVal && newVal.length > 0 ? newVal : undefined });
};
const selectUserLength = computed(() => {
  if (formData.value.candidateUsers) {
    return formData.value.candidateUsers.split(',').length;
  } else {
    return 0;
  }
});
const selectRoleLength = computed(() => {
  if (formData.value.candidateGroups) {
    return formData.value.candidateGroups.split(',').length;
  } else {
    return 0;
  }
});
onBeforeMount(() => {
  const extensionElements = getExtensionElements(false);
  if (extensionElements && extensionElements.get('values')) {
    let extAssigneeElement = extensionElements.get('values').find((item) => item.$type === 'flowable:extAssignee');
    if (extAssigneeElement) {
      assignee.value = JSON.parse(extAssigneeElement.body);
    }
  }
  if (formData.value.loopCharacteristics) {
    const loopCharacteristics = formData.value.loopCharacteristics;
    formData.value.collection = loopCharacteristics.collection || '';
    formData.value.elementVariable = loopCharacteristics.elementVariable || '';
    formData.value.completionCondition = loopCharacteristics.completionCondition?.body || '';
    formData.value.multiInstanceType = loopCharacteristics.isSequential ? MultiInstanceTypeEnum.SERIAL : MultiInstanceTypeEnum.PARALLEL;
  }
  if (formData.value.assignee) {
    formData.value.fixedAssignee = formData.value.assignee;
  }
});
const formRules = ref<ElFormRules>({
  id: [{ required: true, message: '请输入', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }]
});
const AllocationTypeSelect = [
  { id: 'b9cdf970-dd91-47c0-819f-42a7010ca2a6', label: '指定人员', value: AllocationTypeEnum.USER },
  { id: '3f7ccbcd-c464-4602-bb9d-e96649d10585', label: '候选人员', value: AllocationTypeEnum.CANDIDATE },
  { id: 'c49065e0-7f2d-4c09-aedb-ab2d47d9a454', label: '发起人自己', value: AllocationTypeEnum.YOURSELF },
  { id: '6ef40a03-7e9a-4898-89b2-c88fe9064542', label: '发起人指定', value: AllocationTypeEnum.SPECIFY }
];
const SpecifyDesc = [
  { id: 'fa253b34-4335-458c-b1bc-b039e2a2b7a6', label: '指定一个人', value: 'specifySingle' },
  { id: '7365ff54-2e05-4312-9bfb-0b8edd779c5b', label: '指定多个人', value: 'specifyMultiple' }
];
const listFormManage = async () => {
  formManageListLoading.value = true;
  const res = await selectListFormManage();
  formManageList.value = res.data;
  formManageListLoading.value = false;
};
onMounted(() => {
  nextTick(() => {
    listFormManage();
  });
});
</script>
<style lang="scss" scoped></style>
src/bpmn/panel/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
<template>
  <div ref="propertyPanel">
    <div v-if="nodeName" class="node-name">{{ nodeName }}</div>
    <component :is="component" v-if="element" :element="element" />
  </div>
</template>
<script setup lang="ts" name="PropertyPanel">
import { NodeName } from '../assets/lang/zh';
import TaskPanel from './TaskPanel.vue';
import ProcessPanel from './ProcessPanel.vue';
import StartEndPanel from './StartEndPanel.vue';
import GatewayPanel from './GatewayPanel.vue';
import SequenceFlowPanel from './SequenceFlowPanel.vue';
import ParticipantPanel from './ParticipantPanel.vue';
import SubProcessPanel from './SubProcessPanel.vue';
import type { Modeler, ModdleElement } from 'bpmn';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
interface propsType {
  modeler: Modeler;
}
const props = withDefaults(defineProps<propsType>(), {});
const element = ref<ModdleElement>();
const processElement = ref<ModdleElement>();
const startEndType = ['bpmn:IntermediateThrowEvent', 'bpmn:StartEvent', 'bpmn:EndEvent'];
const taskType = [
  'bpmn:UserTask',
  'bpmn:Task',
  'bpmn:SendTask',
  'bpmn:ReceiveTask',
  'bpmn:ManualTask',
  'bpmn:BusinessRuleTask',
  'bpmn:ServiceTask',
  'bpmn:ScriptTask'
];
const sequenceType = ['bpmn:SequenceFlow'];
const gatewayType = ['bpmn:InclusiveGateway', 'bpmn:ExclusiveGateway', 'bpmn:ParallelGateway', 'bpmn:EventBasedGateway', 'bpmn:ComplexGateway'];
const processType = ['bpmn:Process'];
// ç»„件计算
const component = computed(() => {
  if (!element.value) return null;
  const type = element.value.type;
  if (startEndType.includes(type)) return StartEndPanel;
  if (taskType.includes(type)) return TaskPanel;
  if (sequenceType.includes(type)) return SequenceFlowPanel;
  if (gatewayType.includes(type)) return GatewayPanel;
  if (processType.includes(type)) return ProcessPanel;
  if (type === 'bpmn:Participant') return ParticipantPanel;
  if (type === 'bpmn:SubProcess') return SubProcessPanel;
  //return proxy?.$modal.msgWarning('面板开发中....');
  return undefined;
});
const nodeName = computed(() => {
  if (element.value) {
    const bizObj = element.value.businessObject;
    const type = bizObj?.eventDefinitions && bizObj?.eventDefinitions.length > 0 ? bizObj.eventDefinitions[0].$type : bizObj.$type;
    return NodeName[type] || type;
  }
  return '';
});
const handleModeler = () => {
  props.modeler.on('root.added', (e: any) => {
    element.value = null;
    if (e.element.type === 'bpmn:Process') {
      nextTick(() => {
        element.value = e.element;
        processElement.value = e.element;
      });
    }
  });
  props.modeler.on('element.click', (e: any) => {
    if (e.element.type === 'bpmn:Process') {
      nextTick(() => {
        element.value = e.element;
        processElement.value = e.element;
      });
    }
  });
  props.modeler.on('selection.changed', (e: any) => {
    // å…ˆç»™null为了让vue刷新
    element.value = null;
    const newElement = e.newSelection[0];
    if (newElement) {
      nextTick(() => {
        element.value = newElement;
      });
    } else {
      nextTick(() => {
        element.value = processElement.value;
      });
    }
  });
};
onMounted(() => {
  handleModeler();
});
</script>
<style scoped lang="scss">
.node-name {
  font-size: 16px;
  font-weight: bold;
  padding: 10px;
}
</style>
src/bpmn/panel/property/DueDate.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,252 @@
<template>
  <div>
    <el-dialog v-model="visible" :title="title" width="600px" append-to-body>
      <el-form label-width="100px">
        <el-form-item label="小时">
          <el-radio-group v-model="hourValue" @change="hourChange">
            <el-radio-button label="4" value="4" />
            <el-radio-button label="8" value="8" />
            <el-radio-button label="12" value="12" />
            <el-radio-button label="24" value="24" />
            <el-radio-button label="自定义" value="自定义" />
            <el-input-number v-show="hourValue === '自定义'" v-model="customHourValue" :min="1" @change="customHourValueChange"></el-input-number>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="天">
          <el-radio-group v-model="dayValue" @change="dayChange">
            <el-radio-button label="1" value="1" />
            <el-radio-button label="2" value="2" />
            <el-radio-button label="3" value="3" />
            <el-radio-button label="4" value="4" />
            <el-radio-button label="自定义" value="自定义" />
            <el-input-number v-show="dayValue === '自定义'" v-model="customDayValue" :min="1" @change="customDayValueChange"></el-input-number>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="周">
          <el-radio-group v-model="weekValue" @change="weekChange">
            <el-radio-button label="1" value="1" />
            <el-radio-button label="2" value="2" />
            <el-radio-button label="3" value="3" />
            <el-radio-button label="4" value="4" />
            <el-radio-button label="自定义" value="自定义" />
            <el-input-number v-show="weekValue === '自定义'" v-model="customWeekValue" :min="1" @change="customWeekValueChange"></el-input-number>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="月">
          <el-radio-group v-model="monthValue" @change="monthChange">
            <el-radio-button label="1" value="1" />
            <el-radio-button label="2" value="2" />
            <el-radio-button label="3" value="3" />
            <el-radio-button label="4" value="4" />
            <el-radio-button label="自定义" value="自定义" />
            <el-input-number v-show="monthValue === '自定义'" v-model="customMonthValue" :min="1" @change="customMonthValueChange"></el-input-number>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <div>
          <el-button @click="closeDialog">取消</el-button>
          <el-button type="primary" @click="confirm">确定</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import useDialog from '@/hooks/useDialog';
interface PropType {
  modelValue?: string;
  data?: string;
}
const prop = withDefaults(defineProps<PropType>(), {
  modelValue: '',
  data: ''
});
const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
const { title, visible, openDialog, closeDialog } = useDialog({
  title: '设置任务到期时间'
});
const formValue = ref();
const valueType = ref();
const hourValue = ref('');
const dayValue = ref('');
const weekValue = ref('');
const monthValue = ref('');
const customHourValue = ref(1);
const customDayValue = ref(1);
const customWeekValue = ref(1);
const customMonthValue = ref(1);
const hourValueConst = ['4', '8', '12', '24'];
const dayAndWeekAndMonthValueConst = ['1', '2', '3', '4'];
const initValue = () => {
  formValue.value = prop.data;
  if (prop.data) {
    const lastStr = prop.data.substring(prop.data.length - 1);
    if (lastStr === 'H') {
      const hourValueValue = prop.data.substring(2, prop.data.length - 1);
      if (hourValueConst.includes(hourValueValue)) {
        hourValue.value = hourValueValue;
      } else {
        hourValue.value = '自定义';
        customHourValue.value = Number(hourValueValue);
      }
    }
    const dayAndWeekAndMonthValue = prop.data.substring(1, prop.data.length - 1);
    if (lastStr === 'D') {
      if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) {
        dayValue.value = dayAndWeekAndMonthValue;
      } else {
        dayValue.value = '自定义';
        customDayValue.value = Number(dayAndWeekAndMonthValue);
      }
    }
    if (lastStr === 'W') {
      if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) {
        weekValue.value = dayAndWeekAndMonthValue;
      } else {
        weekValue.value = '自定义';
        customWeekValue.value = Number(dayAndWeekAndMonthValue);
      }
    }
    if (lastStr === 'M') {
      if (dayAndWeekAndMonthValueConst.includes(dayAndWeekAndMonthValue)) {
        monthValue.value = dayAndWeekAndMonthValue;
      } else {
        monthValue.value = '自定义';
        customMonthValue.value = Number(dayAndWeekAndMonthValue);
      }
    }
  }
};
const confirm = () => {
  emit('update:modelValue', formValue.value);
  emit('confirmCallBack', formValue.value);
  closeDialog();
};
const customHourValueChange = (customHourValue) => {
  formValue.value = `PT${customHourValue}H`;
  dayValue.value = '';
  weekValue.value = '';
  monthValue.value = '';
  customDayValue.value = 1;
  customWeekValue.value = 1;
  customMonthValue.value = 1;
};
const customDayValueChange = (customDayValue) => {
  formValue.value = `P${customDayValue}D`;
  hourValue.value = '';
  weekValue.value = '';
  monthValue.value = '';
  customHourValue.value = 1;
  customWeekValue.value = 1;
  customMonthValue.value = 1;
};
const customWeekValueChange = (customWeekValue) => {
  formValue.value = `P${customWeekValue}W`;
  hourValue.value = '';
  dayValue.value = '';
  monthValue.value = '';
  customHourValue.value = 1;
  customDayValue.value = 1;
  customMonthValue.value = 1;
};
const customMonthValueChange = (customMonthValue) => {
  formValue.value = `P${customMonthValue}M`;
  hourValue.value = '';
  dayValue.value = '';
  weekValue.value = '';
  customHourValue.value = 1;
  customDayValue.value = 1;
  customWeekValue.value = 1;
};
const hourChange = (hourValue) => {
  if (hourValue === '自定义') {
    formValue.value = `PT${customHourValue.value}H`;
  } else {
    formValue.value = `PT${hourValue}H`;
  }
  dayValue.value = '';
  weekValue.value = '';
  monthValue.value = '';
  customDayValue.value = 1;
  customWeekValue.value = 1;
  customMonthValue.value = 1;
};
const dayChange = (dayValue) => {
  if (dayValue === '自定义') {
    formValue.value = `P${customDayValue.value}D`;
  } else {
    formValue.value = `P${dayValue}D`;
  }
  hourValue.value = '';
  weekValue.value = '';
  monthValue.value = '';
  customHourValue.value = 1;
  customWeekValue.value = 1;
  customMonthValue.value = 1;
};
const weekChange = (weekValue) => {
  if (weekValue === '自定义') {
    formValue.value = `P${customWeekValue.value}W`;
  } else {
    formValue.value = `P${weekValue}W`;
  }
  hourValue.value = '';
  dayValue.value = '';
  monthValue.value = '';
  customHourValue.value = 1;
  customDayValue.value = 1;
  customMonthValue.value = 1;
};
const monthChange = (monthValue) => {
  if (monthValue === '自定义') {
    formValue.value = `P${customMonthValue.value}M`;
  } else {
    formValue.value = `P${monthValue}M`;
  }
  hourValue.value = '';
  dayValue.value = '';
  weekValue.value = '';
  customHourValue.value = 1;
  customDayValue.value = 1;
  customWeekValue.value = 1;
};
watch(
  () => visible.value,
  () => {
    if (visible.value) {
      initValue();
    }
  }
);
defineExpose({
  openDialog,
  closeDialog
});
</script>
src/bpmn/panel/property/ExecutionListener.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,308 @@
<template>
  <div>
    <vxe-toolbar>
      <template #buttons>
        <el-button type="primary" link size="small" @click="insertEvent">新增</el-button>
        <el-button type="primary" link size="small" @click="removeSelectRowEvent">删除</el-button>
      </template>
    </vxe-toolbar>
    <vxe-table
      ref="tableRef"
      size="mini"
      height="100px"
      border
      show-overflow
      keep-source
      :data="tableData"
      :menu-config="menuConfig"
      @cell-dblclick="cellDBLClickEvent"
      @menu-click="contextMenuClickEvent"
    >
      <vxe-column type="checkbox" width="40"></vxe-column>
      <vxe-column type="seq" width="40"></vxe-column>
      <vxe-column field="event" title="事件" min-width="100px">
        <template #default="slotParams">
          <span>{{ eventSelect.find((e) => e.value === slotParams.row.event)?.label }}</span>
        </template>
      </vxe-column>
      <vxe-column field="type" title="类型" min-width="100px">
        <template #default="slotParams">
          <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span>
        </template>
      </vxe-column>
      <vxe-column field="className" title="Java ç±»å" min-width="100px"> </vxe-column>
    </vxe-table>
    <el-dialog
      v-model="formDialog.visible.value"
      :title="formDialog.title.value"
      width="600px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
      append-to-body
    >
      <el-form ref="formRef" :model="formData" :rules="tableRules" label-width="100px">
        <el-form-item label="事件" prop="event">
          <el-select v-model="formData.event">
            <el-option v-for="item in eventSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="类型" prop="type">
          <template #label>
            <span>
              ç±»åž‹
              <el-tooltip placement="top">
                <el-icon><QuestionFilled /></el-icon>
                <template #content>
                  ç±»ï¼šç¤ºä¾‹ com.company.MyCustomListener,自定义类必须实现 org.flowable.engine.delegate.TaskListener æŽ¥å£<br />
                  è¡¨è¾¾å¼ï¼šç¤ºä¾‹ ${myObject.callMethod(task, task.eventName)}<br />
                  å§”托表达式:示例 ${myListenerSpringBean} ï¼Œè¯¥ springBean éœ€è¦å®žçް org.flowable.engine.delegate.TaskListener æŽ¥å£
                </template>
              </el-tooltip>
            </span>
          </template>
          <el-select v-model="formData.type">
            <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item
          :label="typeSelect.filter((e) => e.value === formData.type)[0] ? typeSelect.filter((e) => e.value === formData.type)[0]?.label : '表达式'"
          prop="className"
        >
          <el-input v-model="formData.className" type="text"></el-input>
        </el-form-item>
      </el-form>
      <el-tabs type="border-card">
        <el-tab-pane label="参数">
          <ListenerParam ref="listenerParamRef" :table-data="formData.params" />
        </el-tab-pane>
      </el-tabs>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="formDialog.closeDialog">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="submitEvent">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import ListenerParam from './ListenerParam.vue';
import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
import type { ExecutionListenerVO } from 'bpmnDesign';
import type { Moddle, Modeler, ModdleElement } from 'bpmn';
import usePanel from '../../hooks/usePanel';
import useDialog from '@/hooks/useDialog';
import useModelerStore from '@/store/modules/modeler';
const emit = defineEmits(['close']);
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const selectRow = ref<ExecutionListenerVO | null>();
const formDialog = useDialog({
  title: selectRow.value ? '编辑&保存' : '新增&保存'
});
const { showConfig, elementType, updateProperties } = usePanel({
  element: toRaw(props.element)
});
const { getModdle } = useModelerStore();
const moddle = getModdle();
const listenerParamRef = ref<InstanceType<typeof ListenerParam>>();
const tableRef = ref<VxeTableInstance<ExecutionListenerVO>>();
const formRef = ref<ElFormInstance>();
const initData: ExecutionListenerVO = {
  event: '',
  type: '',
  className: '',
  params: []
};
const formData = ref<ExecutionListenerVO>({ ...initData });
const tableData = ref<ExecutionListenerVO[]>([]);
const tableRules = ref<ElFormRules>({
  event: [{ required: true, message: '请选择', trigger: 'blur' }],
  type: [{ required: true, message: '请选择', trigger: 'blur' }],
  className: [{ required: true, message: '请输入', trigger: 'blur' }]
});
const submitEvent = async () => {
  const error = await listenerParamRef.value.validate();
  await formRef.value.validate((validate) => {
    if (validate && !error) {
      const $table = tableRef.value;
      if ($table) {
        formData.value.params = listenerParamRef.value.getTableData();
        if (selectRow.value) {
          Object.assign(selectRow.value, formData.value);
        } else {
          $table.insertAt({ ...formData.value }, -1);
        }
        updateElement();
        formDialog.closeDialog();
      }
    }
  });
};
const removeSelectRowEvent = async () => {
  const $table = tableRef.value;
  if ($table) {
    const selectCount = $table.getCheckboxRecords().length;
    if (selectCount === 0) {
      proxy?.$modal.msgWarning('请选择行');
    } else {
      await $table.removeCheckboxRow();
      updateElement();
    }
  }
};
const insertEvent = async () => {
  Object.assign(formData.value, initData);
  selectRow.value = null;
  formDialog.openDialog();
};
const editEvent = (row: ExecutionListenerVO) => {
  Object.assign(formData.value, row);
  selectRow.value = row;
  formDialog.openDialog();
};
const removeEvent = async (row: ExecutionListenerVO) => {
  await proxy?.$modal.confirm('您确定要删除该数据?');
  const $table = tableRef.value;
  if ($table) {
    await $table.remove(row);
    updateElement();
  }
};
const updateElement = () => {
  const $table = tableRef.value;
  const data = $table.getTableData().fullData;
  if (data.length) {
    let extensionElements = props.element.businessObject.get('extensionElements');
    if (!extensionElements) {
      extensionElements = moddle.create('bpmn:ExtensionElements');
    }
    // æ¸…除旧值
    extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:ExecutionListener') ?? [];
    data.forEach((item) => {
      const executionListener = moddle.create('flowable:ExecutionListener');
      executionListener['event'] = item.event;
      executionListener[item.type] = item.className;
      if (item.params && item.params.length) {
        item.params.forEach((field) => {
          const fieldElement = moddle.create('flowable:Field');
          fieldElement['name'] = field.name;
          fieldElement[field.type] = field.value;
          executionListener.get('fields').push(fieldElement);
        });
      }
      extensionElements.get('values').push(executionListener);
    });
    updateProperties({ extensionElements: extensionElements });
  } else {
    const extensionElements = props.element.businessObject[`extensionElements`];
    if (extensionElements) {
      extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:ExecutionListener') ?? [];
    }
  }
};
const cellDBLClickEvent: VxeTableEvents.CellDblclick<ExecutionListenerVO> = ({ row }) => {
  editEvent(row);
};
const menuConfig = reactive<VxeTablePropTypes.MenuConfig<ExecutionListenerVO>>({
  body: {
    options: [
      [
        { code: 'edit', name: '编辑', prefixIcon: 'vxe-icon-edit', disabled: false },
        { code: 'remove', name: '删除', prefixIcon: 'vxe-icon-delete', disabled: false }
      ]
    ]
  },
  visibleMethod({ options, column }) {
    const isDisabled = !column;
    options.forEach((list) => {
      list.forEach((item) => {
        item.disabled = isDisabled;
      });
    });
    return true;
  }
});
const contextMenuClickEvent: VxeTableEvents.MenuClick<ExecutionListenerVO> = ({ menu, row, column }) => {
  const $table = tableRef.value;
  if ($table) {
    switch (menu.code) {
      case 'edit':
        editEvent(row);
        break;
      case 'remove':
        removeEvent(row);
        break;
    }
  }
};
const initTableData = () => {
  tableData.value =
    props.element.businessObject.extensionElements?.values
      .filter((item) => item.$type === 'flowable:ExecutionListener')
      .map((item) => {
        let type;
        if ('class' in item) type = 'class';
        if ('expression' in item) type = 'expression';
        if ('delegateExpression' in item) type = 'delegateExpression';
        return {
          event: item.event,
          type: type,
          className: item[type],
          params:
            item.fields?.map((field) => {
              let fieldType;
              if ('stringValue' in field) fieldType = 'stringValue';
              if ('expression' in field) fieldType = 'expression';
              return {
                name: field.name,
                type: fieldType,
                value: field[fieldType]
              };
            }) ?? []
        };
      }) ?? [];
};
onMounted(() => {
  initTableData();
});
const typeSelect = [
  { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: 'ç±»', value: 'class' },
  { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '表达式', value: 'expression' },
  { id: '4b8135ab-6bc3-4a0f-80be-22f58bc6c5fd', label: '委托表达式', value: 'delegateExpression' }
];
const eventSelect = [
  { id: 'e6e0a51a-2d5d-4dc4-b847-b5c14f43a6ab', label: '开始', value: 'start' },
  { id: '6da97c1e-15fc-4445-8943-75d09f49778e', label: '结束', value: 'end' },
  { id: '6a2cbcec-e026-4f11-bef7-fff0b5c871e2', label: '启用', value: 'take' }
];
</script>
<style scoped lang="scss">
.el-badge {
  :deep(.el-badge__content) {
    top: 10px;
  }
}
</style>
src/bpmn/panel/property/ListenerParam.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
<template>
  <vxe-toolbar>
    <template #buttons>
      <el-button icon="Plus" @click="insertRow">新增</el-button>
    </template>
  </vxe-toolbar>
  <vxe-table
    ref="tableRef"
    :height="height"
    border
    show-overflow
    keep-source
    :data="tableData"
    :edit-rules="tableRules"
    :edit-config="{ trigger: 'click', mode: 'row', showStatus: true }"
  >
    <vxe-column type="seq" width="40"></vxe-column>
    <vxe-column field="type" title="类型" :edit-render="{}">
      <template #default="slotParams">
        <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span>
      </template>
      <template #edit="slotParams">
        <vxe-select v-model="slotParams.row.type">
          <vxe-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></vxe-option>
        </vxe-select>
      </template>
    </vxe-column>
    <vxe-column field="name" title="名称" :edit-render="{}">
      <template #edit="slotParams">
        <vxe-input v-model="slotParams.row.name" type="text"></vxe-input>
      </template>
    </vxe-column>
    <vxe-column field="value" title="值" :edit-render="{}">
      <template #edit="slotParams">
        <vxe-input v-model="slotParams.row.value" type="text"></vxe-input>
      </template>
    </vxe-column>
    <vxe-column title="操作" width="100" show-overflow align="center">
      <template #default="slotParams">
        <el-tooltip content="删除" placement="top">
          <el-button link type="danger" icon="Delete" @click="removeRow(slotParams.row)"></el-button>
        </el-tooltip>
      </template>
    </vxe-column>
  </vxe-table>
</template>
<script setup lang="ts">
import { VXETable, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
import type { ParamVO } from 'bpmnDesign';
import useDialog from '@/hooks/useDialog';
interface PropType {
  height?: string;
  tableData?: ParamVO[];
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = withDefaults(defineProps<PropType>(), {
  height: '200px',
  tableData: () => []
});
const tableRules = ref<VxeTablePropTypes.EditRules>({
  type: [{ required: true, message: '请选择', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }],
  value: [{ required: true, message: '请输入', trigger: 'blur' }]
});
const { title, visible, openDialog, closeDialog } = useDialog({
  title: '监听器参数'
});
const typeSelect = [
  { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: '字符串', value: 'stringValue' },
  { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '表达式', value: 'expression' }
];
const tableRef = ref<VxeTableInstance<ParamVO>>();
const getTableData = () => {
  const $table = tableRef.value;
  if ($table) {
    return $table.getTableData().fullData;
  }
  return [];
};
const insertRow = async () => {
  const $table = tableRef.value;
  if ($table) {
    const { row: newRow } = await $table.insertAt({}, -1);
    // æ’入一条数据并触发校验
    await $table.validate(newRow);
  }
};
const removeRow = async (row: ParamVO) => {
  await proxy?.$modal.confirm('您确定要删除该数据?');
  const $table = tableRef.value;
  if ($table) {
    await $table.remove(row);
  }
};
const validate = async () => {
  const $table = tableRef.value;
  if ($table) {
    return await $table.validate(true);
  }
};
defineExpose({
  closeDialog,
  openDialog,
  validate,
  getTableData
});
</script>
<style scoped lang="scss"></style>
src/bpmn/panel/property/TaskListener.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,310 @@
<template>
  <div>
    <vxe-toolbar>
      <template #buttons>
        <el-button type="primary" link size="small" @click="insertEvent">新增</el-button>
        <el-button type="primary" link size="small" @click="removeSelectRowEvent">删除</el-button>
      </template>
    </vxe-toolbar>
    <vxe-table
      ref="tableRef"
      size="mini"
      height="100px"
      border
      show-overflow
      keep-source
      :data="tableData"
      :menu-config="menuConfig"
      @cell-dblclick="cellDBLClickEvent"
      @menu-click="contextMenuClickEvent"
    >
      <vxe-column type="checkbox" width="40"></vxe-column>
      <vxe-column type="seq" width="40"></vxe-column>
      <vxe-column field="event" title="事件" min-width="100px">
        <template #default="slotParams">
          <span>{{ eventSelect.find((e) => e.value === slotParams.row.event)?.label }}</span>
        </template>
      </vxe-column>
      <vxe-column field="type" title="类型" min-width="100px">
        <template #default="slotParams">
          <span>{{ typeSelect.find((e) => e.value === slotParams.row.type)?.label }}</span>
        </template>
      </vxe-column>
      <vxe-column field="className" title="Java ç±»å" min-width="100px"> </vxe-column>
    </vxe-table>
    <el-dialog
      v-model="formDialog.visible.value"
      :title="formDialog.title.value"
      width="600px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
      append-to-body
    >
      <el-form ref="formRef" :model="formData" :rules="tableRules" label-width="100px">
        <el-form-item label="事件" prop="event">
          <template #label>
            <span>
              äº‹ä»¶
              <el-tooltip placement="top">
                <el-icon><QuestionFilled /></el-icon>
                <template #content>
                  create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。<br />
                  assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发assignment事件。<br />
                  complete(完成):当任务已经完成,从运行时数据中删除前触发。<br />
                  delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
                </template>
              </el-tooltip>
            </span>
          </template>
          <el-select v-model="formData.event">
            <el-option v-for="item in eventSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="类型" prop="type">
          <el-select v-model="formData.type">
            <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item
          :label="typeSelect.filter((e) => e.value === formData.type)[0] ? typeSelect.filter((e) => e.value === formData.type)[0]?.label : '表达式'"
          prop="className"
        >
          <el-input v-model="formData.className" type="text"></el-input>
        </el-form-item>
      </el-form>
      <el-tabs type="border-card">
        <el-tab-pane label="参数">
          <ListenerParam ref="listenerParamRef" :table-data="formData.params" />
        </el-tab-pane>
      </el-tabs>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="formDialog.closeDialog">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="submitEvent">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import ListenerParam from './ListenerParam.vue';
import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
import type { TaskListenerVO } from 'bpmnDesign';
import type { ModdleElement } from 'bpmn';
import usePanel from '../../hooks/usePanel';
import useDialog from '@/hooks/useDialog';
import useModelerStore from '@/store/modules/modeler';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
interface PropType {
  element: ModdleElement;
}
const props = withDefaults(defineProps<PropType>(), {});
const selectRow = ref<TaskListenerVO | null>();
const formDialog = useDialog({
  title: selectRow.value ? '编辑&保存' : '新增&保存'
});
const { showConfig, elementType, updateProperties } = usePanel({
  element: toRaw(props.element)
});
const { getModdle } = useModelerStore();
const moddle = getModdle();
const listenerParamRef = ref<InstanceType<typeof ListenerParam>>();
const tableRef = ref<VxeTableInstance<TaskListenerVO>>();
const formRef = ref<ElFormInstance>();
const initData: TaskListenerVO = {
  event: '',
  type: '',
  className: '',
  name: '',
  params: []
};
const formData = ref<TaskListenerVO>({ ...initData });
const currentIndex = ref(0);
const tableData = ref<TaskListenerVO[]>([]);
const tableRules = ref<VxeTablePropTypes.EditRules>({
  event: [{ required: true, message: '请选择', trigger: 'blur' }],
  type: [{ required: true, message: '请选择', trigger: 'blur' }],
  name: [{ required: true, message: '请输入', trigger: 'blur' }],
  className: [{ required: true, message: '请输入', trigger: 'blur' }]
});
const submitEvent = async () => {
  const error = await listenerParamRef.value.validate();
  await formRef.value.validate((validate) => {
    if (validate && !error) {
      const $table = tableRef.value;
      if ($table) {
        formData.value.params = listenerParamRef.value.getTableData();
        if (selectRow.value) {
          Object.assign(selectRow.value, formData.value);
        } else {
          $table.insertAt({ ...formData.value }, -1);
        }
        updateElement();
        formDialog.closeDialog();
      }
    }
  });
};
const insertEvent = async () => {
  Object.assign(formData.value, initData);
  selectRow.value = null;
  formDialog.openDialog();
};
const editEvent = (row: TaskListenerVO) => {
  Object.assign(formData.value, row);
  selectRow.value = row;
  formDialog.openDialog();
};
const removeEvent = async (row: TaskListenerVO) => {
  await proxy?.$modal.confirm('您确定要删除该数据?');
  const $table = tableRef.value;
  if ($table) {
    await $table.remove(row);
    updateElement();
  }
};
const removeSelectRowEvent = async () => {
  const $table = tableRef.value;
  if ($table) {
    const selectCount = $table.getCheckboxRecords().length;
    if (selectCount === 0) {
      proxy?.$modal.msgWarning('请选择行');
    } else {
      await $table.removeCheckboxRow();
      updateElement();
    }
  }
};
const updateElement = () => {
  const $table = tableRef.value;
  const data = $table.getTableData().fullData;
  if (data.length) {
    let extensionElements = props.element.businessObject.get('extensionElements');
    if (!extensionElements) {
      extensionElements = moddle.create('bpmn:ExtensionElements');
    }
    // æ¸…除旧值
    extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:TaskListener') ?? [];
    data.forEach((item) => {
      const taskListener = moddle.create('flowable:TaskListener');
      taskListener['event'] = item.event;
      taskListener[item.type] = item.className;
      if (item.params && item.params.length) {
        item.params.forEach((field) => {
          const fieldElement = moddle.create('flowable:Field');
          fieldElement['name'] = field.name;
          fieldElement[field.type] = field.value;
          taskListener.get('fields').push(fieldElement);
        });
      }
      extensionElements.get('values').push(taskListener);
    });
    updateProperties({ extensionElements: extensionElements });
  } else {
    const extensionElements = props.element.businessObject[`extensionElements`];
    if (extensionElements) {
      extensionElements.values = extensionElements.values?.filter((item) => item.$type !== 'flowable:TaskListener') ?? [];
    }
  }
};
const cellDBLClickEvent: VxeTableEvents.CellDblclick<TaskListenerVO> = ({ row }) => {
  editEvent(row);
};
const menuConfig = reactive<VxeTablePropTypes.MenuConfig<TaskListenerVO>>({
  body: {
    options: [
      [
        { code: 'edit', name: '编辑', prefixIcon: 'vxe-icon-edit', disabled: false },
        { code: 'remove', name: '删除', prefixIcon: 'vxe-icon-delete', disabled: false }
      ]
    ]
  },
  visibleMethod({ options, column }) {
    const isDisabled = !column;
    options.forEach((list) => {
      list.forEach((item) => {
        item.disabled = isDisabled;
      });
    });
    return true;
  }
});
const contextMenuClickEvent: VxeTableEvents.MenuClick<TaskListenerVO> = ({ menu, row, column }) => {
  const $table = tableRef.value;
  if ($table) {
    switch (menu.code) {
      case 'edit':
        editEvent(row);
        break;
      case 'remove':
        removeEvent(row);
        break;
    }
  }
};
const initTableData = () => {
  tableData.value =
    props.element.businessObject.extensionElements?.values
      .filter((item) => item.$type === 'flowable:TaskListener')
      .map((item) => {
        let type;
        if ('class' in item) type = 'class';
        if ('expression' in item) type = 'expression';
        if ('delegateExpression' in item) type = 'delegateExpression';
        return {
          event: item.event,
          type: type,
          className: item[type],
          params:
            item.fields?.map((field) => {
              let fieldType;
              if ('stringValue' in field) fieldType = 'stringValue';
              if ('expression' in field) fieldType = 'expression';
              return {
                name: field.name,
                type: fieldType,
                value: field[fieldType]
              };
            }) ?? []
        };
      }) ?? [];
};
onMounted(() => {
  initTableData();
});
const typeSelect = [
  { id: '742fdeb7-23b4-416b-ac66-cd4ec8b901b7', label: 'ç±»', value: 'class' },
  { id: '660c9c46-8fae-4bae-91a0-0335420019dc', label: '表达式', value: 'expression' },
  { id: '4b8135ab-6bc3-4a0f-80be-22f58bc6c5fd', label: '委托表达式', value: 'delegateExpression' }
];
const eventSelect = [
  { id: 'e6e0a51a-2d5d-4dc4-b847-b5c14f43a6ab', label: '创建', value: 'create' },
  { id: '6da97c1e-15fc-4445-8943-75d09f49778e', label: '指派', value: 'assignment' },
  { id: '6a2cbcec-e026-4f11-bef7-fff0b5c871e2', label: '完成', value: 'complete' },
  { id: '68801972-85f1-482f-bd86-1fad015c26ed', label: '删除', value: 'delete' }
];
</script>
<style scoped lang="scss">
.el-badge {
  :deep(.el-badge__content) {
    top: 10px;
  }
}
</style>
src/components/BpmnDesign/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
<template>
  <div class="design">
    <el-dialog v-model="visible" width="100%" fullscreen :title="title">
      <div class="modeler">
        <bpmn-design ref="bpmnDesignRef" @save-call-back="saveCallBack"></bpmn-design>
      </div>
    </el-dialog>
  </div>
</template>
<script lang="ts" setup name="Design">
import { getInfo, editModelXml } from '@/api/workflow/model';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { ModelForm } from '@/api/workflow/model/types';
import BpmnDesign from '@/bpmn/index.vue';
import useDialog from '@/hooks/useDialog';
const bpmnDesignRef = ref<InstanceType<typeof BpmnDesign>>();
const modelForm = ref<ModelForm>();
const emit = defineEmits(['closeCallBack']);
const { visible, title } = useDialog({
  title: '编辑流程'
});
const modelId = ref('');
const open = async (id) => {
  visible.value = true;
  modelId.value = id;
  const { data } = await getInfo(id);
  modelForm.value = data;
  bpmnDesignRef.value.initDiagram(modelForm.value.xml);
};
//保存模型
const saveCallBack = async (data) => {
  await proxy?.$modal.confirm('是否确认保存?');
  data.loading.value = true;
  modelForm.value.id = modelId.value;
  modelForm.value.xml = data.xml;
  modelForm.value.svg = data.svg;
  modelForm.value.key = data.key;
  modelForm.value.name = data.name;
  editModelXml(modelForm.value).then((res) => {
    if (res.code === 200) {
      visible.value = false;
      proxy?.$modal.msgSuccess('保存成功');
      emit('closeCallBack', data);
    }
  });
  data.loading.value = false;
};
/**
 * å¯¹å¤–暴露子组件方法
 */
defineExpose({
  open
});
</script>
<style lang="scss" scoped>
.design {
  :deep(.el-dialog .el-dialog__body) {
    max-height: 100% !important;
    min-height: calc(100vh - 80px);
    padding: 10px 0 10px 0 !important;
  }
  :deep(.el-dialog__header) {
    padding: 0 0 5px 0 !important;
  }
}
</style>
src/components/BpmnView/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,411 @@
<template>
  <div v-loading="loading" class="bpmnDialogContainers">
    <el-header style="border-bottom: 1px solid rgb(218 218 218); height: auto">
      <div class="header-div">
        <div>
          <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
            <el-button size="small" icon="Rank" @click="fitViewport" />
          </el-tooltip>
          <el-tooltip effect="dark" content="放大" placement="bottom">
            <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
          </el-tooltip>
          <el-tooltip effect="dark" content="缩小" placement="bottom">
            <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
          </el-tooltip>
        </div>
        <div>
          <div class="tips-label">
            <div class="un-complete">未完成</div>
            <div class="in-progress">进行中</div>
            <div class="complete">已完成</div>
          </div>
        </div>
      </div>
    </el-header>
    <div class="flow-containers">
      <el-container class="bpmn-el-container" style="align-items: stretch">
        <el-main style="padding: 0">
          <div ref="canvas" class="canvas" />
        </el-main>
      </el-container>
    </div>
  </div>
</template>
<script lang="ts" setup>
import BpmnViewer from 'bpmn-js/lib/Viewer';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
import { ModuleDeclaration } from 'didi';
import type { Canvas, ModdleElement } from 'bpmn';
import EventBus from 'diagram-js/lib/core/EventBus';
import Overlays from 'diagram-js/lib/features/overlays/Overlays';
import processApi from '@/api/workflow/processInstance/index';
const canvas = ref<HTMLElement>();
const modeler = ref<BpmnViewer>();
const taskList = ref([]);
const zoom = ref(1);
const xml = ref('');
const loading = ref(false);
const bpmnVisible = ref(true);
const historyList = ref([]);
const init = (businessKey) => {
  loading.value = true;
  bpmnVisible.value = true;
  nextTick(async () => {
    if (modeler.value) modeler.value.destroy();
    modeler.value = new BpmnViewer({
      container: canvas.value,
      additionalModules: [
        {
          //禁止滚轮滚动
          zoomScroll: ['value', '']
        },
        ZoomScrollModule,
        MoveCanvasModule
      ] as ModuleDeclaration[]
    });
    const resp = await processApi.getHistoryList(businessKey);
    xml.value = resp.data.xml;
    taskList.value = resp.data.taskList;
    historyList.value = resp.data.historyList;
    await createDiagram(xml.value);
    loading.value = false;
  });
};
const initXml = (xmlStr: string) => {
  loading.value = true;
  bpmnVisible.value = true;
  nextTick(async () => {
    if (modeler.value) modeler.value.destroy();
    modeler.value = new BpmnViewer({
      container: canvas.value,
      additionalModules: [
        {
          //禁止滚轮滚动
          zoomScroll: ['value', '']
        },
        ZoomScrollModule,
        MoveCanvasModule
      ] as ModuleDeclaration[]
    });
    xml.value = xmlStr;
    await createDiagram(xml.value);
    loading.value = false;
  });
};
const createDiagram = async (data) => {
  try {
    await modeler.value.importXML(data);
    fitViewport();
    fillColor();
    loading.value = false;
    addEventBusListener();
  } catch (err) {
    console.log(err);
  }
};
const addEventBusListener = () => {
  const eventBus = modeler.value.get<EventBus>('eventBus');
  const overlays = modeler.value.get<Overlays>('overlays');
  eventBus.on<ModdleElement>('element.hover', (e) => {
    let data = historyList.value.find((t) => t.taskDefinitionKey === e.element.id);
    if (e.element.type === 'bpmn:UserTask' && data) {
      setTimeout(() => {
        genNodeDetailBox(e, overlays, data);
      }, 10);
    }
  });
  eventBus.on('element.out', (e) => {
    overlays.clear();
  });
};
const genNodeDetailBox = (e, overlays, data) => {
  overlays.add(e.element.id, {
    position: { top: e.element.height, left: 0 },
    html: `<div class="verlays">
                    <p>审批人员: ${data.nickName || ''}<p/>
                    <p>节点状态:${data.status || ''}</p>
                    <p>开始时间:${data.startTime || ''}</p>
                    <p>结束时间:${data.endTime || ''}</p>
                    <p>审批耗时:${data.runDuration || ''}</p>
                    <p>流程版本:v${data.version || ''}</p>
                   </div>`
  });
};
// è®©å›¾èƒ½è‡ªé€‚应屏幕
const fitViewport = () => {
  zoom.value = modeler.value.get<Canvas>('canvas').zoom('fit-viewport');
  const bbox = document.querySelector<SVGGElement>('.flow-containers .viewport').getBBox();
  const currentViewBox = modeler.value.get('canvas').viewbox();
  const elementMid = {
    x: bbox.x + bbox.width / 2 - 65,
    y: bbox.y + bbox.height / 2
  };
  modeler.value.get<Canvas>('canvas').viewbox({
    x: elementMid.x - currentViewBox.width / 2,
    y: elementMid.y - currentViewBox.height / 2,
    width: currentViewBox.width,
    height: currentViewBox.height
  });
  zoom.value = (bbox.width / currentViewBox.width) * 1.8;
};
// æ”¾å¤§ç¼©å°
const zoomViewport = (zoomIn = true) => {
  zoom.value = modeler.value.get<Canvas>('canvas').zoom();
  zoom.value += zoomIn ? 0.1 : -0.1;
  modeler.value.get<Canvas>('canvas').zoom(zoom.value);
};
//上色
const fillColor = () => {
  const canvas = modeler.value.get<Canvas>('canvas');
  bpmnNodeList(modeler.value._definitions.rootElements[0].flowElements, canvas);
};
//递归上色
const bpmnNodeList = (flowElements, canvas) => {
  flowElements.forEach((n) => {
    if (n.$type === 'bpmn:UserTask') {
      const completeTask = taskList.value.find((m) => m.key === n.id);
      if (completeTask) {
        canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo');
        n.outgoing?.forEach((nn) => {
          const targetTask = taskList.value.find((m) => m.key === nn.targetRef.id);
          if (targetTask) {
            canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo');
          } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
            canvas.addMarker(nn.id, completeTask.completed ? 'highlight' : 'highlight-todo');
            canvas.addMarker(nn.targetRef.id, completeTask.completed ? 'highlight' : 'highlight-todo');
            nn.targetRef.outgoing.forEach((e) => {
              gateway(e.id, e.targetRef.$type, e.targetRef.id, canvas, completeTask.completed);
            });
          } else if (nn.targetRef.$type === 'bpmn:ParallelGateway') {
            canvas.addMarker(nn.id, completeTask.completed ? 'highlight' : 'highlight-todo');
            canvas.addMarker(nn.targetRef.id, completeTask.completed ? 'highlight' : 'highlight-todo');
            nn.targetRef.outgoing.forEach((e) => {
              gateway(e.id, e.targetRef.$type, e.targetRef.id, canvas, completeTask.completed);
            });
          } else if (nn.targetRef.$type === 'bpmn:InclusiveGateway') {
            canvas.addMarker(nn.id, completeTask.completed ? 'highlight' : 'highlight-todo');
            canvas.addMarker(nn.targetRef.id, completeTask.completed ? 'highlight' : 'highlight-todo');
            nn.targetRef.outgoing.forEach((e) => {
              gateway(e.id, e.targetRef.$type, e.targetRef.id, canvas, completeTask.completed);
            });
          }
        });
      }
    } else if (n.$type === 'bpmn:ExclusiveGateway') {
      n.outgoing.forEach((nn) => {
        const targetTask = taskList.value.find((m) => m.key === nn.targetRef.id);
        if (targetTask) {
          canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo');
        }
      });
    } else if (n.$type === 'bpmn:ParallelGateway') {
      n.outgoing.forEach((nn) => {
        const targetTask = taskList.value.find((m) => m.key === nn.targetRef.id);
        if (targetTask) {
          canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo');
        }
      });
    } else if (n.$type === 'bpmn:InclusiveGateway') {
      n.outgoing.forEach((nn) => {
        const targetTask = taskList.value.find((m) => m.key === nn.targetRef.id);
        if (targetTask) {
          canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo');
        }
      });
    } else if (n.$type === 'bpmn:SubProcess') {
      const completeTask = taskList.value.find((m) => m.key === n.id);
      if (completeTask) {
        canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo');
      }
      bpmnNodeList(n.flowElements, canvas);
    } else if (n.$type === 'bpmn:StartEvent') {
      canvas.addMarker(n.id, 'startEvent');
      if (n.outgoing) {
        n.outgoing.forEach((nn) => {
          const completeTask = taskList.value.find((m) => m.key === nn.targetRef.id);
          if (completeTask) {
            canvas.addMarker(nn.id, 'highlight');
            canvas.addMarker(n.id, 'highlight');
          }
        });
      }
    } else if (n.$type === 'bpmn:EndEvent') {
      canvas.addMarker(n.id, 'endEvent');
      const completeTask = taskList.value.find((m) => m.key === n.id);
      if (completeTask) {
        canvas.addMarker(completeTask.key, 'highlight');
        canvas.addMarker(n.id, 'highlight');
        return;
      }
    }
  });
};
const gateway = (id, targetRefType, targetRefId, canvas, completed) => {
  if (targetRefType === 'bpmn:ExclusiveGateway') {
    canvas.addMarker(id, completed ? 'highlight' : 'highlight-todo');
    canvas.addMarker(targetRefId, completed ? 'highlight' : 'highlight-todo');
  }
  if (targetRefType === 'bpmn:ParallelGateway') {
    canvas.addMarker(id, completed ? 'highlight' : 'highlight-todo');
    canvas.addMarker(targetRefId, completed ? 'highlight' : 'highlight-todo');
  }
  if (targetRefType === 'bpmn:InclusiveGateway') {
    canvas.addMarker(id, completed ? 'highlight' : 'highlight-todo');
    canvas.addMarker(targetRefId, completed ? 'highlight' : 'highlight-todo');
  }
};
defineExpose({
  init,
  initXml
});
</script>
<style lang="scss" scoped>
.canvas {
  width: 100%;
  height: 100%;
}
.header-div {
  display: flex;
  padding: 10px 0;
  justify-content: space-between;
  .tips-label {
    display: flex;
    div {
      margin-right: 10px;
      padding: 5px;
      font-size: 12px;
    }
    .un-complete {
      border: 1px solid #000;
    }
    .in-progress {
      background-color: rgb(255, 237, 204);
      border: 1px dashed orange;
    }
    .complete {
      background-color: rgb(204, 230, 204);
      border: 1px solid green;
    }
  }
}
.view-mode {
  .el-header,
  .el-aside,
  .djs-palette,
  .bjs-powered-by {
    display: none;
  }
  .el-loading-mask {
    background-color: initial;
  }
  .el-loading-spinner {
    display: none;
  }
}
.bpmn-el-container {
  height: calc(100vh - 350px);
}
.flow-containers {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  .canvas {
    width: 100%;
    height: 100%;
  }
  .load {
    margin-right: 10px;
  }
  :deep(.el-form-item__label) {
    font-size: 13px;
  }
  :deep(.djs-palette) {
    left: 0 !important;
    top: 0;
    border-top: none;
  }
  :deep(.djs-container svg) {
    min-height: 650px;
  }
  :deep(.startEvent.djs-shape .djs-visual > :nth-child(1)) {
    fill: #77df6d !important;
  }
  :deep(.endEvent.djs-shape .djs-visual > :nth-child(1)) {
    fill: #ee7b77 !important;
  }
  :deep(.highlight.djs-shape .djs-visual > :nth-child(1)) {
    fill: green !important;
    stroke: green !important;
    fill-opacity: 0.2 !important;
  }
  :deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
    fill: green !important;
  }
  :deep(.highlight.djs-shape .djs-visual > path) {
    fill: green !important;
    fill-opacity: 0.2 !important;
    stroke: green !important;
  }
  :deep(.highlight.djs-connection > .djs-visual > path) {
    stroke: green !important;
  }
  // è¾¹æ¡†æ»šåŠ¨åŠ¨ç”»
  @keyframes path-animation {
    from {
      stroke-dashoffset: 100%;
    }
    to {
      stroke-dashoffset: 0%;
    }
  }
  :deep(.highlight-todo.djs-connection > .djs-visual > path) {
    animation: path-animation 60s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    stroke-dasharray: 4px !important;
    stroke: orange !important;
    fill-opacity: 0.2 !important;
    marker-end: url('#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr');
  }
  :deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
    animation: path-animation 60s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    stroke-dasharray: 4px !important;
    stroke: orange !important;
    fill: orange !important;
    fill-opacity: 0.2 !important;
  }
}
:deep(.verlays) {
  width: 250px;
  background: rgb(102, 102, 102);
  border-radius: 4px;
  border: 1px solid #ebeef5;
  color: #fff;
  padding: 15px 10px;
  p {
    line-height: 28px;
    margin: 0;
    padding: 0;
  }
  cursor: pointer;
}
</style>
src/components/Process/multiInstanceUser.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,378 @@
<template>
  <el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body :close-on-click-modal="false">
    <div v-if="multiInstance === 'add'" class="p-2">
      <el-row :gutter="20">
        <!-- éƒ¨é—¨æ ‘ -->
        <el-col :lg="4" :xs="24" style="">
          <el-card shadow="hover">
            <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
            <el-tree
              ref="deptTreeRef"
              class="mt-2"
              node-key="id"
              :data="deptOptions"
              :props="{ label: 'label', children: 'children' }"
              :expand-on-click-node="false"
              :filter-node-method="filterNode"
              highlight-current
              default-expand-all
              @node-click="handleNodeClick"
            ></el-tree>
          </el-card>
        </el-col>
        <el-col :lg="20" :xs="24">
          <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div v-show="showSearch" class="search">
              <el-form ref="queryFormRef" :model="queryParams" :inline="true">
                <el-form-item label="用户名称" prop="userName">
                  <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="手机号码" prop="phonenumber">
                  <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                </el-form-item>
              </el-form>
            </div>
          </transition>
          <el-card shadow="hover">
            <template #header>
              <el-row :gutter="10">
                <right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="handleQuery"></right-toolbar>
              </el-row>
            </template>
            <el-table ref="multipleTableRef" v-loading="loading" :data="userList" row-key="userId" @selection-change="handleSelectionChange">
              <el-table-column type="selection" width="50" align="center" />
              <el-table-column key="userId" label="用户编号" align="center" prop="userId" />
              <el-table-column key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
              <el-table-column key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
              <el-table-column key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
              <el-table-column label="创建时间" align="center" prop="createTime" width="160">
                <template #default="scope">
                  <span>{{ scope.row.createTime }}</span>
                </template>
              </el-table-column>
            </el-table>
            <pagination
              v-show="total > 0"
              v-model:page="queryParams.pageNum"
              v-model:limit="queryParams.pageSize"
              :total="total"
              @pagination="handleQuery"
            />
          </el-card>
          <el-card shadow="hover">
            <el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin: 2px" closable @close="handleCloseTag(user, index)"
              >{{ user.userName }}
            </el-tag>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <div v-if="multiInstance === 'delete'" class="p-2">
      <el-table v-loading="loading" :data="taskList" @selection-change="handleTaskSelection">
        <el-table-column type="selection" width="55" />
        <el-table-column prop="name" label="任务名称" />
        <el-table-column prop="assigneeName" label="办理人" />
      </el-table>
    </div>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
        <el-button @click="visible = false">取 æ¶ˆ</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup name="User" lang="ts">
import { deptTreeSelect, listUser, optionSelect } from '@/api/system/user';
import {
  addMultiInstanceExecution,
  deleteMultiInstanceExecution,
  getTaskUserIdsByAddMultiInstance,
  getListByDeleteMultiInstance
} from '@/api/workflow/task';
import { UserVO } from '@/api/system/user/types';
import { DeptVO } from '@/api/system/dept/types';
import { ComponentInternalInstance } from 'vue';
import { ElTree, ElTable } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
  // å®½
  width: {
    type: String,
    default: '70%'
  },
  // é«˜
  height: {
    type: String,
    default: '100%'
  },
  // æ ‡é¢˜
  title: {
    type: String,
    default: '加签人员'
  },
  //是否多选
  multiple: {
    type: Boolean,
    default: true
  },
  //回显用户id
  userIdList: {
    type: Array,
    default: () => []
  }
});
const deptTreeRef = ref(ElTree);
const multipleTableRef = ref(ElTable);
const userList = ref<UserVO[]>();
const taskList = ref<Array<any>[]>();
const loading = ref(true);
const showSearch = ref(true);
const selectionTask = ref<Array<any>[]>();
const visible = ref(false);
const total = ref(0);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const chooseUserList = ref(ref<UserVO[]>());
const userIds = ref<Array<number | string>>([]);
//加签或者减签
const multiInstance = ref('');
const queryParams = ref<Record<string, any>>({
  pageNum: 1,
  pageSize: 10,
  userName: '',
  nickName: '',
  taskId: ''
});
/** æŸ¥è¯¢ç”¨æˆ·åˆ—表 */
const getAddMultiInstanceList = async (taskId: string, userIdList: Array<number | string>) => {
  deptOptions.value = [];
  getTreeSelect();
  multiInstance.value = 'add';
  userIds.value = userIdList;
  visible.value = true;
  queryParams.value.taskId = taskId;
  loading.value = true;
  const res1 = await getTaskUserIdsByAddMultiInstance(taskId);
  queryParams.value.excludeUserIds = res1.data;
  const res = await listUser(queryParams.value);
  loading.value = false;
  userList.value = res.rows;
  total.value = res.total;
  if (userList.value && userIds.value.length > 0) {
    const data = await optionSelect(userIds.value);
    if (data.data && data.data.length > 0) {
      chooseUserList.value = data.data;
      data.data.forEach((user: UserVO) => {
        multipleTableRef.value!.toggleRowSelection(
          userList.value.find((item) => {
            return item.userId == user.userId;
          }),
          true
        );
      });
    }
  }
};
const getList = async () => {
  loading.value = true;
  const res1 = await getTaskUserIdsByAddMultiInstance(queryParams.value.taskId);
  queryParams.value.excludeUserIds = res1.data;
  const res = await listUser(queryParams.value);
  loading.value = false;
  userList.value = res.rows;
  total.value = res.total;
  if (userList.value && userIds.value.length > 0) {
    const data = await optionSelect(userIds.value);
    if (data.data && data.data.length > 0) {
      chooseUserList.value = data.data;
      data.data.forEach((user: UserVO) => {
        multipleTableRef.value!.toggleRowSelection(
          userList.value.find((item) => {
            return item.userId == user.userId;
          }),
          true
        );
      });
    }
  }
};
const getDeleteMultiInstanceList = async (taskId: string) => {
  deptOptions.value = [];
  loading.value = true;
  queryParams.value.taskId = taskId;
  multiInstance.value = 'delete';
  visible.value = true;
  const res = await getListByDeleteMultiInstance(taskId);
  taskList.value = res.data;
  loading.value = false;
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getAddMultiInstanceList(queryParams.value.taskId, userIds.value);
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryParams.value.pageNum = 1;
  queryParams.value.deptId = undefined;
  queryParams.value.userName = undefined;
  queryParams.value.nickName = undefined;
  deptTreeRef.value.setCurrentKey(null);
  handleQuery();
};
/** é€‰æ‹©æ¡æ•°  */
const handleSelectionChange = (selection: UserVO[]) => {
  if (props.multiple) {
    chooseUserList.value = selection.filter((element, index, self) => {
      return self.findIndex((x) => x.userId === element.userId) === index;
    });
    selection.forEach((u) => {
      if (chooseUserList.value && !chooseUserList.value.includes(u)) {
        multipleTableRef.value!.toggleRowSelection(u, undefined);
      }
    });
    userIds.value = chooseUserList.value.map((item) => {
      return item.userId;
    });
  } else {
    chooseUserList.value = selection;
    if (selection.length > 1) {
      let delRow = selection.shift();
      multipleTableRef.value!.toggleRowSelection(delRow, undefined);
    }
    if (selection.length === 0) {
      chooseUserList.value = [];
    }
  }
};
/** é€‰æ‹©æ¡æ•°  */
const handleTaskSelection = (selection: any) => {
  selectionTask.value = selection;
};
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
const getTreeSelect = async () => {
  const res = await deptTreeSelect();
  deptOptions.value = res.data;
};
/** é€šè¿‡æ¡ä»¶è¿‡æ»¤èŠ‚ç‚¹  */
const filterNode = (value: string, data: any) => {
  if (!value) return true;
  return data.label.indexOf(value) !== -1;
};
/** æ ¹æ®åç§°ç­›é€‰éƒ¨é—¨æ ‘ */
watchEffect(
  () => {
    if (visible.value && deptOptions.value && deptOptions.value.length > 0) {
      deptTreeRef.value.filter(deptName.value);
    }
  },
  {
    flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
  }
);
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: DeptVO) => {
  queryParams.value.deptId = data.id;
  getList();
};
//删除tag
const handleCloseTag = (user: UserVO, index: any) => {
  if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) {
    multipleTableRef.value.selection.forEach((u: UserVO, i: number) => {
      if (user.userId === u.userId) {
        multipleTableRef.value.selection.splice(i, 1);
      }
    });
  }
  if (chooseUserList.value && chooseUserList.value.length > 0) {
    chooseUserList.value.splice(index, 1);
  }
  multipleTableRef.value.toggleRowSelection(user, undefined);
  if (userIds.value && userIds.value.length > 0) {
    userIds.value.forEach((userId, i) => {
      if (userId === user.userId) {
        userIds.value.splice(i, 1);
      }
    });
  }
};
const submitFileForm = async () => {
  if (multiInstance.value === 'add') {
    if (chooseUserList.value && chooseUserList.value.length > 0) {
      loading.value = true;
      let userIds = chooseUserList.value.map((item) => {
        return item.userId;
      });
      let nickNames = chooseUserList.value.map((item) => {
        return item.nickName;
      });
      let params = {
        taskId: queryParams.value.taskId,
        assignees: userIds,
        assigneeNames: nickNames
      };
      await addMultiInstanceExecution(params);
      emits('submitCallback');
      loading.value = false;
      proxy?.$modal.msgSuccess('操作成功');
      visible.value = false;
    }
  } else {
    if (selectionTask.value && selectionTask.value.length > 0) {
      loading.value = true;
      let taskIds = selectionTask.value.map((item: any) => {
        return item.id;
      });
      let executionIds = selectionTask.value.map((item: any) => {
        return item.executionId;
      });
      let assigneeIds = selectionTask.value.map((item: any) => {
        return item.assignee;
      });
      let assigneeNames = selectionTask.value.map((item: any) => {
        return item.assigneeName;
      });
      let params = {
        taskId: queryParams.value.taskId,
        taskIds: taskIds,
        executionIds: executionIds,
        assigneeIds: assigneeIds,
        assigneeNames: assigneeNames
      };
      await deleteMultiInstanceExecution(params);
      emits('submitCallback');
      loading.value = false;
      proxy?.$modal.msgSuccess('操作成功');
      visible.value = false;
    }
  }
};
//事件
const emits = defineEmits(['submitCallback']);
/**
 * å¯¹å¤–暴露子组件方法
 */
defineExpose({
  getAddMultiInstanceList,
  getDeleteMultiInstanceList
});
</script>
src/enums/SettingTypeEnum.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
export enum SettingTypeEnum {
  TITLE = 'title',
  THEME = 'theme',
  SIDE_THEME = 'sideTheme',
  SHOW_SETTINGS = 'showSettings',
  TOP_NAV = 'topNav',
  TAGS_VIEW = 'tagsView',
  FIXED_HEADER = 'fixedHeader',
  SIDEBAR_LOGO = 'sidebarLogo',
  DYNAMIC_TITLE = 'dynamicTitle',
  ANIMATION_ENABLE = 'animationEnable',
  LAYOUT = 'layout',
  DARK = 'dark',
  LAYOUT_SETTING = 'layout-setting'
}
src/enums/bpmn/IndexEnums.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
export enum AllocationTypeEnum {
  USER = 'user',
  CANDIDATE = 'candidate',
  YOURSELF = 'yourself',
  SPECIFY = 'specify'
}
export enum SpecifyDescEnum {
  SPECIFY_MULTIPLE = 'specifyMultiple',
  SPECIFY_SINGLE = 'specifySingle'
}
export enum MultiInstanceTypeEnum {
  SERIAL = 'serial',
  PARALLEL = 'parallel',
  NONE = 'none'
}
src/enums/layout/LayoutEnum.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
export enum ThemeEnum {
  DARK = 'theme-dark',
  LIGHT = 'theme-light'
}
src/lang/en_US.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
{
  "route": {
    "dashboard": "Dashboard",
    "document": "Document"
  },
  "login": {
    "username": "Username",
    "password": "Password",
    "login": "Login",
    "code": "Verification Code",
    "copyright": ""
  },
  "navbar": {
    "full": "Full Screen",
    "language": "Language",
    "dashboard": "Dashboard",
    "document": "Document",
    "message": "Message",
    "layoutSize": "Layout Size",
    "selectTenant": "Select Tenant",
    "layoutSetting": "Layout Setting",
    "personalCenter": "Personal Center",
    "logout": "Logout"
  }
}
src/lang/zh_CN.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
{
  "route": {
    "dashboard": "首页",
    "document": "项目文档"
  },
  "login": {
    "username": "用户名",
    "password": "密码",
    "login": "登 å½•",
    "code": "请输入验证码",
    "copyright": ""
  },
  "navbar": {
    "full": "全屏",
    "language": "语言",
    "dashboard": "首页",
    "document": "项目文档",
    "message": "消息",
    "layoutSize": "布局大小",
    "selectTenant": "选择租户",
    "layoutSetting": "布局设置",
    "personalCenter": "个人中心",
    "logout": "退出登录"
  }
}
src/store/modules/modeler.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
import type { Modeler, Modeling, Canvas, ElementRegistry, Moddle, BpmnFactory } from 'bpmn';
type ModelerStore = {
  modeler: Modeler | undefined;
  moddle: Moddle | undefined;
  modeling: Modeling | undefined;
  canvas: Canvas | undefined;
  elementRegistry: ElementRegistry | undefined;
  bpmnFactory: BpmnFactory | undefined;
  // æµç¨‹å®šä¹‰æ ¹èŠ‚ç‚¹ä¿¡æ¯
  procDefId: string | undefined;
  procDefName: string | undefined;
};
const defaultState: ModelerStore = {
  modeler: undefined,
  moddle: undefined,
  modeling: undefined,
  canvas: undefined,
  elementRegistry: undefined,
  bpmnFactory: undefined,
  procDefId: undefined,
  procDefName: undefined
};
export const useModelerStore = defineStore('modeler', () => {
  let modeler = defaultState.modeler;
  let moddle = defaultState.moddle;
  let modeling = defaultState.modeling;
  let canvas = defaultState.canvas;
  let elementRegistry = defaultState.elementRegistry;
  let bpmnFactory = defaultState.bpmnFactory;
  const procDefId = ref(defaultState.procDefId);
  const procDefName = ref(defaultState.procDefName);
  const getModeler = () => modeler;
  const getModdle = () => moddle;
  const getModeling = (): Modeling | undefined => modeling;
  const getCanvas = (): Canvas | undefined => canvas;
  const getElRegistry = (): ElementRegistry | undefined => elementRegistry;
  const getBpmnFactory = (): BpmnFactory | undefined => bpmnFactory;
  const getProcDefId = (): string | undefined => procDefId.value;
  const getProcDefName = (): string | undefined => procDefName.value;
  // è®¾ç½®æ ¹èŠ‚ç‚¹
  const setModeler = (modelers: Modeler | undefined) => {
    if (modelers) {
      modeler = modelers;
      modeling = modelers.get<Modeling>('modeling');
      moddle = modelers.get<Moddle>('moddle');
      canvas = modelers.get<Canvas>('canvas');
      bpmnFactory = modelers.get<BpmnFactory>('bpmnFactory');
      elementRegistry = modelers.get<ElementRegistry>('elementRegistry');
    } else {
      modeling = moddle = canvas = elementRegistry = bpmnFactory = undefined;
    }
  };
  // è®¾ç½®æµç¨‹å®šä¹‰æ ¹èŠ‚ç‚¹ä¿¡æ¯
  const setProcDef = (modeler: Modeler | undefined) => {
    procDefId.value = modeler.get<Canvas>('canvas').getRootElement().businessObject.get('id');
    procDefName.value = modeler.get<Canvas>('canvas').getRootElement().businessObject.get('name');
  };
  return {
    getModeler,
    getModdle,
    getModeling,
    getCanvas,
    getElRegistry,
    getBpmnFactory,
    getProcDefId,
    getProcDefName,
    setModeler,
    setProcDef
  };
});
export default useModelerStore;
src/types/bpmn/editor/global.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
import { MessageApiInjection } from 'naive-ui/lib/message/src/MessageProvider';
declare global {
  interface Window {
    bpmnInstances: any;
    __messageBox: MessageApiInjection;
    URL: any;
  }
}
declare interface Window {
  bpmnInstances: any;
}
src/types/bpmn/index.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
declare module 'bpmn' {
  import type modeler from 'bpmn-js/lib/Modeler';
  import type modeling from 'bpmn-js/lib/features/modeling/Modeling';
  import type canvas from 'diagram-js/lib/core/Canvas';
  import type elementRegistry from 'diagram-js/lib/core/ElementRegistry';
  import type bpmnFactory from 'bpmn-js/lib/features/modeling/BpmnFactory';
  export type Modeler = modeler;
  export type Modeling = modeling;
  export type Canvas = canvas;
  export type ElementRegistry = elementRegistry;
  export type Moddle = import('moddle').Moddle;
  export type ModdleElement = import('moddle').ModdleElement;
  export type BpmnFactory = bpmnFactory;
}
src/types/bpmn/moddle.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
declare module 'moddle' {
  import type { Element as element } from 'bpmn-js/lib/model/Types';
  export type Element = {
    get<T>(name: string): T;
    set(name: string, value: any): void;
  } & element;
  export interface ModdleElement extends Element {
    $model: Moddle;
    readonly $type: string;
    $attrs: object | {};
    $parent: any;
    businessObject: ModdleElement;
    type: string;
    [field: string]: any;
    hasType(element: ModdleElement, type?: string): boolean;
  }
  export interface Package {
    name: string;
    prefix: string;
  }
  export interface Moddle {
    typeCache: Record<string, ModdleElement>;
    getPackage: typeof Registry.prototype.getPackage;
    getPackages: typeof Registry.prototype.getPackages;
    create(type: string, attrs?: any): ModdleElement;
  }
}
src/types/bpmn/panel.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
declare module 'bpmnDesign' {
  import { AllocationTypeEnum, SpecifyDescEnum, MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
  export interface ParamVO {
    type: string;
    name: string;
    value: string;
  }
  export interface TaskListenerVO {
    event: string;
    type: string;
    name: string;
    className: string;
    params: ParamVO[];
  }
  export interface ExecutionListenerVO {
    event: string;
    type: string;
    className: string;
    params: ParamVO[];
  }
  interface BasePanel {
    id: string;
    name: string;
  }
  export interface ProcessPanel extends BasePanel {}
  export interface TaskPanel extends BasePanel {
    allocationType: AllocationTypeEnum;
    specifyDesc: SpecifyDescEnum;
    multiInstanceType: MultiInstanceTypeEnum;
    async?: boolean;
    priority?: number;
    formKey?: string;
    skipExpression?: string;
    isForCompensation?: boolean;
    triggerServiceTask?: boolean;
    autoStoreVariables?: boolean;
    ruleVariablesInput?: string;
    excludeTaskListener?: boolean;
    exclude?: boolean;
    class?: string;
    dueDate?: string;
    fixedAssignee?: string;
    candidateUsers?: string;
    assignee?: string;
    candidateGroups?: string;
    collection?: string;
    elementVariable?: string;
    completionCondition?: string;
    isSequential?: boolean;
    loopCharacteristics?: {
      collection: string;
      elementVariable: string;
      isSequential: boolean;
      completionCondition: {
        body: string;
      };
    };
  }
  export interface StartEndPanel extends BasePanel {}
  export interface GatewayPanel extends BasePanel {}
  export interface SequenceFlowPanel extends BasePanel {
    conditionExpression: {
      body: string;
    };
    conditionExpressionValue: string;
    skipExpression: string;
  }
  export interface ParticipantPanel extends BasePanel {}
  export interface SubProcessPanel extends BasePanel {
    multiInstanceType: MultiInstanceTypeEnum;
    collection?: string;
    elementVariable?: string;
    completionCondition?: string;
    loopCharacteristics?: {
      collection: string;
      elementVariable: string;
      isSequential: boolean;
      completionCondition: {
        body: string;
      };
    };
  }
}
src/views/workflow/formManage/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,243 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div v-show="showSearch" class="search">
        <el-form ref="queryFormRef" :model="queryParams" :inline="true">
          <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>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:formManage:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:formManage:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
              >修改</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:formManage:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
              >删除</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:formManage:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="formManageList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="表单名称" align="center" prop="formName" />
        <el-table-column label="表单类型" align="center">
          <template #default="scope">
            <dict-tag :options="wf_form_type" :value="scope.row.formType"></dict-tag>
          </template>
        </el-table-column>
        <el-table-column label="地址" align="center" prop="router" />
        <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 v-hasPermi="['workflow:formManage:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button v-hasPermi="['workflow:formManage:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹è¡¨å•ç®¡ç†å¯¹è¯æ¡† -->
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="formManageFormRef" :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="formType">
          <el-radio-group v-model="form.formType" @change="form.router = ''">
            <el-radio v-for="dict in wf_form_type" :key="dict.value" border :value="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item v-if="form.formType === 'static'" label="路由地址" prop="router">
          <el-input v-model="form.router" placeholder="请输入路由地址" />
        </el-form-item>
        <el-form-item v-else label="表单" prop="router">
          <el-input v-model="form.router" disabled placeholder="请选择表单">
            <template #append>
              <el-button icon="Search" />
            </template>
          </el-input>
        </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 :loading="buttonLoading" type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="FormManage" lang="ts">
import { listFormManage, getFormManage, delFormManage, addFormManage, updateFormManage } from '@/api/workflow/formManage';
import { FormManageVO, FormManageQuery, FormManageForm } from '@/api/workflow/formManage/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_form_type } = toRefs<any>(proxy?.useDict('wf_form_type'));
const formManageList = ref<FormManageVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const formManageFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
});
const initFormData: FormManageForm = {
  id: undefined,
  formName: undefined,
  formType: 'static',
  remark: undefined
};
const data = reactive<PageData<FormManageForm, FormManageQuery>>({
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    formName: undefined,
    formType: undefined
  },
  rules: {
    id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
    formName: [{ required: true, message: '表单名称不能为空', trigger: 'blur' }],
    formType: [{ required: true, message: '表单类型不能为空', trigger: 'change' }],
    router: [{ required: true, message: '不能为空', trigger: 'blur' }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢è¡¨å•管理列表 */
const getList = async () => {
  loading.value = true;
  const res = await listFormManage(queryParams.value);
  formManageList.value = res.rows;
  total.value = res.total;
  loading.value = false;
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  formManageFormRef.value?.resetFields();
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: FormManageVO[]) => {
  ids.value = selection.map((item) => item.id);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = '添加表单管理';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: FormManageVO) => {
  reset();
  const _id = row?.id || ids.value[0];
  const res = await getFormManage(_id);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = '修改表单管理';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  formManageFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateFormManage(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addFormManage(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  });
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: FormManageVO) => {
  const _ids = row?.id || ids.value;
  await proxy?.$modal.confirm('是否确认删除表单管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  await delFormManage(_ids);
  proxy?.$modal.msgSuccess('删除成功');
  await getList();
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download(
    'workflow/formManage/export',
    {
      ...queryParams.value
    },
    `formManage_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
  getList();
});
</script>
src/views/workflow/model/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,383 @@
<template>
  <div class="p-2">
    <el-row :gutter="20">
      <!-- æµç¨‹åˆ†ç±»æ ‘ -->
      <el-col :lg="4" :xs="24" style="">
        <el-card shadow="hover">
          <el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
          <el-tree
            ref="categoryTreeRef"
            class="mt-2"
            node-key="id"
            :data="categoryOptions"
            :props="{ label: 'categoryName', children: 'children' }"
            :expand-on-click-node="false"
            :filter-node-method="filterNode"
            highlight-current
            default-expand-all
            @node-click="handleNodeClick"
          ></el-tree>
        </el-card>
      </el-col>
      <el-col :lg="20" :xs="24">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
          <div v-show="showSearch" class="mb-[10px]">
            <el-card shadow="hover">
              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
                <el-form-item label="模型名称" prop="name">
                  <el-input v-model="queryParams.name" placeholder="请输入模型名称" clearable @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="模型KEY" prop="key">
                  <el-input v-model="queryParams.key" placeholder="请输入模型KEY" clearable @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                </el-form-item>
              </el-form>
            </el-card>
          </div>
        </transition>
        <el-card shadow="hover">
          <template #header>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="primary" plain :disabled="multiple" icon="Download" @click="clickExportZip()">导出</el-button>
              </el-col>
              <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
            </el-row>
          </template>
          <el-table v-loading="loading" border :data="modelList" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" align="center" />
            <el-table-column fixed align="center" type="index" label="序号" width="80"></el-table-column>
            <el-table-column align="center" :show-overflow-tooltip="true" prop="name" label="模型名称" width="200"></el-table-column>
            <el-table-column align="center" prop="key" label="模型KEY"></el-table-column>
            <el-table-column align="center" prop="version" label="版本号" width="90">
              <template #default="scope"> v{{ scope.row.version }}.0</template>
            </el-table-column>
            <el-table-column align="center" prop="metaInfo" label="备注说明" min-width="130"></el-table-column>
            <el-table-column align="center" :show-overflow-tooltip="true" prop="createTime" label="创建时间" width="160"></el-table-column>
            <el-table-column align="center" :show-overflow-tooltip="true" prop="lastUpdateTime" label="更新时间" width="160"></el-table-column>
            <el-table-column fixed="right" label="操作" align="center" width="170" class-name="small-padding fixed-width">
              <template #default="scope">
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Pointer" @click="clickDesign(scope.row.id)">设计流程</el-button>
                  </el-col>
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
                  </el-col>
                </el-row>
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="ScaleToOriginal" @click="clickDeploy(scope.row.id, scope.row.key)">
                      æµç¨‹éƒ¨ç½²
                    </el-button>
                  </el-col>
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopy(scope.row)"> å¤åˆ¶æ¨¡åž‹ </el-button>
                  </el-col>
                </el-row>
              </template>
            </el-table-column>
          </el-table>
          <pagination
            v-show="total > 0"
            v-model:page="queryParams.pageNum"
            v-model:limit="queryParams.pageSize"
            :total="total"
            @pagination="getList"
          />
        </el-card>
      </el-col>
    </el-row>
    <!-- è®¾è®¡æµç¨‹å¼€å§‹ -->
    <Design ref="designRef" @close-call-back="handleQuery"></Design>
    <!-- è®¾è®¡æµç¨‹ç»“束 -->
    <!-- æ·»åŠ æ¨¡åž‹å¯¹è¯æ¡† -->
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="650px" append-to-body :close-on-click-modal="false">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="模型名称:" prop="name">
          <el-input v-model="form.name" :disabled="ids && ids.length > 0 && billType === 'update'" maxlength="20" show-word-limit />
        </el-form-item>
        <el-form-item label="模型KEY:" prop="key">
          <el-input v-model="form.key" :disabled="ids && ids.length > 0 && billType === 'update'" maxlength="20" show-word-limit />
        </el-form-item>
        <el-form-item label="流程分类" prop="categoryCode">
          <el-tree-select
            v-model="form.categoryCode"
            :data="categoryOptions"
            :props="{ value: 'categoryCode', label: 'categoryName', children: 'children' }"
            value-key="categoryCode"
            placeholder="请选择流程分类"
            check-strictly
          />
        </el-form-item>
        <el-form-item label="备注:" prop="description">
          <el-input v-model="form.description" type="textarea" maxlength="200" show-word-limit></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script lang="ts" setup name="Model">
import Design from '../../../components/BpmnDesign/index.vue';
import { listModel, addModel, delModel, modelDeploy, getInfo, update } from '@/api/workflow/model';
import { ModelQuery, ModelForm, ModelVO } from '@/api/workflow/model/types';
import { listCategory } from '@/api/workflow/category';
import { copyModel } from '@/api/workflow/model';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const formRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
const designRef = ref<InstanceType<typeof Design>>();
type CategoryOption = {
  categoryCode: string;
  categoryName: string;
  children?: CategoryOption[];
};
const buttonLoading = ref(false);
const loading = ref(true);
const ids = ref<string[]>([]);
const single = ref(true);
const multiple = ref(true);
const showSearch = ref(true);
const total = ref(0);
const modelList = ref<ModelVO[]>([]);
const categoryOptions = ref<CategoryOption[]>([]);
const categoryName = ref('');
const billType = ref<string>('');
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
});
const initFormData: ModelForm = {
  id: '',
  name: '',
  key: '',
  categoryCode: '',
  xml: '',
  svg: '',
  description: ''
};
const data = reactive<PageData<ModelForm, ModelQuery>>({
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    name: '',
    key: '',
    categoryCode: ''
  },
  rules: {
    name: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
    key: [{ required: true, message: '模型KEY不能为空', trigger: 'blur' }],
    categoryCode: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }]
  }
});
const { queryParams, form, rules } = toRefs(data);
onMounted(() => {
  getList();
  getTreeselect();
});
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: ModelForm) => {
  queryParams.value.categoryCode = data.categoryCode;
  if (data.categoryCode === 'ALL') {
    queryParams.value.categoryCode = '';
  }
  handleQuery();
};
/** é€šè¿‡æ¡ä»¶è¿‡æ»¤èŠ‚ç‚¹  */
const filterNode = (value: string, data: any) => {
  if (!value) return true;
  return data.categoryName.indexOf(value) !== -1;
};
/** æ ¹æ®åç§°ç­›é€‰éƒ¨é—¨æ ‘ */
watchEffect(
  () => {
    categoryTreeRef.value?.filter(categoryName.value);
  },
  {
    flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
  }
);
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  queryParams.value.categoryCode = '';
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: ModelVO[]) => {
  ids.value = selection.map((item: ModelVO) => item.id);
  single.value = selection.length !== 1;
  multiple.value = !selection.length;
};
//分页
const getList = async () => {
  loading.value = true;
  const resp = await listModel(queryParams.value);
  modelList.value = resp.rows;
  total.value = resp.total;
  loading.value = false;
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: ModelVO) => {
  const id = row?.id || ids.value;
  await proxy?.$modal.confirm('是否确认删除模型id为【' + id + '】的数据项?');
  loading.value = true;
  await delModel(id).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess('删除成功');
};
// æµç¨‹éƒ¨ç½²
const clickDeploy = async (id: string, key: string) => {
  await proxy?.$modal.confirm('是否部署模型key为【' + key + '】流程?');
  loading.value = true;
  await modelDeploy(id).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess('部署成功');
};
//新增打开
const handleAdd = () => {
  billType.value = 'add';
  ids.value = [];
  getTreeselect();
  form.value = { ...initFormData };
  dialog.visible = true;
  dialog.title = '新增模型';
};
//修改打开
const handleUpdate = () => {
  billType.value = 'update';
  dialog.title = '修改模型';
  nextTick(async () => {
    await getTreeselect();
    const _id = ids.value[0];
    const res = await getInfo(_id);
    Object.assign(form.value, res.data);
    dialog.visible = true;
  });
};
//复制打开
const handleCopy = (row?: ModelVO) => {
  billType.value = 'copy';
  dialog.title = '复制模型';
  nextTick(async () => {
    await getTreeselect();
    form.value = { ...initFormData };
    form.value.id = row.id;
    dialog.visible = true;
  });
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  formRef.value.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if ('copy' === billType.value) {
        await copyModel(form.value);
        proxy?.$modal.msgSuccess('操作成功');
      } else if (ids.value && ids.value.length > 0 && 'update' === billType.value) {
        form.value.id = ids.value[0];
        await update(form.value);
        proxy?.$modal.msgSuccess('操作成功');
      } else {
        initXml(form.value.key, form.value.name);
        form.value.xml = xml.value;
        await addModel(form.value);
        proxy?.$modal.msgSuccess('操作成功');
      }
      dialog.visible = false;
      await getList();
    }
  });
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  formRef.value.resetFields();
};
// æ‰“开设计流程
const clickDesign = async (id: string) => {
  await designRef.value.open(id);
};
// å¯¼å‡ºæµç¨‹æ¨¡åž‹
const clickExportZip = () => {
  proxy?.$download.zip('/workflow/model/export/zip/' + ids.value, '模型');
};
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»ä¸‹æ‹‰æ ‘结构 */
const getTreeselect = async () => {
  const res = await listCategory();
  categoryOptions.value = [];
  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '顶级节点', children: [] };
  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
  categoryOptions.value.push(data);
};
const xml = ref<string>('');
const initXml = async (key: string, name: string) => {
  xml.value = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef">
  <process id="${key}" name="${name}">
    <startEvent id="startNode1" name="开始" />
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_flow">
    <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c">
      <bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke="">
        <omgdc:Bounds x="240" y="200" width="30" height="30" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="242" y="237" width="23" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>`;
  return xml;
};
</script>
src/views/workflow/processDefinition/components/processPreview.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
<template>
  <el-dialog v-model="data.visible" title="预览" width="70%" append-to-body destroy-on-close>
    <div v-if="data.type === 'bpmn' && data.xmlStr">
      <BpmnViewer ref="bpmnViewerRef"></BpmnViewer>
    </div>
    <div v-if="data.type === 'xml' && data.xmlStr">
      <highlightjs language="xml" :code="data.xmlStr" />
    </div>
    <template #footer>
      <span v-if="data.type === 'xml'" class="dialog-footer"> </span>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import BpmnViewer from '@/components/BpmnView/index.vue';
const data = reactive({
  visible: false,
  type: '',
  xmlStr: ''
});
const bpmnViewerRef = ref<InstanceType<typeof BpmnViewer>>();
type PreviewType = 'xml' | 'bpmn';
//打开
const openDialog = (xmlStr: string, type: PreviewType) => {
  data.visible = true;
  data.xmlStr = xmlStr;
  data.type = type;
  /** æµç¨‹å›¾ */
  if (type === 'bpmn') {
    /** å¿…须放在nextTick å¦åˆ™ç¬¬ä¸€æ¬¡æ‰“开为空 */
    nextTick(() => {
      bpmnViewerRef.value?.initXml(data.xmlStr);
    });
  }
};
/**
 * å¯¹å¤–暴露子组件方法
 */
defineExpose({
  openDialog
});
</script>