vue使用svg画拓扑图(关系图) 拖拽 缩放

概述

项目刚开始用的echarts画的拓扑图,echarts有个关系图可以直接画出来,但是上个前端在拖拽功能上留了bug,我眼睛都快看瞎了,都没找出来哪里出问题,还找了各种文章借鉴学习都没搞定

后来跑到gitHub上面找了个大神写的拓扑,根据他的代码改好了,不过他用的svg画的图,所以我这份记录也是svg

注意

  1. 数据从后端获取,模拟数据已经提供,修改getData()中代码就行
  2. 缩放功能用到了d3,先安装npm install d3 --save-devmain.js中引入 import * as d3 from 'd3'; Vue.prototype.$d3 = d3
  3. 图片自己随便弄,注意图片名字和后缀名就行

代码

HTML

<template>
    <div class="content">
        <!--拓扑存放位置-->
        <svg
                class="topo"
                id="svg"
                ondragover="return false"
                oncontextmenu="return true"
                @mousewheel="zooming"
        >
            <!-- 已连接的线 -->
            <line
                    v-for="(item) in lines"
                    :key="item.x"
                    :x1="item.x1" :y1="item.y1"
                    :x2="item.x2" :y2="item.y2"
                    style="stroke:rgb(214,214,218);stroke-width:2"/>

            <g
                    v-for="(item, index) in topoNodes"
                    :key="item.id"
                    @mousedown.left.stop.prevent="moveAndLink(index, $event)"
            >
                <image :xlink:href="item.symbol" width="50px" height="50px" :x="item.x" :y="item.y"></image>
                <text :x="item.x + 25" :y="item.y + 66" style="text-anchor: middle; user-select: none;">
                    {{item.name}}
                </text>
            </g>
        </svg>
    </div>
</template>

JavaScript

<script>
    export default {
        name: 'SvgDemo',
        props: {},
        data() {
            return {
                res: {
                    code: 200,
                    data: [{
                        name: "default",
                        devices: [
                            {
                                id: "3",
                                name: "Router",
                                ip: "169.254.200.2",
                                type: "router",
                                x: 400,
                                y: 50
                            },
                            {
                                id: "1",
                                name: "Linux",
                                ip: "192.168.67.101",
                                type: "server",
                                x: 52,
                                y: 500
                            },
                            {
                                id: "5",
                                name: "Winserver",
                                ip: "192.168.67.200",
                                type: "server",
                                x: 500,
                                y: 500
                            },
                            {
                                id: "4",
                                name: "SW",
                                ip: "192.168.67.201",
                                type: "switch",
                                x: 200,
                                y: 200
                            }
                        ],
                        relation: [
                            {source: "3", target: "4", network: "Net-CSRiface_1"},//连线——————source:起点,target(目标):终点
                            {source: "1", target: "4", network: "Net-SWiface_16"},
                            {source: "5", target: "4", network: "Net-R4iface_0"}
                        ]
                    }]
                },
                topoNodes: [], // topo图中的节点
                topoLinks: [], // topo图中的连线
                isMove: true,// 操作模式,默认为移动。可切换为连接模式
                positions: [],//更改的位置
                token: null
            }
        },

        computed: {
            // 动态计算节点间的连线
            lines() {
                let hash = {}
                const OFFSET = 20
                this.topoNodes.forEach((item, index) => {
                    hash[item.id] = index
                })
                /*
                    hash:{
                        1: 1
                        3: 0
                        4: 3
                        5: 2
                     },

                     source:3 1 5,
                     target:4 4 4
                 */
                return this.topoLinks.map(item => {
                    const startNode = this.topoNodes[hash[Number(item.source)]]
                    const endNode = this.topoNodes[hash[Number(item.target)]]
                    return {
                        x1: startNode.x + OFFSET,
                        y1: startNode.y + OFFSET,
                        x2: endNode.x + OFFSET,
                        y2: endNode.y + OFFSET,
                    }
                })
            }
        },
        created() {
            this.token = sessionStorage.getItem("token");
            this.getData();
        },
        methods: {
            getData() {
                //使用模拟数据
                /*this.topoNodes = this.res.data[0].devices;
                this.topoLinks=this.res.data[0].relation;
                for (let item of this.topoNodes) {
                    item.symbol = require(`@/assets/images/${item.type}.svg`);
                }
                for (let item of this.topoLinks) {
                    item.source = Number(item.source);
                    item.target=Number(item.target);
                }
                console.log(this.topoNodes)*/

                //使用接口返回数据
                this.$axios({
                    url: window.config.Login_URL + "/mirror/spaces/topology", //topology
                    method: "GET",
                    headers: {
                        MyToken: this.token,
                    },
                    data: {
                        status: this.value,
                    },
                })
                    .then((res) => {
                        if (res.data.code === 200) {
                            this.topoNodes = res.data.data[0].devices;
                            this.topoLinks = res.data.data[0].relation;
                            for (let item of this.topoNodes) {
                                item.symbol = require(`@/assets/images/${item.type}.svg`);
                            }
                            for (let item of this.topoLinks) {
                                item.source = Number(item.source);
                                item.target = Number(item.target);
                            }
                            //console.log('初始数据',this.topoNodes)
                        }
                    })
                    .catch(() => {
                        this.$message.error("获取失败");
                    });
            },

            //移动事件
            moveAndLink(index, e) {
                // 判断当前模式
                if (this.isMove) {
                    // 移动模式
                    const layerX = e.layerX - this.topoNodes[index].x;
                    const layerY = e.layerY - this.topoNodes[index].y;

                    //实时获取更新后的坐标
                    document.onmousemove = (e) => {
                        this.topoNodes[index].x = e.layerX - layerX;
                        this.topoNodes[index].y = e.layerY - layerY;
                    }
                    //将新坐标存进数据库
                    //如果使用模拟数据,删除该方法
                    document.onmouseup = () => {
                        this.positions = [];
                        for (let j = 0; j < this.topoNodes.length; j++) {
                            this.positions.push({
                                'ip': this.topoNodes[j].ip,
                                'x': this.topoNodes[j].x,
                                'y': this.topoNodes[j].y
                            })
                        }
                        this.$axios({
                            url: window.config.Login_URL + "/mirror/spaces/topology",
                            method: "POST",
                            headers: {
                                MyToken: this.token,
                            },
                            data: {
                                positions: this.positions,
                            },
                        })
                            .then(() => {
                            })
                            .catch(() => {
                            });
                        document.onmousemove = null
                        document.onmouseup = null
                    }

                } else {
                    document.onmousemove = null // 重置鼠标移动事件
                    this.isMove = true // 重置为移动模式
                }
            },

            // 放大缩小
            zooming(){
                var svg = this.$d3.select('#svg');
                var zoom = this.$d3.zoom().scaleExtent([0.4, 5]).on("zoom", function (e) {
                    svg.selectAll('g').attr('transform',e.transform);
                    svg.selectAll('line').attr('transform',e.transform)
                });
                svg.call(zoom)
            }
        }
    }
</script>

CSS

<style scoped>
    .content {
        width: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .topo {
        width: 1070px;
        height: 600px;
    }
</style>
  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值