如何使用Antv X6使用拖拽布局?

发布于:2025-05-22 ⋅ 阅读:(20) ⋅ 点赞:(0)

拖拽效果图

拖拽后

布局预览

官方: X6 图编辑引擎 | AntV

安装依赖

# npm
npm install @antv/x6 --save
npm install @antv/x6-plugin-dnd --save
npm install @antv/x6-plugin-export --save

需要引入的代码

import { Graph, Shape } from '@antv/x6';
import { Dnd } from "@antv/x6-plugin-dnd";

页面布局实现 我们设计左右布局

<div class="pannels-drag-view">
    <div class="left-tree">
        <div style="width: 100%;padding: 0px 10px 10px;">
           <div style="border-bottom: 1px solid #FAFAFA;"></div>
                <div class="tree-one">
                   <a-space>
                      <AppstoreFilled height="20px"/>
                      <span>00000001</span>
                   </a-space>
                  </div>
                <div style="padding-left: 10px;" class="move-view"
                    @mousedown="startDrag($event, {key: value.key, title: value.title})">
1111111111111111</div>
            <div>
       </div>
    </div>
    <div class="right-view" :style="{height: graphHeight+'px', 'min-height': 500, maxHeight: 900}">
       <div ref="container" :style="{height: graphHeight+'px'}"></div>
    </div>
</div>
<style lang="less">
.pannels-layout-view {
    background-color: #FFFFFF;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    .left-tree {
        width: 200px;
        min-width: 200px;
        border: 1px solid #e5e5e5;
        margin-right: 20px;
        display: flex;
        display: -webkit-flex;
        flex-direction: column;
        .tree-one {
            padding: 10px 0px 0px;
            cursor: pointer;
            display: flex;
            flex-direction: row;
            justify-items: start;
        }
        .move-view {
            padding: 10px;
            cursor: move;
        }
        .move-view:hover {
            background-color: #F7F7F7;
        }
        .ant-tree-switcher {
            width: 10px;
        }
        .ant-tree {
            .ant-tree-node-content-wrapper  {
                .cursor_move {
                    cursor: move;
                }

            }
        }
    }
    .right-view {
        display: flex;
        flex-direction: column;
        flex: 1;
        height: 100%;
        overflow: auto;
        border: 1px solid #e5e5e5;
        overflow: hidden;
        position: relative;
        .layout-setting {
            position: absolute;
            top: 1px;
            right: 1px;
            z-index: 10;
            padding: 6px 12px;
            background: hsla(0, 0%, 100%, .7);
            .ant-btn {
                border-width: 0;
            }
            .ant-btn-icon-only {
                padding: 1px 0;
                border-width: 0;
            }
        }
    }
}

</style>

创建节点

// 创建节点
Graph.registerNode(
    'custom-node',
    {
        inherit: 'rect',
        width: 50,
        height: 70,
    },
    true,
);

在onMounted 中设置画布,和初始化内容

// 初始化画布
graph = new Graph({
        container: container.value,
        autoResize: true,
        background: {
            color: '#F2F7FA',
        },
        interacting: ({cell}) => {
            if (cell.getData() == undefined || cell.getData().disableMove) {
                return { nodeMovable: false }
            }
            return true;
        },
        panning: true,
        mousewheel: true,
        embedding: {
            enabled: true,
            findParent({node}) {
                const bbox = node.getBBox();
                return this.getNodes().filter((nodeTemp) => {
                    const data = nodeTemp.getData();
                    if (data && data.parent) {
                        const targetBox = nodeTemp.getBBox();
                        const targetBBox = bbox.intersectsWithRect(targetBox);
                        return targetBBox;
                    }
                    return false;
                })
            }
        }
    });

处理布局 ,在画布上绘制 19 * 20个虚线方框,作为父容器,如下图

代码示例:

onMounted(() => {
    let targetPointTemp = [];
    let nodesListTemp = []
    // 处理布局
    for (let i = 0; i < 20; i++) {
        for (let j = 0; j < 9; j++) {
            let x = i * 60;
            let y = j * 80;
            // 设置吸附点
            targetPointTemp.push({
                x: x,
                y: y
            })
            const rectNode = new Shape.Rect({
                id: `${i}-${j}`,
                shape: 'custom-node',
                x: x,
                y: y,
                width: 50,
                height: 70,
                zIndex: 9,
                // label: `${i}-${j}`,
                data: {
                    parent: true,
                    disableMove: true,
                    cpx: i,
                    cpy: j
                },
                draggable: false,
                attrs: {
                    body: {
                        fill: '#F1F4F6',
                        stroke: "#333333",
                        strokeWidth: 1,
                        strokeDasharray: '4, 4'
                    },
                    title: {
                        text: `${i}-${j}`,
                        fill: '#333333',
                        verticalAnchor: 'bottom',
                        fontSize: 12,
                        refX: 10,
                        refY: 60,
                    }
                },
                markup: [
                    {
                        tagName: 'rect',
                        selector: 'body',
                    },
                    {
                        tagName: 'text',
                        selector: 'title',
                    }
                ],
            })
            nodesListTemp.push(rectNode);
        }
    }
    graph.zoom(-0.2);

    shapeNodesList.value = nodesListTemp;
    // 添加节点到画布
    graph.addNodes(nodesListTemp);
})

使用DND画布外向画布内拖拽,并吸附,效果如下:

实现:外部向画布拖拽

import { Dnd } from "@antv/x6-plugin-dnd";

// 移动左侧树,配合DND 与Graph 拖拽与监听
const startDrag = (e, data) => {
    const {key, title } = data;
    const keys = key.split('-');
    const invSn = keys[0];
    const id = keys[1];
    const newNode = graph.createNode({
        id: title?title:'',
        shape: 'rect',
        width: 150,
        height: 30,
        draggable: true,
        data: {
            parent: false,
            disableMove: false,
            id: id,
            partSn: title,
            invSn: invSn,
            partSn: title,
        },
        attrs: {
            label: {
                text: title,
                fill: '#FFFFFF',
                verticalAnchor: 'middle',
                fontSize: 12,
                ellipsis: true,
                breakWord: true,
                textWrap: {
                    width: -10,
                    height: -10,
                    ellipsis: true
                }
            },
            body: {
                stroke: "#333333",
                strokeWidth: 1,
                fill: '#999999'
            }
        },
        zIndex: 11
    });

    const dnd = new Dnd({
        target: graph,
        getDragNode: (node) => node.clone({keepId: true}),
        getDropNode: (node) => node.clone({keepId: true}),
        validateNode: () => {
            console.log("drag successed")
        },
    })
    dnd.start(newNode, e);
    currentParent.value = null;
}

画布中监听并处理拖拽事件并吸附

onMounted(() => {
// 添加节点监听
     graph.on('node:added', (env) => {
        const { cell, node } = env;
        console.log("node:added");
        const data = cell.data;
        // 获取父节点
        const parent = node.getParent();
        let position = cell.position();
        if (parent) {
            position = parent.getPosition();
            if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {
                graph.removeNode(cell);
                return false;
            }
        } else {
            if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {
                graph.removeNode(cell);
                return false;
            }
        }
        //  删除dom   
        removeDomResData(data, cell);
        node.setProp('size', { width: 50, height: 70 });
        if (parent) {
            // 判断子节点数量
            const childCount = parent.getChildCount();
            if (childCount > 1) {
                startDragOut(data, cell);
                return false
            }
            position = parent.getPosition();
            cell.position(position.x, position.y, cell);
            cell.setAttrs({
                body: {
                    stroke: "#222222",
                    strokeWidth: 1,
                    fill: '#3E82FF'
                }
            })
            cell.setParent(parent);
            cell.insertTo(parent);
        } else {
            const cellParent = cell.getParent();
            cell.setAttrs({
                body: {
                    stroke: "#222222",
                    strokeWidth: 1,
                    fill: '#3E82FF'
                }
            })
            if (cellParent) {
                cell.setParent(cellParent);
                cell.insertTo(cellParent);
            }
        }
        saveLayoutData(data, cell);
    });

})

嵌入父节点的监听

// 嵌入父节点监听
    graph.on('node:embedded', ({ cell, node }) => {
        const parent = cell.getParent();
        const position = parent.getPosition();
        const data = cell.getData(); // 获取节点数据
        if (data.parent != undefined && data.parent) {
            parent.removeChild(node);
            return false;
        }
        if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {
            parent.removeChild(node);
            cell.position(startX.value, startY.value, cell);
            cell.setParent(currentParent.value);
            cell.insertTo(currentParent.value);
            return false;
        }
        const childCount = parent.getChildCount();
        if (childCount > 1) {
            parent.removeChild(node);
            if (currentParent.value) {
                cell.position(startX.value, startY.value, cell);
                cell.setParent(currentParent.value);
                cell.insertTo(currentParent.value);
                return false
            } else {
                const cellParent = cell.parent;
                if (cellParent) {
                    const cellCount = cell.parent.getChildCount();
                    if (cellCount > 1) {
                        return false
                    }
                    const px = cellParent.getPosition().x;
                    const py = cellParent.getPosition().y;
                    cell.position(px, py, cell);
                    cell.setParent(cellParent);
                    cell.insertTo(cellParent);
                    return false
                }
                // cell.setParent(null);
                graph.removeCell(cell);
                startDragOut(data, cell);
                return false;
            }
        }

        cell.position(position.x, position.y, cell);
        cell.setAttrs({
            body: {
                stroke: "#222222",
                strokeWidth: 1,
                fill: '#3E82FF'
            }
        })
        cell.setParent(parent);
        cell.insertTo(parent);
        saveLayoutData(data, cell);
    });

画布中 子节点的移动处理

onMounted(() => {
    // 鼠标按下事件
    graph.on('node:mousedown', (node)=>{
        console.log("node:mousedown")
        const { cell } = node;
        const parent = cell.getParent();
        if (parent) {
            currentParent.value = parent;
        } else {
            currentParent.value = null;
        }
        // 记录初始位置
        if (!cell.data.parent) {
            const position = cell.position();
            startX.value = position.x;
            startY.value = position.y;
        } else {
            startX.value = null;
            startY.value = null;
        }
    });
/// 鼠标按下后的离开事件
    graph.on("node:mouseup", (env) => {
        console.log("node:mouseup")
        const { cell, node } = env;
        const position = cell.position();
        if (position.x < 0 && position.y > 0 && position.y < maxY.value) {
            const data = cell.getData();
            startDragOut(data, cell);
            return false;
        }
        if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {
            cell.position(startX.value, startY.value, cell);
            if (currentParent.value) {
                cell.setParent(currentParent.value);
                cell.insertTo(currentParent.value);
            }
            return false;
        }
    });

     // 监听节点事件函数
    graph.on('node:removed', (args) => {
        //   更新有效节点数据对象
        const { cell } = args;
        const data = cell.getData();
        removeLayoutData(data);
    });

})

画布中布局变化记录事件

// 保存布局变化
const saveLayoutData = (data, cell) => {
    const { id, partSn, invSn} = data;
    let tempList = notSavedDataList.value;
    const position = cell.getPosition();
    const x = Math.ceil(position.x / 60);
    const y = Math.ceil(position.y / 80);
    const _index = _.findIndex(tempList, {id: id});
    if (_index >= 0) {
        const newData = {
            id: id,
            laidOutX: x,
            laidOutY: y,
            partSn: partSn,
            invSn: invSn
        }
        tempList[_index] = newData;
    } else {
        const newData = {
            id: id,
            laidOutX: x,
            laidOutY: y,
            partSn: partSn,
            invSn: invSn
        }
        tempList.push(newData);
    }
    notSavedDataList.value = tempList;
}

需要结合antv/X6的事件监听事件灵活应用


graph.on('cell:click', ({ e, x, y, cell, view }) => {})

graph.on('node:click', ({ e, x, y, node, view }) => {})

graph.on('edge:click', ({ e, x, y, edge, view }) => {})

graph.on('blank:click', ({ e, x, y }) => {})

graph.on('cell:mouseenter', ({ e, cell, view }) => {})

graph.on('node:mouseenter', ({ e, node, view }) => {})

graph.on('edge:mouseenter', ({ e, edge, view }) => {})

graph.on('graph:mouseenter', ({ e }) => {})


网站公告

今日签到

点亮在社区的每一天
去签到