美文网首页
前端实现自定义流程图、关系图

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

作者: Xbbing | 来源:发表于2023-09-18 15:25 被阅读0次

    效果图

    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>
    

    相关文章

      网友评论

          本文标题:前端实现自定义流程图、关系图

          本文链接:https://www.haomeiwen.com/subject/ldpgvdtx.html