精华内容
下载资源
问答
  • vue工作流编辑器 Vue块 (Vue-Blocks) Vue2 dataflow graph editor. Vue2数据流图编辑器。 View demo 查看演示 Download Source 下载源 基本用法 (Basic usage) <template> <div id="app"> <...

    vue工作流编辑器

    Vue块 (Vue-Blocks)

    Vue2 dataflow graph editor.

    Vue2数据流图编辑器。

    基本用法 (Basic usage)

    <template>
      <div id="app">
        <VueBlocksContainer
          ref="container"
          :blocksContent="blocks"
          :scene.sync="scene"
          class="container"/>
      </div>
    </template>
    
    <script>
      import VueBlocksContainer from '...path.../vue-blocks/src'
    
      export default {
        name: 'App',
        components: {
          VueBlocksContainer
        },
        data: function () {
          return {
            blocks: [
              {
                name: 'test',
                title: 'Test block',
                family: 'Test',
                description: 'Description text',
                fields: [
                  {
                    name: 'in1',
                    type: 'event',
                    attr: 'input'
                  },
                  {
                    name: 'in2',
                    type: 'event',
                    attr: 'input'
                  },
                  {
                    name: 'out1',
                    type: 'event',
                    attr: 'output'
                  },
                  {
                    name: 'out2',
                    type: 'event',
                    attr: 'output'
                  }
                ]
              }
            ],
            scene: {
              blocks: [
                {
                  id: 1,
                  x: 0,
                  y: 0,
                  name: 'test',
                  title: 'Test block',
                  values: {
                    property: [
                      {
                        name: 'message',
                        type: 'string'
                      }
                    ]
                  }
                },
                {
                  id: 2,
                  x: 0,
                  y: 50,
                  name: 'test',
                  title: 'Test block 2',
                  values: {
                    property: [
                      {
                        name: 'message',
                        type: 'string'
                      }
                    ]
                  }
                }
              ],
              links: [],
              container: {
                centerX: 0,
                centerY: 0,
                scale: 1
              }
            }
          }
        }
      }
    </script>
    
    
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
    
      html {
        width: 100vw;
        height: 100vh;
      }
    
      body,
      #app,
      .container {
        width: 100%;
        height: 100%;
      }
    </style>

    道具 (Props)

    blocksContent (blocksContent)

    Type: Array.<Node> Required: true Default: []

    类型: Array.<Node>必需: true默认值: []

    Object Node:

    对象Node

    {
      name: 'name-node',
      title: 'Title node',
      family: 'family - just for grouping',
      description: 'Description text',
      fields: Array.<NodeField>
    }

    Object NodeField:

    对象NodeField

    {
      name: 'name',
      type: 'type-name', // not used
      attr: 'attribute' // input/output or custom
      'other': - for custom
    }

    Custom attributes are available in scene.blocks[index].values.YourAttrName

    自定义属性可在scene.blocks[index].values.YourAttrName

    现场 (scene)

    Type: Object Required: false Default:

    类型: Object必需: false默认值:

    {
      blocks: [],
      links: [],
      container: {}
    }

    Object Scene:

    对象Scene

    {
      blocks: Array.<Block>,
      links: Array.<BlockLinks>,
      container: {
       centerX: number
       centerY: number
       scale: number
      }
    }

    Object Block:

    对象Block

    {
      id: number,
      x: number,
      y: number,
      name: string,
      title: string,
      values: {
        customAttribute: [ // show "NodeField"
          name: NodeField (without name and attr fields)
        ]
      }
    }

    Object BlockLinks:

    对象BlockLinks

    {
      id: number, // ID
      originID: number, // Origin block ID
      originSlot: number, // Origin block slot number
      targetID: number, // Target block ID
      targetSlot: number // Target block slot number
    }

    构建设置 (Build Setup)

    # install dependencies
    yarn
    
    # serve with hot reload at localhost:8080
    yarn dev
    
    # build for production with minification
    yarn run build
    
    # build for production and view the bundle analyzer report
    yarn run build --report
    
    # run unit tests
    yarn run unit
    
    # run all tests
    yarn test

    翻译自: https://vuejsexamples.com/vue2-dataflow-graph-editor/

    vue工作流编辑器

    展开全文
  • 基于vue和jsplumb的工作流编辑器开发

    千次阅读 2020-04-29 16:23:29
    需要实现一个工作流,支持拖拽节点生成工作流。 业务实现 支持页面布局缩放 支持节点 支持if else 支持多分支 技术点 网格背景 工作流缩放 工作流技术实现 节点拖拽 技术选型 vue jsplumb sortablejs(vue-...

    背景

    需要实现一个工作流,支持拖拽节点生成工作流。

    业务实现

    • 支持页面布局缩放
    • 支持节点
    • 支持if else
    • 支持多分支

    技术点

    • 网格背景
    • 工作流缩放
    • 工作流技术实现
    • 节点拖拽

    技术选型

    • vue
    • jsplumb
    • sortablejs(vue-draggable)

    难点攻破

    网格背景

    主要是利用css的 linear-gradientbackground-size 实现的。

    <div class="flow-layout">
        <div class="flow-editor">
            <div class="canvas-container">
            </div>
        </div>
    </div>
    
    .flow-layout {
      display: flex;
      flex-direction: column;
    }
    
    .flow-editor {
      position: relative;
      display: flex;
      flex-direction: row;
      flex: 1;
      overflow: hidden;
    }
    
    .canvas-container {
      flex: 1;
      overflow: auto;
      z-index: 0;
    }
    

    在这里插入图片描述

    .canvas-container:before {
      content: "";
      height: 10px;
      width: 100%;
      display: block;
      background-repeat-y: no-repeat;
      position: absolute;
      background-image: linear-gradient(90deg, #ccc 1px, transparent 0), linear-gradient(90deg, #ddd 1px, transparent 0);
      background-size: 75px 10px, 5px 5px;
    }
    
    .canvas-container:after {
      content: "";
      height: 100%;
      width: 10px;
      display: block;
      background-repeat-x: no-repeat;
      position: absolute;
      top: 0;
      background-image: linear-gradient(#ccc 1px, transparent 0), linear-gradient(#ddd 1px, transparent 0);
      background-size: 10px 75px, 5px 5px;
    }
    

    工作流缩放

    主要结合 css的 属性选择符 E[att="val"] ,通过修改zoom的值,来实现缩放功能。

    在这里插入图片描述

    <div class="flow-zoom" :data-zoom="canvasDataRoom + '%'">
        <div class="zoom-btn">
            <el-button size="mini" :class="{'el-button--primary':canvasRoomMinusEnable}" icon="el-icon-minus"
                       circle
                       @click="handleMinusCanvas"></el-button>
        </div>
        <div class="zoom-btn">
            <el-button size="mini" :class="{'el-button--primary':canvasRoomPlusEnable}" icon="el-icon-plus"
                       circle
                       @click="handlePlusCanvas"></el-button>
        </div>
    </div>
    
    
    <div class="canvas-container" :data-zoom="canvasDataRoom">
        <div class="campaignCanvas"></div>
    </div>
    
    .canvas-container[data-zoom="100"] {
      background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
      background-size: 75px 75px, 75px 75px, 15px 15px, 15px 15px;
    }
    
    .canvas-container[data-zoom="90"] {
      background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
      background-size: 70px 70px, 70px 70px, 14px 14px, 14px 14px;
    }
    
    .canvas-container[data-zoom="80"] {
      background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
      background-size: 60px 60px, 60px 60px, 12px 12px, 12px 12px;
    }
    
    .canvas-container[data-zoom="70"] {
      background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
      background-size: 55px 55px, 55px 55px, 11px 11px, 11px 11px;
    }
    
    .canvas-container[data-zoom="60"] {
      background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
      background-size: 45px 45px, 45px 45px, 9px 9px, 9px 9px;
    }
    
    .canvas-container[data-zoom="50"] {
      background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
      background-size: 40px 40px, 40px 40px, 8px 8px, 8px 8px;
    }
    
    

    工作流技术实现

    主要是依赖 jsplumb 实现的。

    主要利用 jsplumb 实现两个节点的连接。

    让dom节点变成jsplumb 可拖拽节点

    <div id="_uuid">
    </div>
    
    jsPlumb.draggable(_uuid, {});
    

    两个节点的连接。

    jsPlumb.connect({
        source: source,
        target: target,
        endpoint: 'Dot',
        // 连接线的样式
        connectorStyle: {strokeStyle: "#ccc", joinStyle: "round", outlineColor: "#ccc"}, // 链接 style
        // 连接线配置,起点可用
        connector: ["Flowchart", {
            stub: [10, 20],
            gap: 1,
            cornerRadius: 2,
            alwaysRespectStubs: true
        }], //  链接
        //
        endpointStyle: {fill: 'transparent', outlineStroke: 'transparent', outlineWidth: 2},
        // 线的样式
        paintStyle: {stroke: 'lightgray', strokeWidth: 2},
        // 锚点的位置
        anchor: ['BottomCenter', 'TopCenter'],
        // 遮罩层-设置箭头
        overlays: [
            ['PlainArrow', {width: 10, length: 10, location: 1}],
            ['Custom', {
                location: .5,
                id: 'nodeTempSmall',
                create: function () {
                    let $el = that.$refs[target][0].$el;
                    $el.dataset.target = target;
                    $el.dataset.source = source;
                    return $el;
                },
                visible: false
            }],
            ['Label', {location: 1, id: "flowItemDesc", cssClass: "node-item-label", visible: true}] //
        ]
    });
    

    删除一个节点

    jsPlumb.removeAllEndpoints(uuid);
    

    两个节点之间创建节点

    在这里插入图片描述

     function createFlowConnectionLabel(sourceList, target) {
    
        if (!Array.isArray(sourceList)) {
            sourceList = [sourceList];
        }
    
        sourceList.forEach((source) => {
            //
            let lines = this.$options.jsPlumb.getConnections({
                source: source,
                target: target
            });
            //
            lines.forEach((line) => {
                line.getOverlay('nodeTempSmall').setVisible(true);
                line.bind('click', this.handleFlowLabelClick);
            });
        });
    }
    

    两个节点之间的文案创建

    在这里插入图片描述

     function createFlowItemLabel(source, target, label) {
        this.$nextTick(() => {
            let lines = this.$options.jsPlumb.getConnections({
                source: source,
                target: target
            });
            if (lines.length > 0) {
                lines[0].getOverlay("flowItemDesc").setLabel(`<span class="node-item-title" title="${label}">${label}</span>`);
            }
        });
    }
    

    节点拖拽

    主要是依赖 sortablejs 实现的

    主要利用vue-draggable 封装好的组件,来实现拖拽。 核心代码

    拖拽的目的地区域。

    <draggable class="flow-item node-temp node-temp-img"
               ref="tempNode"
               :id="flowItem.uuid"
               :group="{name:'sortable', pull:false, put: true }">
    </draggable>
    

    被拖拽的目标对象。

    <draggable class="items-box"
               :key="index"
               :list="flowItem.children"
               :group="{name:'sortable', pull: 'clone', put: false }"
               v-bind="dragConfig"
               :move="handleFlowMoveItem"
               @start="handleFlowMoveStart"
               @end="handleFlowMoveEnd"
               :sort="false"
               :ref="flowItem.ref">
            <div class="node-temp-img"></div>
        </template>
    </draggable>
    

    项目截图

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    项目地址

    github: https://github.com/bosscheng/vue-draggable-workflow

    demo: https://bosscheng.github.io/vue-draggable-workflow

    展开全文
  • 本文是对 ‘基于vue和jsplumb的工作流编辑器开发’ 的扩展 业务实现 撤销 初始化数据 自动排列 清空数据 撤销 对于撤销的实现,主要是需要一个缓冲内存,存储每次操作之后的数据结构,方便再点击撤销按钮的时候,...

    背景

    本文是对 ‘基于vue和jsplumb的工作流编辑器开发’ 的扩展

    业务实现

    • 撤销
    • 初始化数据
    • 自动排列
    • 清空数据

    撤销

    对于撤销的实现,主要是需要一个缓冲内存,存储每次操作之后的数据结构,方便再点击撤销按钮的时候,从缓冲内存中拿出数据结构来渲染页面。

    利用数组来存储操作之后的数据结构。

    let MEMORY_LIST = [];
    

    这里只缓存10次操作。

    
    $_updateMemoryList() {
        <!--格式化数据结构-->
        const tempItem = this.formatData();
    
        // max store is 10
        if (MEMORY_LIST.length > 10) {
            MEMORY_LIST.shift();
        }
        MEMORY_LIST.push(tempItem);
        // 更新按钮可操作状态。
        this.$_updateCanUndoBtn();
    }
    

    然后在各种操作,比如新增节点,拖拽节点的时候,调用$_updateMemoryList方法

    当点击撤销按钮的时候,

    handleUndo() {
        if (!this.canUndo) {
            return;
        }
    
        if (MEMORY_LIST.length > 0) {
            const tempItem = MEMORY_LIST.pop();
            this.$_doClear();
            this.$options.jsPlumb.reset();
            this.$nextTick(() => {
                this.updateFlow(tempItem, this.$_plumbRepaintEverything);
            })
        }
    
        this.$_updateCanUndoBtn();
    },
    

    直接从缓冲内存里面读取数据结构,重新渲染流程图。更新按钮状态。

    初始化数据

    对于初始化渲染的数据结构

    {
        positions:{
            "key":{
                left:'xx',
                top:'xx'
            }
        },
        steps:[
            {
                elementId:'startNode',
                stepId:'uuid',
                nextStep:'uuid'
            },
            {
                "elementId": "switchNode",
                "stepId": "c89829f5-8595-458c-b040-4ff84d27befc",
                "nextSteps": [
                    {
                        "nextStep": "d611c32f-b6c0-4b97-80d9-47b783bd93ad",
                    },
                    {
                        "nextStep": "7bd4fc3d-c3b9-4b19-81dc-e49cd1e7b5c5",
                    },
                    {
                        "nextStep": "85c30556-75fa-441c-9a1d-0dced21755a5",
                    }
                ],
            },
            {
                "elementId": "stopNode",
                "stepId": "d611c32f-b6c0-4b97-80d9-47b783bd93ad",
                "nextStep": null
            },
        ]
    }
    

    通过这样的数据结构,然后执行渲染方法updateFlow

    updateFlow(editItem, callback) {
        let positions = JSON.parse(editItem.positions);
        let steps = editItem.steps;
        let flowList = [];
    
        steps.forEach((step) => {
            let flowItem = this.getFlowItemById(step.elementId);
    
            if (!flowItem) {
                return;
            }
            flowItem.next = [];
            flowItem.prev = [];
            flowItem.uuid = step.stepId;
    
            let position = positions[step.stepId];
    
            if (position) {
                flowItem.left = position.left;
                flowItem.top = position.top;
            }
    
            if (step.nextStep) {
                flowItem.next = [step.nextStep];
            } else if (step.nextSteps) {
                flowItem.next = step.nextSteps.map((nextStep) => {
                    return nextStep.nextStep;
                });
            }
    
            if (flowItem.type !== FLOW_ITEM_TYPE.endNode) {
                //
                if (this.isIfFlowItem(flowItem.type)) {
                    let formData = clone(step.nextSteps[0]);
                    formData.stepName = step.stepName;
                    flowItem.formData = this.getFlowItemFormData(formData);
                    // else
                    if (formData.isDefault) {
                        flowItem.nextElseId = formData.nextStep;
                        flowItem.nextIfId = step.nextSteps[1].nextStep;
                    } else {
                        flowItem.nextIfId = formData.nextStep;
                        flowItem.nextElseId = step.nextSteps[1].nextStep;
                    }
                    if (step.stepJson) {
                        let stepOtherObj = JSON.parse(step.stepJson);
                        flowItem.formData.ifNodeTitle = stepOtherObj.ifNodeTitle;
                    }
                } else if (this.isExpandFlowItem(flowItem.type)) {
                    let ruleGroupList = step.nextSteps;
                    let formData = {};
                    formData.stepName = step.stepName;
                    formData.ruleGroupList = ruleGroupList;
                    flowItem.formData = formData;
                } else {
                    flowItem.formData = this.getFlowItemFormData(step);
                }
            }
    
            flowList.push(flowItem);
        });
    
        // update
        flowList.forEach((item) => {
            if (item.next.length > 0) {
                item.next.forEach((id) => {
                    let nextItem = _.find(flowList, (tempItem) => {
                        return tempItem.uuid === id;
                    });
    
                    if (nextItem) {
                        if (nextItem.prev.indexOf(item.uuid) === -1) {
                            nextItem.prev.push(item.uuid);
                        }
                    }
                });
            }
        });
        this.flowList = flowList;
    
        this.$nextTick(() => {
            //
            flowList.forEach((item) => {
                this.$options.jsPlumb.draggable(item.uuid, {});
            });
    
            this.$nextTick(() => {
                flowList.forEach((item) => {
                    item.next.forEach((id, index) => {
                        let nextFlowItem = this.getFlow(id);
                        if (!this.isTempFlowItem(nextFlowItem)) {
                            this.addFlowItemConnect(item.uuid, id);
                        } else {
                            this.draggableFlowConnect(item.uuid, id, true);
                        }
                        //
                        if (this.isIfFlowItem(item.type)) {
                            let isIf = item.nextIfId === nextFlowItem.uuid;
                            this.createFlowItemLabel(item.uuid, id, isIf ? '是' : '否');
                        } else if (this.isExpandFlowItem(item.type)) {
                            let name = this.getExpandFlowItemName(item, id);
                            this.createFlowItemLabel(item.uuid, id, name);
                        }
                    });
                });
                this.$nextTick(() => {
                    callback && callback();
                })
            });
        });
    },
    
    

    自动排列

    对于自动排列的实现,主要是借鉴了 一种紧凑树形布局算法的实现的实现,写的非常棒,谢谢这位巨人,让我站在了他的肩膀上。

    大体的思路就是:

    1. 我们从上往下,先根据相邻节点间地最小间距 FLOW_LEFT_STEP_LENGTH对树进行第一次布局。先初始化根节点的位置 。从根节点开始。人为设定好根节点的坐标 ,然后将根节点的子节点挂在根节点下,且子节点分布在根节点的FLOW_STEP_LENGTH高度下方,子节点彼此间距为FLOW_LEFT_STEP_LENGTH且相对于根节点对称分布。递归进行此步骤,直到所有的节点都布局好。
    2. 然后,我们需要一个hashTree,用作将树保存到一个按层次分别的线性表中。我们将树转换到hashTree。效果图中的树对应hashTree如下:
    /**
     * layer [
     *   0  [ node(0) ], 
     *   1  [ node(1), node(2), node(3), node(4), node(5) ],
     *   2  [ node(6), node(7), node(8), node(9), node(10), node(11), node(12), node(13), node(14), node(15), node(16), node(17), node(18), node(19), node(20) ],
     *   3  [ node(21), node(22), node(23), node(24), node(25), node(26), node(27), node(28) ]
     * ]
     */
    
    1. 从最低层开始从下往上按层遍历hashTree,检测相邻的节点。假设n1,n2为相邻的一对节点,n1的在线性表的下标小于n2。检测n1,n2是否重叠。如果发生重叠,则左边不动,整体往右进行调整。但调整的不是n2节点,而是“与n1的某个祖先节点为兄弟节点的n2的祖先节点”。
    2. 每移动完一个节点,其父节点都会失去对称性,所以要进行调整。但我们不动父节点,只通过往左移动子节点来恢复对称性。
    3. 每次恢复对称性后,有某些子节点又会发生重叠现象,所以这时要回到底层重新开始扫描。
    4. 重复3,4,5步骤,直到所有重叠都被消除,布局完成。

    先初始化 开始节点的位置。

     this.tempLayerMap = [];
    const startNode = _.find(this.flowList, (flowItem) => {
        return this.isStartFlowItem(flowItem);
    });
    
    // init start flow item
    startNode.top = FLOW_START_STEP_TOP;
    startNode.left = this.getFlowItemInitLeft();
    

    初始化第一层的数据。

    this.tempLayerMap[0] = [startNode];
    
    

    对子树进行递归下降布局

    this.$_layoutChild(startNode, 1);
    

    在这个方法里面,遍历所有的子节点,初始化topleft 的位置。

    $_layoutChild 方法

    $_layoutChild(pre, layer) {
        const nextList = pre.next;
        const nextListLength = nextList.length;
    
        if (this.tempLayerMap[layer] === undefined) {
            this.tempLayerMap[layer] = [];
        }
    
        nextList.forEach((nextFlowUUid, index) => {
            const flowItem = this.getFlow(nextFlowUUid);
            // 初始化 top
            flowItem.top = pre.top + FLOW_STEP_LENGTH;
            const startLeft = pre.left - (FLOW_LEFT_STEP_LENGTH * (nextListLength - 1)) / 2
            // 初始化 left
            flowItem.left = startLeft + FLOW_LEFT_STEP_LENGTH * index;
            this.tempLayerMap[layer].push(flowItem);
            if (flowItem.next && flowItem.next.length > 0) {
                this.$_layoutChild(flowItem, layer + 1);
            }
        })
    
    },
    
    

    然后再 对所有子节点进行上升重叠调整

    $_adjustChild()
    
    
    $_adjustChild() {
        let flowList = null;
        <!--从最底层开始-->
        for (let i = this.tempLayerMap.length - 1; i >= 0; i--) {
            flowList = this.tempLayerMap[i];
            flowList.forEach((flowItem, index) => {
                const leftFlowItem = flowList[index - 1];
                <!--发生重叠-->
                if (leftFlowItem && flowItem.left - leftFlowItem.left < FLOW_LEFT_STEP_LENGTH) {
                <!---->
                    const parentFlowItem = this.$_findCommonParentNode(leftFlowItem, flowItem);
                    const leftOffset = Math.abs(flowItem.left - leftFlowItem.left) + FLOW_LEFT_STEP_LENGTH;
                    <!--更改每个节点的left值--> 
                    this.$_translateXTree(parentFlowItem, leftOffset);
                    const prevFlowItem = this.getFlow(parentFlowItem.prev[0]);
                    <!--居中所有子节点-->
                    this.$_centerChild(prevFlowItem);
                    <!--移动后下层节点有可能再次发生重叠,所以重新从底层扫描-->
                    i = this.tempLayerMap.length;
                }
            })
        }
    },
    

    清空数据(重做)

    因为涉及到清除掉每个节点信息,以及节点绑定的事件,以及jsPlumb上面绑定的事件,所以不能单纯的清除vue 绑定的 list 数据、

    所以我的做法是,找到开始节点,然后一层层的遍历下去,把节点信息以及绑定的jsPlumb信息删除掉。

    $_doClear(needInit) {
        const startNode = _.find(this.flowList, (flowItem) => {
            return this.isStartFlowItem(flowItem);
        });
    
        if (startNode) {
            this.deleteNextFlowItem(startNode.uuid);
            if (needInit) {
                this.$nextTick(() => {
                    this.initFlow();
                })
            }
    
        }
    },
    

    项目地址

    github: https://github.com/bosscheng/vue-draggable-workflow

    demo: https://bosscheng.github.io/vue-draggable-workflow

    展开全文
  • bpmn-js + vue实现工作流设计

    万次阅读 2020-04-24 21:30:27
    自定义流程设计bpmn-js初体验安装vue使用vue-cli创建项目安装bpmn-js简单的查看工作流图形的例子中间遇到的问题新需求:可以拖拽自定义工作流 此文只为记录一下自己学习bpmn-js和使用bpmn-js实现工作流设计的...

    此文只为记录一下自己学习bpmn-js和使用bpmn-js实现工作流设计器的过程。以后我将通过不断提出新需求的方式,来逐渐完成一个简单的工作流设计器。

    1、bpmn-js初体验

    安装vue

    新版的vue-cli有图形化工具,新版本的Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g进行卸载

    # 新版安装命令
    npm install -g @vue/cli
    # 安装完成后,我们就可以使用vue ui命令打开图形界面来创建管理vue项目了
    vue ui
    

    在这里插入图片描述

    使用vue-cli创建项目

    略…

    安装bpmn-js

    # 切换到新建的项目
    npm install bpmn-js --save-dev
    

    按装完成后可以在node-modules中找到这几个文件夹
    在这里插入图片描述

    简单的查看工作流图形的例子

    官方的例子都是基于jquery的,在获取bpmn20.xml文档的时候可以直接import,在vue中需要通过异步请求来获取数据,因此需要安装axios

    • 安装axios
    npm install axios
    
    • 安装完成以后修改main.js
      在这里插入图片描述
    • 添加一个vue组件,直接上代码
    <template>
      <div class="containers">
        <div id="canvas" class="canvas" ref="canvas"></div> 
      </div>
    </template>
    <script>
    import BpmnJS from 'bpmn-js' // 引入 bpmn-js
    export default {
        data () {
        },
        mounted() {
            var viewer = new BpmnJS({
                container: '#canvas'
            })
            
            var diagramUrl = 'https://cdn.staticaly.com/gh/bpmn-io/bpmn-js-examples/dfceecba/starter/diagram.bpmn';
            // var diagramUrl = 'http://localhost:8080/pizza-collaboration.bpmn20.xml';
            this.$http.get(diagramUrl)
                .then(function(res){
                       viewer.importXML(res.data, function(err){
                        if (!err) {
                            console.log('success!')
                            viewer.get('canvas').zoom('fit-viewport')
                            console.log('success...')
                        } else {
                            console.log('something went wrong:', err)
                        } 
                    })
                })
                .catch(function(err){
                    console.log(err)
                })
        }
    }
    </script>
    <style lang="scss">
      /*左边工具栏以及编辑节点的样式*/  
      @import '~bpmn-js/dist/assets/diagram-js.css';
      @import '~bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
      @import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
      @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%;
        }
        .bjs-powered-by {
          display: none;
        }
      }
    </style>
    
    

    运行以后可以看到一下页面
    在这里插入图片描述

    使用中间遇到的问题

    • 图形不显示,F12 发现报错

    something went wrong: Error: unparsable content omgdc:Bounds detected; this may indicate an invalid BPMN 2.0 diagram file
    line: 0
    column: 4310
    nested error: missing namespace on omgdc:Bounds
    at error (index.esm.js?42c7:63)
    at handleError (index.esm.js?42c7:689)
    at handleError (index.esm.js?ea8d:193)
    at parse (index.esm.js?ea8d:1016)
    at Parser.parse (index.esm.js?ea8d:298)
    at eval (index.esm.js?42c7:856)

    非常郁闷,我的bpmn文件命名都指明了namespace,为什么还提示 missing namespace?
    viewer#importXML需要的参数是个字符串,所以想当然的将res转化为字符串,后来 仔细查看res中的数据发现res是一个object,res = {data:""} ,所以只需需要取出data就可以了。
    解决:
    在这里插入图片描述

    2、新需求:可以拖拽自定义工作流

    实现工作流的编辑需要用到bpmn-js的另一个重要的组件(BpmnModeler)。
    效果: 这里顺便测试了国际化
    在这里插入图片描述

    还是直接上代码

    <template>
      <div class="containers" ref="containers">
        <div id="js-canvas" class="canvas" ref="canvas"></div> 
      </div>
    </template>
    <script>
    // 引入Modeler
    import BpmnModeler from 'bpmn-js/lib/Modeler' // 引入 bpmn-js
    // 用来进行国际化,参考官方的例子 bpmn-js-examples-master ===> i18n
    import customTranslate from '../customTranslate/customTranslate';
    export default {
        data () {
            return {
                bpmnModeler: null,
                containers: null,
                canvas: null,
                customTranslateModule: {
                  translate: [ 'value', customTranslate ]
                }
            }
        },
        methods:{
            openDiagram(xml){
                this.bpmnModeler.importXML(xml, function(err) {
                    if (err) {
                        // container
                        //     .removeClass('with-diagram')
                        //     .addClass('with-error');
                        console.error(err);
                    } else {
                      // container
                      //   .removeClass('with-error')
                      //   .addClass('with-diagram');
                    }
                });
            },
            // 注意:必须先加载一个bpmn文件,新建就是加载一个空的bpmn文件,否则不能拖拽节点
            createNewDiagram(){
                //var diagramUrl = 'https://cdn.staticaly.com/gh/bpmn-io/bpmn-js-examples/dfceecba/starter/diagram.bpmn';
                // var diagramUrl = 'http://localhost:8080/newDiagram.bpmn';
                this.$http.get(diagramUrl)
                	// 这里必须使用箭头函数,否则提示找不到openDiagram方法
                    .then((res)=>{
                        console.log(res.data)
                        this.openDiagram(res.data)
                    })
                    .catch((err)=>{
                        console.log(err)
                    })        
            },
        },
        mounted() {
            // 获取到属性ref为“containers”的dom节点
            this.containers = this.$refs.containers
            console.log(this.customTranslate)
            // 获取到属性ref为“canvas”的dom节点
            const canvas = this.$refs.canvas
            this.bpmnModeler = new BpmnModeler({
                                container: canvas,
                                additionalModules: [
                                    this.$data.customTranslateModule
                                ]
                            })
            this.createNewDiagram()
        }
    }
    </script>
    <style lang="scss">
      /*左边工具栏以及编辑节点的样式*/  
      @import '~bpmn-js/dist/assets/diagram-js.css';
      @import '~bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
      @import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
      @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%;
        }
        .bjs-powered-by {
          display: none;
        }
      }
    </style>
    
    

    碰到的问题:

    1. 找不到openDiagram方法: 开始的时候在axios#get#then方法中使用匿名函数作为回调函数,这个问题主要是由于this这个对象指向引起的. 改成箭头函数,问题解决。
    • axios中的this的指向:

    匿名函数的指针指向->函数操作的本身
    箭头函数的指针指向->组件
    也就是说当你需要使用到组件中声明的变量或者函数,就需要使用箭头函数

    简单记录一下匿名函数和箭头函数中this的指向:

    • 匿名函数:

    在一般情况下,this对象时在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。但是,匿名函数的执行环境具有全局性,因此它的this对象通常指向windows.

    • 箭头函数:

    (1)默认指向定义它时,所处上下文的对象的this指向。即ES6箭头函数里this的指向就是上下文里对象this指向,偶尔没有上下文对象,this就指向window
    (2)即使是call,apply,bind等方法也不能改变箭头函数this的指向

    3、新需求:添加属性面板

    属性面板需要单独安装

    npm install --save bpmn-js-properties-panel
    npm install --save camunda-bpmn-moddle
    

    待续。。。

    展开全文
  • 项目详情:https://blog.csdn.net/weixin_41039677/article/details/117516337?spm=1001.2014.3001.5501
  • 我是一个后端老炮,在做工作流服务时,深感动态表单的重要性。所以,慢慢摸索和学习,结合自己的需求,搭建了本套动态表单系统。 本系统是基于 lerna 和 yarn workspaces 构建的, monorepo 方式管理代码的项目(很...
  • 作为没用过工作流的一员,之前看过vue引入activiti工作流的案例,但是组件中存在很多问题,这个是我修改过并且成功调用,大家可以供参考,富含Java代码(Jfinal),后续出spring版本的, 前台版本vue4.0版本, ...
  • Vue集成activity工作流

    万次阅读 热门讨论 2019-08-17 17:40:38
    情景: 由于activiti与系统应用主题样式出入较大,协商后决定将activiti的... 通过iframe在相应的前台工作流界面引入activiti的model.html(最外层的主html,名字可能有出入)。 mounted时将this,即vuecompo...
  • 标题:springboot-vue-activiti前后端分离快速开发平台脚手架,整合工作审批流,流程在线编辑器 前言 目前市场上有很多开源平台没有整合工作流,即使有,也是价格不菲的商业版,来看这篇文章的估计也了解了行情,...
  • Atom-vue-atom-snippets.zip,Vue snippets to my daily Vue workflow. Originally a port of the excellent https://github.com/sdras/vue-vscode-snippetsVue原子片段,atom是一个用web技术构建的开源文本编辑器
  • 特别注意: Springboot 工作流 前后分离 + 跨域 版本 (权限控制到菜单和按钮) 后台框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+接口 前端页面:html +vue.js 形式 jquery ajax 异步跨域 json 格式数据...
  • 本框架为 :springcloud + Springboot 微服务\分布式 工作流 前后分离 + 跨域 版本 (权限控制到菜单和按钮) 后台框架 :springcloud Greenwich.SR1 + springboot 2.1.4 + activiti6.0.0 + mybaits + maven + json ...
  • 特别注意: Springboot 工作流 前后分离 + 跨域 版本 (权限控制到菜单和按钮) 后台框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+接口 前端页面:html +vue.js 形式 jquery ajax 异步跨域 json 格式数据...
  • vue-loader配置 /build/vue-loader.config.js const docsLoader = require('./doc-loader') exports.defaule = () => { return { preserveWhitepace: true, //去除html标签最后的空格 cssModules: { /...
  • 特别注意: Springboot 工作流 前后分离 + 跨域 版本 (权限控制到菜单和按钮) 后台框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+接口 前端页面:html +vue.js 形式 jquery ajax 异步跨域 json 格式数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,212
精华内容 2,084
关键字:

vue工作流编辑器

vue 订阅