Browse Source

update 优化bpmn位置

LiuHao 1 year ago
parent
commit
945eec5418

+ 0 - 0
src/components/BpmnDesign/assets/defaultXML.ts → src/bpmn/assets/defaultXML.ts


+ 0 - 0
src/components/BpmnDesign/assets/lang/zh.ts → src/bpmn/assets/lang/zh.ts


+ 0 - 0
src/components/BpmnDesign/assets/moddle/flowable.ts → src/bpmn/assets/moddle/flowable.ts


+ 0 - 0
src/components/BpmnDesign/assets/module/ContextPad/CustomContextPadProvider.ts → src/bpmn/assets/module/ContextPad/CustomContextPadProvider.ts


+ 0 - 0
src/components/BpmnDesign/assets/module/Palette/CustomPaletteProvider.ts → src/bpmn/assets/module/Palette/CustomPaletteProvider.ts


+ 0 - 0
src/components/BpmnDesign/assets/module/Renderer/CustomRenderer.ts → src/bpmn/assets/module/Renderer/CustomRenderer.ts


+ 1 - 1
src/components/BpmnDesign/assets/module/Translate/index.ts → src/bpmn/assets/module/Translate/index.ts

@@ -1,4 +1,4 @@
-import zh from '@/components/BpmnDesign/assets/lang/zh';
+import zh from '../../lang/zh';
 
 const customTranslate = (template: any, replacements: any) => {
   replacements = replacements || {};

+ 0 - 0
src/components/BpmnDesign/assets/module/index.ts → src/bpmn/assets/module/index.ts


+ 0 - 0
src/components/BpmnDesign/assets/showConfig.ts → src/bpmn/assets/showConfig.ts


+ 0 - 0
src/components/BpmnDesign/assets/style/index.scss → src/bpmn/assets/style/index.scss


+ 2 - 2
src/components/BpmnDesign/hooks/usePanel.ts → src/bpmn/hooks/usePanel.ts

@@ -1,4 +1,4 @@
-import showConfig from '@/components/BpmnDesign/assets/showConfig';
+import showConfig from '../assets/showConfig';
 import { ModdleElement } from 'bpmn';
 import useModelerStore from '@/store/modules/modeler';
 import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
@@ -116,7 +116,7 @@ export default (ops: Options) => {
     }
   };
   const formKeyChange = (newVal: string) => {
-      updateProperties({ formKey: newVal });
+    updateProperties({ formKey: newVal });
   };
   const constant = {
     MultiInstanceType: [

+ 0 - 0
src/components/BpmnDesign/hooks/useParseElement.ts → src/bpmn/hooks/useParseElement.ts


+ 498 - 0
src/bpmn/index.vue

@@ -0,0 +1,498 @@
+<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-button size="small" type="primary" @click="saveXml">保 存</el-button>
+                <el-dropdown size="small">
+                  <el-button size="small" type="primary"> 预 览 </el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item icon="Document" @click="previewXML">XML预览</el-dropdown-item>
+                      <el-dropdown-item icon="View" @click="previewSVG"> SVG预览</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+
+                <el-dropdown size="small">
+                  <el-button size="small" type="primary"> 下 载 </el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item icon="Download" @click="downloadXML">下载XML</el-dropdown-item>
+                      <el-dropdown-item icon="Download" @click="downloadSVG"> 下载SVG</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+                <el-tooltip effect="dark" content="新建" placement="bottom">
+                  <el-button size="small" icon="CirclePlus" @click="newDiagram" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
+                  <el-button size="small" icon="Rank" @click="fitViewport" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="放大" placement="bottom">
+                  <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="缩小" placement="bottom">
+                  <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="后退" placement="bottom">
+                  <el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" />
+                </el-tooltip>
+                <el-tooltip effect="dark" content="前进" placement="bottom">
+                  <el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" />
+                </el-tooltip>
+              </el-space>
+            </div>
+          </el-header>
+          <div ref="canvas" class="canvas" />
+        </el-container>
+        <div :class="{ 'process-panel': true, 'hide': panelFlag }">
+          <div class="process-panel-bar" @click="panelBarClick">
+            <div class="open-bar">
+              <el-link type="default" :underline="false">
+                <svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon>
+              </el-link>
+            </div>
+          </div>
+          <transition enter-active-class="animate__animated animate__fadeIn">
+            <div v-show="showPanel" v-if="bpmnModeler" class="panel-content">
+              <PropertyPanel :modeler="bpmnModeler" />
+            </div>
+          </transition>
+        </div>
+      </el-container>
+    </div>
+  </div>
+  <div>
+    <el-dialog v-model="perviewXMLShow" title="XML预览" width="80%" append-to-body>
+      <highlightjs :code="xmlStr" language="XML" />
+    </el-dialog>
+  </div>
+  <div>
+    <el-dialog v-model="perviewSVGShow" title="SVG预览" width="80%" append-to-body>
+      <div style="text-align: center" v-html="svgData" />
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="BpmnDesign">
+import 'bpmn-js/dist/assets/diagram-js.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
+import './assets/style/index.scss';
+import { Canvas, Modeler } from 'bpmn';
+import PropertyPanel from './panel/index.vue';
+import BpmnModeler from 'bpmn-js/lib/Modeler.js';
+import defaultXML from './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: #f5f7fa;
+        }
+      }
+    }
+  }
+}
+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 #eeeeee;
+  box-shadow: #cccccc 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>

+ 3 - 3
src/components/BpmnDesign/panel/GatewayPanel.vue → src/bpmn/panel/GatewayPanel.vue

@@ -39,11 +39,11 @@
   </div>
 </template>
 <script setup lang="ts">
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useParseElement from '../hooks/useParseElement';
+import usePanel from '../hooks/usePanel';
 import { Modeler, ModdleElement } from 'bpmn';
 import { GatewayPanel } from 'bpmnDesign';
-import ExecutionListener from '@/components/BpmnDesign/panel/property/ExecutionListener.vue';
+import ExecutionListener from './property/ExecutionListener.vue';
 
 interface PropType {
   element: ModdleElement;

+ 3 - 2
src/components/BpmnDesign/panel/ParticipantPanel.vue → src/bpmn/panel/ParticipantPanel.vue

@@ -39,8 +39,9 @@
   </div>
 </template>
 <script setup lang="ts">
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useParseElement from '../hooks/useParseElement';
+import usePanel from '../hooks/usePanel';
+import ExecutionListener from './property/ExecutionListener.vue';
 import { ModdleElement } from 'bpmn';
 import { ParticipantPanel } from 'bpmnDesign';
 

+ 2 - 2
src/components/BpmnDesign/panel/ProcessPanel.vue → src/bpmn/panel/ProcessPanel.vue

@@ -41,8 +41,8 @@
 
 <script setup lang="ts">
 import ExecutionListener from './property/ExecutionListener.vue';
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useParseElement from '../hooks/useParseElement';
+import usePanel from '../hooks/usePanel';
 import { Modeler, ModdleElement } from 'bpmn';
 import { ProcessPanel } from 'bpmnDesign';
 

+ 4 - 3
src/components/BpmnDesign/panel/SequenceFlowPanel.vue → src/bpmn/panel/SequenceFlowPanel.vue

@@ -45,11 +45,12 @@
   </div>
 </template>
 <script setup lang="ts">
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useParseElement from '../hooks/useParseElement';
+import useModelerStore from '@/store/modules/modeler';
+import usePanel from '../hooks/usePanel';
+import ExecutionListener from './property/ExecutionListener.vue';
 import { Modeler, ModdleElement } from 'bpmn';
 import { SequenceFlowPanel } from 'bpmnDesign';
-import useModelerStore from '@/store/modules/modeler';
 
 interface PropType {
   element: ModdleElement;

+ 3 - 2
src/components/BpmnDesign/panel/StartEndPanel.vue → src/bpmn/panel/StartEndPanel.vue

@@ -39,8 +39,9 @@
   </div>
 </template>
 <script setup lang="ts">
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import ExecutionListener from './property/ExecutionListener.vue';
+import useParseElement from '../hooks/useParseElement';
+import usePanel from '../hooks/usePanel';
 import { Modeler, ModdleElement } from 'bpmn';
 import { StartEndPanel } from 'bpmnDesign';
 

+ 3 - 2
src/components/BpmnDesign/panel/SubProcessPanel.vue → src/bpmn/panel/SubProcessPanel.vue

@@ -108,8 +108,9 @@
   </div>
 </template>
 <script setup lang="ts">
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import ExecutionListener from './property/ExecutionListener.vue';
+import useParseElement from '../hooks/useParseElement';
+import usePanel from '../hooks/usePanel';
 import { ModdleElement } from 'bpmn';
 import { SubProcessPanel } from 'bpmnDesign';
 import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';

+ 16 - 9
src/components/BpmnDesign/panel/TaskPanel.vue → src/bpmn/panel/TaskPanel.vue

@@ -21,9 +21,14 @@
             <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 prop="formKey" label="表单地址" v-loading="formManageListLoading">
-              <el-select @change="formKeyChange" v-model="formData.formKey" clearable filterable placeholder="请选择表单"  style="width: 260px" >
-                <el-option  v-for="item in formManageList"  :key="item.id"  :label="item.formTypeName+':'+item.formName" :value="item.formType+':'+item.id" />
+            <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>
@@ -231,11 +236,13 @@
   </div>
 </template>
 <script setup lang="ts">
-import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import useParseElement from '../hooks/useParseElement';
+import usePanel from '../hooks/usePanel';
 import UserSelect from '@/components/UserSelect';
 import RoleSelect from '@/components/RoleSelect';
-import DueDate from '@/components/BpmnDesign/panel/property/DueDate.vue';
+import ExecutionListener from './property/ExecutionListener.vue';
+import TaskListener from './property/TaskListener.vue';
+import DueDate from './property/DueDate.vue';
 import { ModdleElement } from 'bpmn';
 import { TaskPanel } from 'bpmnDesign';
 import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums';
@@ -464,11 +471,11 @@ const SpecifyDesc = [
 ];
 
 const listFormManage = async () => {
-  formManageListLoading.value = true
+  formManageListLoading.value = true;
   const res = await selectListFormManage();
   formManageList.value = res.data;
-  formManageListLoading.value = false
-}
+  formManageListLoading.value = false;
+};
 onMounted(() => {
   nextTick(() => {
     listFormManage();

+ 0 - 0
src/components/BpmnDesign/panel/index.vue → src/bpmn/panel/index.vue


+ 0 - 0
src/components/BpmnDesign/panel/property/DueDate.vue → src/bpmn/panel/property/DueDate.vue


+ 5 - 2
src/components/BpmnDesign/panel/property/ExecutionListener.vue → src/bpmn/panel/property/ExecutionListener.vue

@@ -66,7 +66,10 @@
             <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-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>
@@ -90,7 +93,7 @@ import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
 import { ExecutionListenerVO } from 'bpmnDesign';
 import { Moddle, Modeler, ModdleElement } from 'bpmn';
 
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import usePanel from '../../hooks/usePanel';
 import useDialog from '@/hooks/useDialog';
 import useModelerStore from '@/store/modules/modeler';
 

+ 0 - 0
src/components/BpmnDesign/panel/property/ListenerParam.vue → src/bpmn/panel/property/ListenerParam.vue


+ 5 - 2
src/components/BpmnDesign/panel/property/TaskListener.vue → src/bpmn/panel/property/TaskListener.vue

@@ -67,7 +67,10 @@
             <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-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>
@@ -91,7 +94,7 @@ import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
 import { TaskListenerVO } from 'bpmnDesign';
 import { ModdleElement } from 'bpmn';
 
-import usePanel from '@/components/BpmnDesign/hooks/usePanel';
+import usePanel from '../../hooks/usePanel';
 import useDialog from '@/hooks/useDialog';
 import useModelerStore from '@/store/modules/modeler';
 

+ 47 - 474
src/components/BpmnDesign/index.vue

@@ -1,498 +1,71 @@
 <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-button size="small" type="primary" @click="saveXml">保 存</el-button>
-                <el-dropdown size="small">
-                  <el-button size="small" type="primary"> 预 览 </el-button>
-                  <template #dropdown>
-                    <el-dropdown-menu>
-                      <el-dropdown-item icon="Document" @click="previewXML">XML预览</el-dropdown-item>
-                      <el-dropdown-item icon="View" @click="previewSVG"> SVG预览</el-dropdown-item>
-                    </el-dropdown-menu>
-                  </template>
-                </el-dropdown>
-
-                <el-dropdown size="small">
-                  <el-button size="small" type="primary"> 下 载 </el-button>
-                  <template #dropdown>
-                    <el-dropdown-menu>
-                      <el-dropdown-item icon="Download" @click="downloadXML">下载XML</el-dropdown-item>
-                      <el-dropdown-item icon="Download" @click="downloadSVG"> 下载SVG</el-dropdown-item>
-                    </el-dropdown-menu>
-                  </template>
-                </el-dropdown>
-                <el-tooltip effect="dark" content="新建" placement="bottom">
-                  <el-button size="small" icon="CirclePlus" @click="newDiagram" />
-                </el-tooltip>
-                <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
-                  <el-button size="small" icon="Rank" @click="fitViewport" />
-                </el-tooltip>
-                <el-tooltip effect="dark" content="放大" placement="bottom">
-                  <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
-                </el-tooltip>
-                <el-tooltip effect="dark" content="缩小" placement="bottom">
-                  <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
-                </el-tooltip>
-                <el-tooltip effect="dark" content="后退" placement="bottom">
-                  <el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" />
-                </el-tooltip>
-                <el-tooltip effect="dark" content="前进" placement="bottom">
-                  <el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" />
-                </el-tooltip>
-              </el-space>
-            </div>
-          </el-header>
-          <div ref="canvas" class="canvas" />
-        </el-container>
-        <div :class="{ 'process-panel': true, 'hide': panelFlag }">
-          <div class="process-panel-bar" @click="panelBarClick">
-            <div class="open-bar">
-              <el-link type="default" :underline="false">
-                <svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon>
-              </el-link>
-            </div>
-          </div>
-          <transition enter-active-class="animate__animated animate__fadeIn">
-            <div v-show="showPanel" v-if="bpmnModeler" class="panel-content">
-              <PropertyPanel :modeler="bpmnModeler" />
-            </div>
-          </transition>
-        </div>
-      </el-container>
-    </div>
-  </div>
-  <div>
-    <el-dialog v-model="perviewXMLShow" title="XML预览" width="80%" append-to-body>
-      <highlightjs :code="xmlStr" language="XML" />
-    </el-dialog>
-  </div>
-  <div>
-    <el-dialog v-model="perviewSVGShow" title="SVG预览" width="80%" append-to-body>
-      <div style="text-align: center" v-html="svgData" />
+  <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="BpmnDesign">
-import 'bpmn-js/dist/assets/diagram-js.css';
-import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
-import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
-import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
-import './assets/style/index.scss';
-import { Canvas, Modeler } from 'bpmn';
-import PropertyPanel from './panel/index.vue';
-import BpmnModeler from 'bpmn-js/lib/Modeler.js';
-import defaultXML from '@/components/BpmnDesign/assets/defaultXML';
-import flowableModdle from '@/components/BpmnDesign/assets/moddle/flowable';
-import Modules from './assets/module/index';
-import useModelerStore from '@/store/modules/modeler';
-import useDialog from '@/hooks/useDialog';
+<script lang="ts" setup name="Design">
+import { getInfo, editModelXml } from '@/api/workflow/model';
 
-const emit = defineEmits(['closeCallBack', 'saveCallBack']);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
-const { visible, title, openDialog, closeDialog } = useDialog({
+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 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
+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;
 };
 
-/**
- * 初始化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
+  open
 });
 </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;
+<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;
   }
-
-  /** 元素相关设置 */
-  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;
-    }
+  :deep(.el-dialog__header) {
+    padding: 0 0 5px 0 !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: #f5f7fa;
-        }
-      }
-    }
-  }
-}
-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 #eeeeee;
-  box-shadow: #cccccc 0 0 8px;
-  max-height: 100%;
-  width: 25%;
-  height: calc(100vh - 80px);
-  .el-collapse {
-    height: calc(100vh - 162px);
-    overflow: auto;
-  }
-}
-
-// 任务栏 透明度
-//:deep(.djs-palette) {
-//  opacity: 0.3;
-//  transition: all 1s;
-//}
-//
-//:deep(.djs-palette:hover) {
-//  opacity: 1;
-//  transition: all 1s;
-//}
 </style>

+ 0 - 67
src/views/workflow/model/design.vue

@@ -1,67 +0,0 @@
-<template>
-  <div class="design">
-    <el-dialog v-model="visible" width="100%" fullscreen :title="title">
-      <div class="modeler">
-        <bpmn-design ref="bpmnDesignRef" @save-call-back="saveCallBack"></bpmn-design>
-      </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script lang="ts" setup name="Design">
-import { getInfo, editModelXml } from '@/api/workflow/model';
-
-const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-
-import { ModelForm } from '@/api/workflow/model/types';
-import BpmnDesign from '@/components/BpmnDesign';
-import useDialog from '@/hooks/useDialog';
-const bpmnDesignRef = ref<InstanceType<typeof BpmnDesign>>();
-const modelForm = ref<ModelForm>();
-const emit = defineEmits(['closeCallBack']);
-const { visible, title } = useDialog({
-  title: '编辑流程'
-});
-const modelId = ref('');
-const open = async (id) => {
-  visible.value = true;
-  modelId.value = id;
-  const { data } = await getInfo(id);
-  modelForm.value = data;
-  bpmnDesignRef.value.initDiagram(modelForm.value.xml);
-};
-//保存模型
-const saveCallBack = async (data) => {
-  await proxy?.$modal.confirm('是否确认保存?');
-  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 - 50px);
-  }
-}
-</style>

+ 1 - 1
src/views/workflow/model/index.vue

@@ -138,7 +138,7 @@
 </template>
 
 <script lang="ts" setup name="Model">
-import Design from './design.vue';
+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';