基于vue的工作流开发

# 背景

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

# 业务实现

- 支持页面布局缩放

- 支持节点

- 支持if else

- 支持多分支

# 技术点

- 网格背景

- 工作流缩放

- 工作流技术实现

- 节点拖拽

# 技术选型

- vue

- jsplumb

- sortablejs(vue-draggable)

# 难点攻破

## 网格背景

主要是利用css的 `linear-gradient` 和 `background-size` 实现的。

```html

<div class="flow-layout">

    <div class="flow-editor">

        <div class="canvas-container">

        </div>

    </div>

</div>

```

```css

.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;

}

```

![1-1.png](http://note.youdao.com/yws/res/37789/WEBRESOURCE4a51e5ddffbd9c852b7e3f2a6f5b6d57)

```css

.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的值,来实现缩放功能。

![1-2.png](http://note.youdao.com/yws/res/37797/WEBRESOURCE4c902789d999b6c85ecae1c67f2b5150)

```html

<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>

```

```css

.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 可拖拽节点

```html

<div id="_uuid">

</div>

```

```js

jsPlumb.draggable(_uuid, {});

```

## 两个节点的连接。

```js

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}] //

    ]

});

```

## 删除一个节点

```js

jsPlumb.removeAllEndpoints(uuid);

```

## 两个节点之间创建节点

![1-3.png](http://note.youdao.com/yws/res/37849/WEBRESOURCE3009b0deb502513f6ddf0ad6235269df)

```js

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);

        });

    });

}

```

## 两个节点之间的文案创建

![1-4.png](http://note.youdao.com/yws/res/37855/WEBRESOURCE836f52036c0a71f4ba01d40cb9093b2f)

```js

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 封装好的组件,来实现拖拽。 核心代码

拖拽的目的地区域。

```html

<draggable class="flow-item node-temp"

          ref="tempNode"

          :id="flowItem.uuid"

          :group="{name:'sortable', pull:false, put: true }">

    <div class="node-temp-img"></div>

</draggable>

```

被拖拽的目标对象。

```html

<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>

```

# 项目截图

![1.png](http://note.youdao.com/yws/res/37859/WEBRESOURCEca2f5c70acb3b8d860957e799917f074)

![2.png](http://note.youdao.com/yws/res/37861/WEBRESOURCE02712753e79266307821d5e3f7058407)

![3.png](https://note.youdao.com/src/WEBRESOURCEce9404bf40e159b5cbcf2784415e7e6a)

# 项目地址

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

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容