前端实现自定义流程图、关系图

效果图

image.png

拖拽进父节点

image.png

image.png

image.png

点击滑动自动生成父节点

image.png

image.png

组件封装 sdFlow

npm i @antv/x6 @antv/x6-plugin-stencil @antv/x6-plugin-transform @antv/x6-plugin-selection @antv/x6-plugin-snapline @antv/x6-plugin-keyboard @antv/x6-plugin-clipboard @antv/x6-plugin-history insert-css --save

  <!--
 * @Descripttion: 流程图自定义绘制
 * @version: 1.0
 * @Author: xushuaibing
 * @Date: 2023年9月14日00:00:00
-->
<template>
  <div id="container">
      <!-- <div id="graph-container" style="min-width: 400px; min-height: 600px"></div> -->
  </div>
</template>

<script>
import { Graph, Shape } from "@antv/x6";
import { Stencil } from "@antv/x6-plugin-stencil";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import insertCss from "insert-css";

export default {
    name: "SdFlow",
    props: {
        dataShow: {
            type: Object,
            default: () => {}
        }
    },
    watch: {
        // 数据回显
        dataShow: {
            immediate: true,
            deep: true,
            handler(v) {
                if(v) {
                    this.$nextTick(() => {
                        this.graph.fromJSON(v)
                    })
                }
            }
        }
    },
    data() {
        return {
            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: '',
            parentX: '',
            parentY: '',
            zIndex: 0.4
        };
    },
    methods: {
        // 初始化
        initGarph() {
            const graph = new Graph({
                container: document.getElementById("graph-container"),
                grid: true,
                mousewheel: {
                    enabled: true,
                    zoomAtMousePosition: true,
                    modifiers: "ctrl",
                    minScale: 0.5,
                    maxScale: 3,
                },
                // 设置父节点 组合关系
                embedding: {
                    enabled: true,
                    findParent({ node }) {
                        const bbox = node.getBBox()
                        return this.getNodes().filter((node) => {
                            const data = node.getData()
                            if (data && data.parent) {
                            const targetBBox = node.getBBox()
                            return bbox.isIntersectWithRect(targetBBox)
                            }
                            return false
                        })
                    },
                },
                connecting: {
                    router: "manhattan",
                    connector: {
                        name: "rounded",
                        args: {
                            radius: 8,
                        },
                    },
                    anchor: "center",
                    connectionPoint: "anchor",
                    allowBlank: false,
                    snap: {
                        radius: 20,
                    },
                    createEdge() {
                        return new Shape.Edge({
                            attrs: {
                                line: {
                                    stroke: "#A2B1C3",
                                    strokeWidth: 1,
                                    targetMarker: {
                                        // name: "block",
                                        // name: "circle",
                                        width: 12,
                                        height: 8,
                                    },
                                },
                            },
                            // 双击填写关系
                            tools: [
                                {
                                name: 'edge-editor',
                                args: {
                                    attrs: {
                                    backgroundColor: '#fff',
                                    },
                                },
                                },
                            ],
                            zIndex: 10,
                        });
                    },
                    validateConnection({ targetMagnet }) {
                        return !!targetMagnet;
                    },
                },
                highlighting: {
                    embedding: {
                        name: "stroke",
                        args: {
                            padding: -2,
                            attrs: {
                                // fill: "#5F95FF",
                                stroke: "#397CD6",
                                strokeWidth: 1
                            },
                        },
                    },
                },
            });
            this.graph = graph
            // #region 使用插件
            graph
                .use(
                    new Transform({
                        resizing: true,
                        rotating: true,
                    })
                )
                .use(
                    new Selection({
                        rubberband: true,
                        showNodeSelectionBox: true,
                    })
                )
                .use(new Snapline())
                .use(new Keyboard())
                .use(new Clipboard())
                .use(new History());
            // #region 初始化 stencil
            const stencil = new Stencil({
                title: "",
                target: graph,
                stencilGraphWidth: '100%',
                stencilGraphHeight: 60,
                collapsable: false,
                groups: [
                    {
                        title: "自定义规则",
                        name: "group1",
                    }
                ],
                layoutOptions: {
                    columns: 12,
                    columnWidth: 80,
                    rowHeight: 55,
                },
            });
            document.getElementById("stencil").appendChild(stencil.container);
            // #region 快捷键与事件
            graph.on('edge:click', ({ e, x, y, node, view }) => { // 连接边
                console.log(e, x, y, node, view)
            })
            graph.on('blank:mousedown', ({ x, y}) => {
                this.parentX = x
                this.parentY = y
            })
            graph.on('blank:mouseup', ({x, y }) => {
                if(Math.abs(x - this.parentX) > 0 && Math.abs(y - this.parentY) > 0) {
                    this.createParent(this.parentX, this.parentY, Math.abs(x - this.parentX), Math.abs(y - this.parentY) )
                    // const nodes = graph.getNodes(); // 选中节点集合
                    // const nodes = graph.getContentArea(); // 绘画出得节点
                    // 获取所画区域得节点
                    const nodeArea = graph.getNodesInArea(this.parentX, this.parentY, Math.abs(x - this.parentX), Math.abs(y - this.parentY))
                    // const arr = nodeArea.filter(item => item.data.parent)
                    let childrenIds = []
                    let parentID = ''
                    console.log(nodeArea)
                    // 获取父和子的id
                    nodeArea.forEach(item => {
                        if(item.data && item.data.parent) {
                            parentID = item.id
                        }else {
                            childrenIds.push(item.id)
                        }
                    })
                    const allCellData = this.getGraphData()
                    // 给数据帮定父子
                    allCellData.cells.forEach(item => {
                        if(item.id == parentID) {
                            item.children = childrenIds
                        }else if(childrenIds.includes(item.id)){
                            item.parent = parentID
                        }
                    })
                    graph.fromJSON(allCellData) // 重新渲染
                }
            })
            // graph.on('view:mounted', ({  view }) => { // 创建节点后触发
            //  if(view.cell.data && view.cell.data.parent) { // 限制为父节点 为实现选中生成他们的父
            //      console.log(view.cell.id, view)
            //  }
            // })
            graph.bindKey(["meta+c", "ctrl+c"], () => {
                const cells = graph.getSelectedCells();
                if (cells.length) {
                    graph.copy(cells);
                }
                return false;
            });
            graph.bindKey(["meta+x", "ctrl+x"], () => {
                const cells = graph.getSelectedCells();
                if (cells.length) {
                    graph.cut(cells);
                }
                return false;
            });
            graph.bindKey(["meta+v", "ctrl+v"], () => {
                if (!graph.isClipboardEmpty()) {
                    const cells = graph.paste({ offset: 32 });
                    graph.cleanSelection();
                    graph.select(cells);
                }
                return false;
            });

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

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

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

            // zoom
            graph.bindKey(["ctrl+1", "meta+1"], () => {
                const zoom = graph.zoom();
                if (zoom < 1.5) {
                    graph.zoom(0.1);
                }
            });
            graph.bindKey(["ctrl+2", "meta+2"], () => {
                const zoom = graph.zoom();
                if (zoom > 0.5) {
                    graph.zoom(-0.1);
                }
            });
            // 控制连接桩显示/隐藏
            const showPorts = (ports, show) => {
                for (let i = 0, len = ports.length; i < len; i += 1) {
                    ports[i].style.visibility = show ? "visible" : "hidden";
                }
            };
            graph.on("node:mouseenter", () => {
                const container = document.getElementById("graph-container");
                const ports = container.querySelectorAll(".x6-port-body");
                showPorts(ports, true);
            });
            graph.on("node:mouseleave", () => {
                const container = document.getElementById("graph-container");
                const ports = container.querySelectorAll(".x6-port-body");
                showPorts(ports, false);
            });
            Graph.registerNode(
                "custom-rect",
                {
                    inherit: "rect",
                    width: 66,
                    height: 36,
                    attrs: {
                        body: {
                            strokeWidth: 1,
                            stroke: "#5F95FF",
                            fill: "#EFF4FF",
                        },
                        text: {
                            fontSize: 12,
                            fill: "#262626",
                        },
                    },
                    ports: { ...this.ports }
                },
                true
            );
            Graph.registerNode(
                "custom-polygon",
                {
                    inherit: "polygon",
                    width: 66,
                    height: 36,
                    attrs: {
                        body: {
                            strokeWidth: 1,
                            stroke: "#5F95FF",
                            fill: "#EFF4FF",
                        },
                        text: {
                            fontSize: 12,
                            fill: "#262626",
                        },
                    },
                    ports: {
                        ...this.ports,
                        items: [
                            {
                                group: "top",
                            },
                            {
                                group: "bottom",
                            },
                        ],
                    },
                },
                true
            );
            Graph.registerNode(
                "custom-circle",
                {
                    inherit: "circle",
                    width: 45,
                    height: 45,
                    attrs: {
                        body: {
                            strokeWidth: 1,
                            stroke: "#5F95FF",
                            fill: "#EFF4FF",
                        },
                        text: {
                            fontSize: 12,
                            fill: "#262626",
                        },
                    },
                    ports: { ...this.ports },
                },
                true
            );
            Graph.registerNode(
                "custom-image",
                {
                    inherit: "rect",
                    width: 52,
                    height: 52,
                    markup: [
                        {
                            tagName: "rect",
                            selector: "body",
                        },
                        {
                            tagName: "image",
                        },
                        {
                            tagName: "text",
                            selector: "label",
                        },
                    ],
                    attrs: {
                        body: {
                            stroke: "#5F95FF",
                            fill: "#5F95FF",
                        },
                        image: {
                            width: 26,
                            height: 26,
                            refX: 13,
                            refY: 16,
                        },
                        label: {
                            refX: 3,
                            refY: 2,
                            textAnchor: "left",
                            textVerticalAnchor: "top",
                            fontSize: 12,
                            fill: "#fff",
                        },
                    },
                    ports: { ...this.ports },
                },
                true
            );
            const r1 = graph.createNode({
                shape: "custom-rect",
                label: "开始",
                attrs: {
                    body: {
                        rx: 20,
                        ry: 26,
                    },
                },
            });
            const r2 = graph.createNode({
                shape: "custom-rect",
                label: "过程"
            });
            const r3 = graph.createNode({
                shape: "custom-rect",
                attrs: {
                    body: {
                        rx: 6,
                        ry: 6,
                    },
                },
                label: "可选过程",
            });
            const r4 = graph.createNode({
                shape: "custom-polygon",
                attrs: {
                    body: {
                        refPoints: "0,10 10,0 20,10 10,20",
                    },
                },
                label: "决策",
            });
            const r5 = graph.createNode({
                shape: "custom-polygon",
                attrs: {
                    body: {
                        refPoints: "10,0 40,0 30,20 0,20",
                    },
                },
                label: "数据",
            });
            const r6 = graph.createNode({
                shape: "custom-circle",
                label: "连接",
            });
            stencil.load([r1, r2, r3, r4, r5, r6], "group1");
        },
        // 创建边
        createEdge(
            id,
            source,
            target,
            vertices
            ) {
            return this.graph.addEdge({
                id,
                source,
                target,
                vertices,
                label: id,
                attrs: {
                    label: {
                        fontSize: 12,
                    },
                },
            })
        },
        // 创建父节点
        createParent(x, y, width, height) {
            this.graph.addNode({
                x,
                y,
                width,
                height,
                zIndex: this.zIndex,
                // zIndex: 0.5,
                attrs: {
                    body: {
                    fill: '#F5F9FF',
                    stroke: '#397CD6',
                    strokeWidth: 1,
                    strokeDasharray: 5
                    },
                    label: {
                    fontSize: 12,
                    },
                },
                data: {
                    parent: true,
                },
                ports: { ...this.ports }
            })
            this.zIndex = this.zIndex - 0.01 // 防止只能父子嵌套2层,实现前者能移入后者
        },
        // 获取JSON对象
        getGraphData() {
            return this.graph.toJSON()
        },
        // css样式
        preWork() {
            // 这里协助演示的代码,在实际项目中根据实际情况进行调整
            const container = document.getElementById("container");
            const stencilContainer = document.createElement("div");
            stencilContainer.id = "stencil";
            const graphContainer = document.createElement("div");
            graphContainer.id = "graph-container";
            container.appendChild(stencilContainer);
            container.appendChild(graphContainer);
            insertCss(`
          #container {
          display: flex;
          flex-direction: column;
          border: 1px solid #dfe3e8;
          height: 100%;
          width: 100%;
          }
          #stencil {
          width: 100%;
          height: 80px;
          position: relative;
          border-right: 1px solid #dfe3e8;
          }
          #graph-container {
          width: 100%;
          height: 100%;
          }
          .x6-widget-stencil  {
          background-color: #fff;
          }
          .x6-widget-stencil-title {
            display: none;
          background-color: #fff;
          }
          .x6-widget-stencil-group-title {
            display: none;
          background-color: #fff !important;
          }
          .x6-widget-transform {
          margin: -1px 0 0 -1px;
          padding: 0px;
          border: 1px solid #239edd;
          }
          .x6-widget-transform > div {
          border: 1px solid #239edd;
          }
          .x6-widget-transform > div:hover {
          background-color: #3dafe4;
          }
          .x6-widget-transform-active-handle {
          background-color: #3dafe4;
          }
          .x6-widget-transform-resize {
          border-radius: 0;
          }
          .x6-widget-selection-inner {
          border: 1px solid #239edd;
          }
          .x6-widget-selection-box {
          opacity: 0;
          }
      `);
        },
    },
    mounted() {
        this.preWork();
        this.initGarph();
    },
};
</script>

<style></style>

组件使用

<template>
    <el-button type="primary" @click="getData">获取data</el-button>
    <sdFlow :dataShow="data" ref="sdFlow"></sdFlow>
</template>

<script>
import sdFlow from '@/components/sdFlow'
export default {
    components: {
        sdFlow
    },
    data() {
        return {
            data: {},
        }
    },  
    methods: {
        // 获取json数据
        getData() {
            console.log(JSON.stringify(this.$refs.sdFlow.getGraphData()))
            console.log(this.$refs.sdFlow.getGraphData())
        }
    }
}
</script>

<style>

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

推荐阅读更多精彩内容