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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容