Browse Source

Merge branch 'lrh' of yinet2020/YinetOASystemWeb into master

吕瑞涵 4 years ago
parent
commit
f36afdbdab

+ 3 - 1
package.json

@@ -42,6 +42,8 @@
   "dependencies": {
     "@riophae/vue-treeselect": "0.4.0",
     "axios": "0.18.1",
+    "bpmn-js": "7.2.1",
+    "bpmn-moddle": "7.0.3",
     "clipboard": "2.0.4",
     "echarts": "4.2.1",
     "element-ui": "2.13.0",
@@ -50,7 +52,7 @@
     "js-beautify": "^1.10.2",
     "js-cookie": "2.2.0",
     "jsencrypt": "3.0.0-rc.1",
-    "less-loader": "^6.2.0",
+    "less-loader": "6.2.0",
     "normalize.css": "7.0.0",
     "nprogress": "0.2.0",
     "path-to-regexp": "2.4.0",

+ 67 - 0
src/api/activiti/modeler/design.js

@@ -0,0 +1,67 @@
+/**
+ * 存储流程设计相关参数
+ */
+export default class BpmData {
+  constructor() {
+    this.controls = [] // 设计器控件
+    this.init()
+  }
+
+  init() {
+    this.controls = [
+      {
+        action: 'create.start-event',
+        title: '开始'
+      },
+      {
+        action: 'create.intermediate-event',
+        title: '中间'
+      },
+      {
+        action: 'create.end-event',
+        title: '结束'
+      },
+      {
+        action: 'create.exclusive-gateway',
+        title: '网关'
+      },
+      {
+        action: 'create.task',
+        title: '任务'
+      },
+      {
+        action: 'create.user-task',
+        title: '用户任务'
+      },
+      {
+        action: 'create.user-sign-task',
+        title: '会签任务'
+      },
+      {
+        action: 'create.subprocess-expanded',
+        title: '子流程'
+      },
+      {
+        action: 'create.data-object',
+        title: '数据对象'
+      },
+      {
+        action: 'create.data-store',
+        title: '数据存储'
+      }, {
+        action: 'create.participant-expanded',
+        title: '扩展流程'
+      },
+      {
+        action: 'create.group',
+        title: '分组'
+      }
+    ]
+  }
+
+  //  获取控件配置信息
+  getControl(action) {
+    const result = this.controls.filter(item => item.action === action)
+    return result[0] || {}
+  }
+}

+ 13 - 0
src/router/index.js

@@ -143,6 +143,19 @@ export const constantRoutes = [
       }
 
     ]
+  },
+  {
+    path: '/activiti/modeler/design',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'index',
+        component: (resolve) => require(['@/views/activiti/modeler/design/index'], resolve),
+        name: 'DesignProcess',
+        meta: { title: '设计流程' }
+      }
+    ]
   }
 
 ]

+ 186 - 0
src/views/activiti/modeler/design/index.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="containers">
+    <div class="canvas" ref="canvas"/>
+    <panel v-if="bpmnModeler" :modeler="bpmnModeler"/>
+    <div class="toolbar">
+      <a title="download">下载</a>
+      <a ref="saveDiagram" href="javascript:" title="download BPMN diagram">BPMN</a>
+      <a ref="saveSvg" href="javascript:" title="download as SVG image">SVG</a>
+    </div>
+  </div>
+</template>
+
+<script>
+  import BpmnModeler from 'bpmn-js/lib/Modeler'
+  import panel from './property'
+  // import BpmData from './BpmData'
+  import BpmData from '@/api/activiti/modeler/design'
+
+  export default {
+    name: 'Page401',
+    data() {
+      return {
+        bpmnModeler: null,
+        element: null,
+        bpmData: new BpmData()
+      }
+    },
+    components: {
+      panel
+    },
+    methods: {
+      createNewDiagram() {
+        const bpmnXmlStr = ''
+        // 将字符串转换成图显示出来
+        this.bpmnModeler.importXML(bpmnXmlStr, err => {
+          if (err) {
+            console.error(err)
+          } else {
+            this.adjustPalette()
+          }
+        })
+      },
+      // 调整左侧工具栏排版
+      adjustPalette() {
+        try {
+          // 获取 bpmn 设计器实例
+          const canvas = this.$refs.canvas
+          const djsPalette = canvas.children[0].children[1].children[4]
+          const djsPalStyle = {
+            width: '130px',
+            padding: '5px',
+            background: 'white',
+            left: '20px',
+            borderRadius: 0
+          }
+          for (var key in djsPalStyle) {
+            djsPalette.style[key] = djsPalStyle[key]
+          }
+          const palette = djsPalette.children[0]
+          const allGroups = palette.children
+          allGroups[0].style['display'] = 'none'
+          // 修改控件样式
+          for (var gKey in allGroups) {
+            const group = allGroups[gKey]
+            for (var cKey in group.children) {
+              const control = group.children[cKey]
+              const controlStyle = {
+                display: 'flex',
+                justifyContent: 'flex-start',
+                alignItems: 'center',
+                width: '100%',
+                padding: '5px'
+              }
+              if (
+                control.className &&
+                control.dataset &&
+                control.className.indexOf('entry') !== -1
+              ) {
+                const controlProps = this.bpmData.getControl(
+                  control.dataset.action
+                )
+                control.innerHTML = `<div style='font-size: 14px;font-weight:500;margin-left:15px;'>${
+                  controlProps['title']
+                }</div>`
+                for (var csKey in controlStyle) {
+                  control.style[csKey] = controlStyle[csKey]
+                }
+              }
+            }
+          }
+        } catch (e) {
+          console.log(e)
+        }
+      },
+      // 下载为SVG格式,done是个函数,调用的时候传入的
+      saveSVG(done) {
+        // 把传入的done再传给bpmn原型的saveSVG函数调用
+        this.bpmnModeler.saveSVG(done)
+      },
+      // 下载为SVG格式,done是个函数,调用的时候传入的
+      saveDiagram(done) {
+        // 把传入的done再传给bpmn原型的saveXML函数调用
+        this.bpmnModeler.saveXML({ format: true }, function(err, xml) {
+          done(err, xml)
+        })
+      },
+      // 当图发生改变的时候会调用这个函数,这个data就是图的xml
+      setEncoded(link, name, data) {
+        // 把xml转换为URI,下载要用到的
+        const encodedData = encodeURIComponent(data)
+        // 获取到图的xml,保存就是把这个xml提交给后台
+        this.xmlStr = data
+        // 下载图的具体操作,改变a的属性,className令a标签可点击,href令能下载,download是下载的文件的名字
+        if (data) {
+          link.className = 'active'
+          link.href = 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData
+          link.download = name
+        }
+      }
+    },
+    mounted() {
+      const canvas = this.$refs.canvas
+      // 生成实例
+      this.bpmnModeler = new BpmnModeler({
+        container: canvas
+      })
+      // 获取a标签dom节点
+      const downloadLink = this.$refs.saveDiagram
+      const downloadSvgLink = this.$refs.saveSvg
+      // 监听流程图改变事件
+      const _this = this
+      this.bpmnModeler.on('commandStack.changed', function() {
+        _this.saveSVG(function(err, svg) {
+          _this.setEncoded(downloadSvgLink, 'diagram.svg', err ? null : svg)
+        })
+        _this.saveDiagram(function(err, xml) {
+          _this.setEncoded(downloadLink, 'diagram.bpmn', err ? null : xml)
+        })
+      })
+      // 新增流程定义
+      this.createNewDiagram()
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  @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";
+
+  .containers {
+    position: absolute;
+    background-color: #ffffff;
+    width: 100%;
+    height: 100%;
+
+    .canvas {
+      width: 100%;
+      height: 100%;
+    }
+
+    .panel {
+      position: absolute;
+      right: 0;
+      top: 50px;
+      width: 300px;
+    }
+
+    .bjs-powered-by {
+      display: none;
+    }
+
+    .toolbar {
+      position: absolute;
+      top: 20px;
+      right: 350px;
+
+      a {
+        text-decoration: none;
+        margin: 5px;
+        color: #409eff;
+      }
+    }
+  }
+</style>

+ 226 - 0
src/views/activiti/modeler/design/property.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="property-panel" ref="propertyPanel">
+    <el-form :inline="true" :model="form" label-width="100px" size="small">
+      <el-form-item label="节点ID">
+        <el-input v-model="form.id" disabled></el-input>
+      </el-form-item>
+      <el-form-item label="节点名称">
+        <el-input v-model="form.name" @input="nameChange"></el-input>
+      </el-form-item>
+      <el-form-item label="节点颜色">
+        <el-color-picker v-model="form.color" @active-change="colorChange"></el-color-picker>
+      </el-form-item>
+      <!-- 任务节点允许选择人员 -->
+      <el-form-item label="节点人员" v-if="userTask">
+        <el-select v-model="form.userType" placeholder="请选择" @change="typeChange">
+          <el-option value="assignee" label="指定人员"></el-option>
+          <el-option value="candidateUsers" label="候选人员"></el-option>
+          <el-option value="candidateGroups" label="角色/岗位"></el-option>
+        </el-select>
+      </el-form-item>
+      <!-- 指定人员 -->
+      <el-form-item label="指定人员" v-if="userTask && form.userType === 'assignee'">
+        <el-select
+          v-model="form.assignee"
+          placeholder="请选择"
+          key="1"
+          @change="(value) => addUser({assignee: value})"
+        >
+          <el-option
+            v-for="item in users"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <!-- 候选人员 -->
+      <el-form-item label="候选人员" v-else-if="userTask && form.userType === 'candidateUsers'">
+        <el-select
+          v-model="form.candidateUsers"
+          placeholder="请选择"
+          key="2"
+          multiple
+          @change="(value) => addUser({candidateUsers: value.join(',') || value})"
+        >
+          <el-option
+            v-for="item in users"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <!-- 角色/岗位 -->
+      <el-form-item label="角色/岗位" v-else-if="userTask && form.userType === 'candidateGroups'">
+        <el-select
+          v-model="form.candidateGroups"
+          placeholder="请选择"
+          @change="(value) => addUser({candidateGroups: value})"
+        >
+          <el-option
+            v-for="item in roles"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <!-- 分支允许添加条件 -->
+      <el-form-item label="分支条件" v-if="sequenceFlow">
+        <el-select v-model="form.user" placeholder="请选择">
+          <el-option
+            v-for="item in users"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "PropertyPanel",
+  props: {
+    modeler: {
+      type: Object,
+      required: true
+    }
+  },
+  computed: {
+    userTask() {
+      if (!this.element) {
+        return;
+      }
+      return this.element.type === "bpmn:UserTask";
+    },
+    sequenceFlow() {
+      if (!this.element) {
+        return;
+      }
+      return this.element.type === "bpmn:SequenceFlow";
+    }
+  },
+  data() {
+    return {
+      form: {
+        id: "",
+        name: "",
+        color: null
+      },
+      element: {},
+      users: [
+        {
+          value: "zhangsan",
+          label: "张三"
+        },
+        {
+          value: "lisi",
+          label: "李四"
+        },
+        {
+          value: "wangwu",
+          label: "王五"
+        }
+      ],
+      roles: [
+        {
+          value: "manager",
+          label: "经理"
+        },
+        {
+          value: "personnel",
+          label: "人事"
+        },
+        {
+          value: "charge",
+          label: "主管"
+        }
+      ]
+    };
+  },
+  mounted() {
+    this.handleModeler();
+  },
+  methods: {
+    handleModeler() {
+      // 监听节点选择变化
+      this.modeler.on("selection.changed", e => {
+        const element = e.newSelection[0];
+        this.element = element;
+        console.log(this.element);
+        if (!element) return;
+        this.form = {
+          ...element.businessObject,
+          ...element.businessObject.$attrs
+        };
+        if (this.form.userType === "candidateUsers") {
+          this.form["candidateUsers"] =
+            this.form["candidateUsers"].split(",") || [];
+        }
+      });
+
+      //  监听节点属性变化
+      this.modeler.on("element.changed", e => {
+        const { element } = e;
+        if (!element) return;
+        //  新增节点需要更新回属性面板
+        if (element.id === this.form.id) {
+          this.form.name = element.businessObject.name;
+          this.form = { ...this.form };
+        }
+      });
+    },
+    // 属性面板名称,更新回流程节点
+    nameChange(name) {
+      const modeling = this.modeler.get("modeling");
+      modeling.updateLabel(this.element, name);
+    },
+    // 属性面板颜色,更新回流程节点
+    colorChange(color) {
+      const modeling = this.modeler.get("modeling");
+      modeling.setColor(this.element, {
+        fill: null,
+        stroke: color
+      });
+      modeling.updateProperties(this.element, { color: color });
+    },
+    // 任务节点配置人员
+    addUser(properties) {
+      this.updateProperties(
+        Object.assign(properties, {
+          userType: Object.keys(properties)[0]
+        })
+      );
+    },
+    // 切换人员类型
+    typeChange() {
+      const types = ["assignee", "candidateUsers", "candidateGroups"];
+      types.forEach(type => {
+        delete this.element.businessObject.$attrs[type];
+        delete this.form[type];
+      });
+    },
+    // 在这里我们封装一个通用的更新节点属性的方法
+    updateProperties(properties) {
+      const modeling = this.modeler.get("modeling");
+      modeling.updateProperties(this.element, properties);
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.property-panel {
+  position: absolute;
+  right: 0px;
+  top: 0px;
+  border-left: 1px solid #cccccc;
+  padding: 20px 0;
+  width: 300px;
+  height: 100%;
+}
+</style>

+ 2 - 0
src/views/activiti/modeler/index.vue

@@ -204,6 +204,7 @@
       /** 编辑按钮操作 */
       handleUpdate(row) {
         this.editOpen = true
+        // this.src = '/activiti/modeler/design/index'
         this.src = 'http://192.168.1.20:8080/modeleredit/modeler.html?modelId=' + row.id
       },
       /** 提交按钮 */
@@ -216,6 +217,7 @@
             setTimeout(() => {
               this.editOpen = true
               this.src = 'http://192.168.1.20:8080/modeleredit/modeler.html?modelId=' + response.data
+              // this.src = '/activiti/modeler/design/index'
             }, 1 * 700)
           }
         })