vue+AvtV x6绘制流程图

1,072 阅读5分钟

因为公司业务需求,需要搞个流程图,用于展示每个节点的详情。在网上查了好多资料,选来选去最终选择了阿里的Antv x6(别问,问就是好看) 222.png

先上效果图

一、左侧自定义图形菜单

 <div id="stencil"></div>
首先安装Avtv x6 
npm install @antv/x6 --save

页面中直接引用 
import { Graph, Shape, Addon, DataUri } from "@antv/x6";
data函数变量声明:
        data(){
          retrun {
               stencil: null, //左侧菜单对象
               graph: null, //画布
               timeEnd: null,
               time: null,
               clickNode: {
                //点击节点对象
                customAttrs: {
                  clickNodeContent: "", // 内容
                  clickNodeContentSize: "", // 字体大小
                  clickNodeBoxSizeWidth: "", // 盒子宽度
                  clickNodeBoxSizeHeight: "", // 盒子高度
                  clickNodeBgColor: "", // 背景颜色
                  clickNodeTextColor: "", // 文字颜色
                  clickNodeBorderColor: "", // 边框颜色
                  positionx: 0, // x位置
                  positiony: 0, // y位置
                },
          },
                  clickNodeTemp: null,
                  drawer: false, //节点编辑抽屉
                  direction: "rtl", //抽屉从右往左打开
                     }
        }
    

//初始化左侧图形菜单
      this.initCanvas();//初始化画布
      this.stencil = new Addon.Stencil({
        target: this.graph,
        stencilGraphWidth: 200,
        stencilGraphHeight: 180,
        groups: [
          {
            title: "基础流程图",
            name: "group1",
          },
        ],
        layoutOptions: {
          columns: 2,
          columnWidth: 80,
          rowHeight: 55,
        },
      });
      let appendChildTemp = document.getElementById("stencil");
      if (appendChildTemp) {
        appendChildTemp.appendChild(this.stencil.container);
      }
      
        //初始化自定义图形
    initCustomGraph() {
      //初始化连接线能连接的点
      const ports = {
        groups: {
          top: {
            position: "top",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  // 设置隐藏,在通过事件鼠标移动显示
                  visibility: "hidden",
                },
              },
            },
          },
          right: {
            position: "right",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          bottom: {
            position: "bottom",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          left: {
            position: "left",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
        },
        items: [
          {
            group: "top",
          },
          {
            group: "right",
          },
          {
            group: "bottom",
          },
          {
            group: "left",
          },
        ],
      };
      Graph.registerNode(
        "custom-polygon",
        {
          inherit: "polygon",
          width: 66,
          height: 36,
          markup: [
            {
              tagName: "polygon",
              selector: "body",
            },
            {
              tagName: "text",
              selector: "label",
            },
          ],
          attrs: {
            body: {
              strokeWidth: 1,
              stroke: "#5F95FF",
              fill: "#EFF4FF",
            },
            text: {
              fontSize: 12,
              fill: "#262626",
            },
          },
          ports: {
            ...ports,
            items: [
              // 这里是限制连接点多少个的地方
              {
                group: "top",
              },
              {
                group: "bottom",
              },
            ],
          },
        },
        true
      );

      Graph.registerNode(
        "custom-circle",
        {
          inherit: "circle",
          width: 45,
          height: 45,
          markup: [
            {
              tagName: "circle",
              selector: "body",
            },
            {
              tagName: "text",
              selector: "label",
            },
          ],
          attrs: {
            body: {
              strokeWidth: 1,
              stroke: "#5F95FF",
              fill: "#EFF4FF",
            },
            text: {
              fontSize: 12,
              fill: "#262626",
            },
          },
          ports: { ...ports },
        },
        true
      );

      Graph.registerNode(
        "custom-rect",
        {
          inherit: "rect",
          width: 66,
          height: 36,
          markup: [
            {
              tagName: "rect",
              selector: "body",
            },
            {
              tagName: "text",
              selector: "label",
            },
          ],
          attrs: {
            body: {
              strokeWidth: 1,
              stroke: "#5F95FF",
              fill: "#EFF4FF",
            },
            text: {
              fontSize: 12,
              fill: "#262626",
            },
          },
          ports: { ...ports },
        },
        true
      );
      const r1 = this.graph.createNode({
        shape: "custom-rect",
        label: "开始",
        attrs: {
          body: {
            rx: 20,
            ry: 26,
          },
        },
      });
      const r2 = this.graph.createNode({
        shape: "custom-rect",
        label: "过程",
      });
      const r3 = this.graph.createNode({
        shape: "custom-rect",
        attrs: {
          body: {
            rx: 6,
            ry: 6,
          },
        },
        label: "可选过程",
      });
      const r4 = this.graph.createNode({
        shape: "custom-polygon",
        attrs: {
          body: {
            refPoints: "0,10 10,0 20,10 10,20",
          },
        },
        label: "决策",
      });
      const r5 = this.graph.createNode({
        shape: "custom-polygon",
        attrs: {
          body: {
            refPoints: "10,0 40,0 30,20 0,20",
          },
        },
        label: "数据",
      });
      const r6 = this.graph.createNode({
        shape: "custom-circle",
        label: "连接",
      });
      this.stencil.load([r1, r2, r3, r4, r5, r6], "group1");

      const showPorts = (ports, show) => {
        for (let i = 0, len = ports.length; i < len; i = i + 1) {
          ports[i].style.visibility = show ? "visible" : "hidden";
        }
      };

      // 移入节点
      this.graph.on("node:mouseenter", () => {
        const container = document.getElementById("container"); // 这里获取id 是你画板的id
        if (container) {
          const ports = container.querySelectorAll(".x6-port-body");
          showPorts(ports, true);
        }
      });

      // 移出节点
      this.graph.on("node:mouseleave", () => {
        const container = document.getElementById("container"); // 这里获取id 是你画板的id
        if (container) {
          const ports = container.querySelectorAll(".x6-port-body");
          showPorts(ports, false);
        }
      });
    }, 
至此左侧图形菜单已完成。

二、右侧(工具栏+画布)

工具栏:
       <div class="topTool">
        <div class="tool" @click="undo">
          <i class="el-icon-refresh-left"></i>
          <div>撤 销</div>
        </div>

        <div class="tool" @click="clear">
          <i class="el-icon-refresh"></i>
          <div>清 空</div>
        </div>

        <div class="tool" @click="downImg">
          <i class="el-icon-picture-outline"></i>
          <div>导出图片</div>
        </div>
        <div class="tool" @click="save">
          <i class="el-icon-finished"></i>
          <div>保 存</div>
        </div>
    //撤销
    undo() {
      if (!this.graph.isHistoryEnabled()) {
      }
      //   if (this.graph.isHistoryEnabled()) {
      //     this.graph.disableHistory();
      //   } else {
      //     this.graph.enableHistory();
      //   }
    },
    //清空
    clear() {
      this.$confirm("是否确定清空当前画布内容?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        this.graph.fromJSON({});
      });
    },
    //导出图片
    downImg() {
      this.graph.toPNG(
        (dataUri) => {
          // 下载
          DataUri.downloadDataUri(dataUri, "chart.png");
        },
        {
          padding: {
            top: 30,
            right: 30,
            bottom: 30,
            left: 30,
          },
        }
      );
    },
    //保存
    save() {
      //页面图形转换成json
      let preservationData = this.formatGraphData();
      localStorage.setItem("graphCacheData", JSON.stringify(preservationData));
      console.log(preservationData);
    },
    //格式化最终保存数据
    formatGraphData() {
      let data = this.graph.toJSON();
      const model = {
        nodes: [],
        edges: [],
      };
      let tmp;
      if (data.cells) {
        tmp = data.cells;
      } else if (data.nodes || data.edges) {
        tmp = [].concat(data.nodes, data.edges);
      }
      if (tmp) {
        tmp.forEach((item) => {
          if (item.shape !== "edge") {
            model.nodes.push(item);
          } else {
            let sourceId = item.source;
            let targetId = item.target;
            model.edges.push({
              source: sourceId,
              target: targetId,
              connector: item.connector,
              attrs: item.attrs,
              router: item.router,
              labels: item.labels,
            });
          }
        });
      }
      return model;
    }
画布
  <div id="container"></div>
  // 初始化画布
      this.graph = new Graph({
        container: document.getElementById("container"),
        grid: true,
        width: 1240, // 这个是自己定义的宽度
        height: 800, // 这个是自己定义的高度
        background: {
          color: "#fff", // 设置画布背景颜色
        },
        mousewheel: {
          enabled: true,
          zoomAtMousePosition: true,
          modifiers: "ctrl",
          minScale: 0.5,
          maxScale: 3,
        },
        panning: {
          enabled: true, // 单独开启拖动操作
          modifiers: "shift", // 按下shift才可以拖动
        },
        connecting: {
          router: {
            name: "manhattan",
            args: {
              padding: 1,
            },
          },
          connector: {
            name: "rounded",
            args: {
              radius: 8,
            },
          },
          anchor: "center",
          connectionPoint: "anchor",
          allowBlank: true,
          snap: true,
          createEdge() {
            return new Shape.Edge({
              attrs: {
                line: {
                  stroke: "#A2B1C3",
                  strokeWidth: 2,
                  targetMarker: {
                    name: "block",
                    width: 12,
                    height: 8,
                  },
                },
              },
              zIndex: 0,
            });
          },
          validateConnection({ targetMagnet }) {
            return !!targetMagnet;
          },
        },
        highlighting: {
          magnetAdsorbed: {
            name: "stroke",
            args: {
              attrs: {
                fill: "#5F95FF",
                stroke: "#5F95FF",
              },
            },
          },
        },
        resizing: true,
        rotating: true,
        selecting: {
          enabled: true,
          rubberband: true,
          showNodeSelectionBox: true,
        },
        snapline: true,
        keyboard: true,
        clipboard: true,
      });
快捷键和事件方法
    this.graph.bindKey(["meta+c", "ctrl+c"], () => {
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          this.graph.copy(cells);
        }
        return false;
      });

      this.graph.bindKey(["meta+x", "ctrl+x"], () => {
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          this.graph.cut(cells);
        }
        return false;
      });
      this.graph.bindKey(["meta+v", "ctrl+v"], () => {
        if (!this.graph.isClipboardEmpty()) {
          const cells = this.graph.paste({ offset: 32 });
          this.graph.cleanSelection();
          this.graph.select(cells);
        }
        return false;
      });

      //undo redo
      this.graph.bindKey(["meta+z", "ctrl+z"], () => {
        if (this.graph.history.canUndo()) {
          this.graph.history.undo();
        }
        return false;
      });
      this.graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
        if (this.graph.history.canRedo()) {
          this.graph.history.redo();
        }
        return false;
      });

      // select all
      this.graph.bindKey(["meta+shift+a", "ctrl+shift+a"], () => {
        const nodes = this.graph.getNodes();
        if (nodes) {
          this.graph.select(nodes);
        }
      });

      //delete
      this.graph.bindKey("backspace", () => {
        const cells = this.graph.getSelectedCells();
        // console.log(cells.isEdge())
        // if (cells.length) {
        //   graph.removeCells(cells)
        // }
      });

      // zoom
      this.graph.bindKey(["ctrl+1", "meta+1"], () => {
        const zoom = this.graph.zoom();
        if (zoom < 1.5) {
          this.graph.zoom(0.1);
        }
      });
      this.graph.bindKey(["ctrl+2", "meta+2"], () => {
        const zoom = this.graph.zoom();
        if (zoom > 0.5) {
          this.graph.zoom(-0.1);
        }
      });

      // 调整节点大小事件
      this.graph.on("node:resized", ({ e, x, y, node, view }) => {
        this.setClickNode(node); // 这里是将节点保存到变量的方法
      });

      // 移动节点事件
      this.graph.on("node:moved", ({ e, x, y, node, view }) => {});

      // 点击背景板
      this.graph.on("blank:click", ({ e, x, y }) => {});

      // 点击节点
      this.graph.on("node:click", ({ e, x, y, node, view }) => {
        this.setClickNode(node);
        this.drawer = true;
      });

      // 添加边事件, 这里是添加线的删除事件
      this.graph.on("edge:added", ({ edge, index, options }) => {
        edge.addTools([
          {
            name: "button-remove", // 添加删除按钮
            args: {
              distance: "85%",
              attrs: {
                y: -50,
                width: 20,
                height: 20,
              },
            },
          },
        ]);
      });
      // 拖动新增的事件
      this.graph.on("node:added", ({ node }) => {
        node.addTools([
          {
            name: "button-remove",
            args: {
              distance: "85%",
              y: -10, // 删除按钮的位置
              x: "100%",
            },
          },
          {
            name: "boundary",
            args: {
              // distance: 20,
              padding: 15,
            },
          },
        ]);
      });

三、节点编辑

1111.png

     //获取点击节点
    setClickNode(node) {
      this.clickNodeTemp = node; // 指向节点
      this.clickNode.customAttrs.clickNodeContent = node.attrs.text.text;
      this.clickNode.customAttrs.clickNodeContentSize =
        node.attrs.text.fontSize;
      this.clickNode.customAttrs.clickNodeTextColor = node.attrs.text.fill;
      const propTemp = this.clickNodeTemp.getProp(); // 获取样式信息
      this.clickNode.customAttrs.clickNodeBoxSizeWidth = propTemp.size.width;
      this.clickNode.customAttrs.clickNodeBoxSizeHeight = propTemp.size.height;
      this.clickNode.customAttrs.clickNodeBorderColor = node.attrs.body.stroke;
      this.clickNode.customAttrs.clickNodeBgColor = node.attrs.body.fill;

      const relativePos = node.position({ relative: true }); // 获取位置信息
      this.clickNode.customAttrs.positionx = relativePos.x;
      this.clickNode.customAttrs.positiony = relativePos.y;
    },
      // 加一减一或者颜色修改 addOrSubOrColorTemp 判断是点击加还是减  或者是选择颜色的
    addOrSubOrColorClickNodeData(addOrSubOrColorTemp, data) {
      if (addOrSubOrColorTemp == "add") {
        this.clickNode[data]++;
      } else if (addOrSubOrColorTemp == "sub") {
        this.clickNode[data]--;
      }
      (this.clickNode.customAttrs.positionx =
        this.clickNode.customAttrs.positionx * 1),
        (this.clickNode.customAttrs.positiony =
          this.clickNode.customAttrs.positiony * 1);
      this.clickNodeTemp.updateAttrs(this.clickNode);
      if (this.clickNodeTemp.setAttrs) {
        // 用这个方法设置颜色样式
        this.clickNodeTemp.setAttrs({
          body: {
            fill: this.clickNode.customAttrs.clickNodeBgColor,
            stroke: this.clickNode.customAttrs.clickNodeBorderColor,
          },
          label: {
            fill: this.clickNode.customAttrs.clickNodeTextColor,
          },
          text: {
            text: this.clickNode.customAttrs.clickNodeContent,
            fontSize: this.clickNode.customAttrs.clickNodeContentSize,
          },
        });

        if (addOrSubOrColorTemp && addOrSubOrColorTemp !== "color") {
          // 用这个方法修改长宽, 位置
          this.clickNodeTemp.setProp({
            size: {
              width: this.clickNode.customAttrs.clickNodeBoxSizeWidth,
              height: this.clickNode.customAttrs.clickNodeBoxSizeHeight,
            },
            position: {
              x: this.clickNode.customAttrs.positionx * 1,
              y: this.clickNode.customAttrs.positiony * 1,
            },
          });
        }
      }
    },

到这需求已经完成了,后续为了展示已存在的流程又补了个初始化加载的方法

 var obj = JSON.parse(
        localStorage.getItem("graphCacheData") || '{"nodes":[],"edges":[]}'
      );
      if (obj && (obj.edges.length > 0 || obj.nodes.length > 0)) {
        let graphCacheData = JSON.parse(localStorage.getItem("graphCacheData"));
        this.graph.fromJSON(graphCacheData);
      }