background

You need to implement a workflow that can be generated by dragging and dropping nodes.

Business implementation

  • Supports page layout scaling
  • Support node
  • Support the if the else
  • Support for multiple branches

Technical point

  • The grid background
  • Workflow scaling
  • Implementation of workflow technology
  • Drag and drop nodes

Technology selection

  • vue
  • jsplumb
  • sortablejs(vue-draggable)

The difficulties in storm

The grid background

It is mainly realized by using linear-gradient and background-size of CSS.

<div class="flow-layout">
    <div class="flow-editor">
        <div class="canvas-container">
        </div>
    </div>
</div>
Copy the code
.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;
}
Copy the code

.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;
}
Copy the code

Workflow scaling

The zoom function is realized by modifying the zoom value based on the property selector E[att=”val”] of the CSS.

<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>
Copy the code
.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;
}

Copy the code

Implementation of workflow technology

The main implementation relies on jSPLumb.

Jsplumb is used to connect the two nodes.

Make dom nodes jSPLumb draggable nodes

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

The connection of two nodes.

jsPlumb.connect({
    source: source,
    target: target,
    endpoint: 'Dot'.// The style of the connection line
    connectorStyle: {strokeStyle: "#ccc".joinStyle: "round".outlineColor: "#ccc"}, / / the link style
    // Connection line configuration, starting point available
    connector: ["Flowchart", {
        stub: [10.20].gap: 1.cornerRadius: 2.alwaysRespectStubs: true}]./ / links
    //
    endpointStyle: {fill: 'transparent'.outlineStroke: 'transparent'.outlineWidth: 2},
    // Line style
    paintStyle: {stroke: 'lightgray'.strokeWidth: 2},
    // Anchor position
    anchor: ['BottomCenter'.'TopCenter'].// Mask layer - Set arrow
    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}] //]});Copy the code

Deleting a node

jsPlumb.removeAllEndpoints(uuid);
Copy the code

Create a node between two nodes

 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);
        });
    });
}
Copy the code

Copy creation between two nodes

 function createFlowItemLabel(source, target, label) {
    this.$nextTick((a)= > {
        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>`); }}); }Copy the code

Drag and drop nodes

Mainly rely on sorTableJS implementation

The main use of vuE-Draggable encapsulated components, to achieve drag. The core code

Drag the destination area.

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

The target object being dragged.

<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>
Copy the code

Project screenshots

The project address

Making: github.com/bosscheng/v…

Demo: bosscheng. Making. IO/vue – draggab…