# 背景
需要实现一个工作流,支持拖拽节点生成工作流。
# 业务实现
- 支持页面布局缩放
- 支持节点
- 支持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