当前位置:   article > 正文

vue实现echarts树图修改节点图片,修改连线颜色,鼠标悬停显示详情,鼠标右键弹出菜单,搜索,导出PNG,高亮,查看节点是否还有子节点,修改树图的展示方式_echarts tree 节点右击菜单

echarts tree 节点右击菜单

其实这些效果之前都有用js写过,但是最近在写vue项目,里面的些许语法还是有些不一样的,所以还是写一遍文章总结一下,下次遇到就可以直接用了。

如果想看js写法,可以看我别的文章

首先,实现效果入下图:

下面简单介绍一下每种实现方式

1.将树图的节点修改成图片以及修改连线的颜色

代码布局如下: 

 主要代码:

symbolSize: [30, 30],
  1. /**
  2. * 遍历树节点信息
  3. * @param nodes 拓扑图节点数据
  4. * 通过某种状态区分显示拓扑图的节点图片或连线颜色
  5. * */
  6. readNodes(nodes) {
  7. for (const item of nodes) { // js遍历树形数组结构
  8. if (item.children && item.children.length) {
  9. this.readNodes(item.children)
  10. }
  11. if (item.name == '服务器') {
  12. item.symbol = 'image://' + require('@/assets/topo/server_online_unreg.png');
  13. // 修改连线颜色
  14. item.lineStyle = {
  15. color: '#ff0000'
  16. }
  17. } else {
  18. item.symbol = 'image://' + require('@/assets/topo/internet.png');
  19. item.lineStyle = {
  20. color: '#2E8B57'
  21. }
  22. }
  23. }
  24. },

2.添加鼠标悬停事件

代码布局如下:

 主要代码:

formatter: this.formatterHover// 修改鼠标悬停显示的内容
  1. /**
  2. * 鼠标悬停显示详情
  3. * @param params
  4. * @returns {string}
  5. */
  6. formatterHover(params) {
  7. // console.log(params);
  8. var deviceType = params.data.type;
  9. var imgPath = params.data.symbol;
  10. // 图片地址截取,因为echarts修改图片的时候有一个------image://---前缀,前缀后面的才是图片真正的地址
  11. var imgPathSrc = imgPath.split('image://')[1];
  12. // console.log('str',imgPathSrc);
  13. if (deviceType === 'Internet' || deviceType === 'hub') {
  14. return "<img src='" + imgPathSrc + " ' width='30px' height='30px'>" + '<span style="padding:0 5px;font-size: 14px;">' + params.data.name + '</span>';
  15. } if (deviceType === 'switch') {
  16. return "<img src='" + imgPathSrc + " ' width='30px' height='30px'>" + '<span style="padding: 0 5px;font-size: 14px;">设备类型:' + params.data.name + '</span>' + '<br>' +
  17. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">IP:' + params.data.IP + '</span>' + '<br>' +
  18. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">MAC:' + params.data.MAC + '</span>' + '<br>';
  19. // +'<button style="padding:2px 5px;border:none;outline:none;color:#ffffff;border-radius: 5px;background:rgba(0,0,0,0.5);"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> IP列表</button>'
  20. // +'<button style="padding:2px 5px;border:none;outline:none;color:#ffffff;border-radius: 5px;background:rgba(0,0,0,0.5);margin-left: 10px;"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> MAC列表</button>';
  21. } else {
  22. return "<img src='" + imgPathSrc + " ' width='30px' height='30px'>" + '<span style="padding: 0 5px;font-size: 14px;">设备类型:' + params.data.name + '</span>' + '<br>' +
  23. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">IP:' + params.data.IP + '</span>' + '<br>' +
  24. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">MAC:' + params.data.MAC + '</span>' + '<br>';
  25. }
  26. },

3.鼠标右键弹出菜单

代码布局如下:

 

主要代码:

  1. <style scoped>
  2. .menu{
  3. /*这个样式不写,右键弹框会一直显示在画布的左下角*/
  4. position: absolute;
  5. background: rgba(255, 255, 255, 1);
  6. border-radius: 5px;
  7. left: -99999px;
  8. top: -999999px;
  9. }
  10. .menu ul{
  11. list-style: none;
  12. padding: 0;
  13. margin: 0;
  14. }
  15. .menu ul li{
  16. cursor: pointer;
  17. padding: 5px 10px;
  18. color: #000000;
  19. border-bottom: 1px dashed #a9a9a9;
  20. font-size: 14px;
  21. }
  22. .menu ul li:last-child{
  23. border-bottom: none;
  24. }
  25. </style>
  1. <!--右键弹出菜单-->
  2. <div ref="rightMenu" class="menu" style="display: none;">
  3. <ul>
  4. <li>下线</li>
  5. <li>上线</li>
  6. <li>掉线</li>
  7. </ul>
  8. </div>
  1. <div class="app-container" @contextmenu.prevent="" @click="mainClick">
  2. </div>
  1. /**
  2. * 鼠标右键,显示右键菜单
  3. */
  4. echarts.init(chart).on('contextmenu', function(params) {
  5. // console.log('params', params)
  6. that.contextmenu = true
  7. // 去掉悬停
  8. that.$refs.main.children[1].style.display = 'none'
  9. that.$refs.rightMenu.style.display = 'block';
  10. // // //让自定义菜单随鼠标的箭头位置移动
  11. that.$refs.rightMenu.style.left = params.event.offsetX + 45 + 'px'
  12. that.$refs.rightMenu.style.top = params.event.offsetY + 45 + 'px'
  13. });
  1. /**
  2. * 点击页面其它地方时,隐藏右键菜单
  3. * */
  4. mainClick() {
  5. this.contextmenu = false
  6. this.$refs.rightMenu.style.display = 'none';
  7. },

 注:以上代码实现右键菜单是没有问题的,但是后期在开发的过程中,发现当在浏览器左下角,右下角,最右边点击时,菜单会被覆盖显示不全,所以对菜单显示的位置做了一些调整,调整完后效果如下:

 主要代码:<至于里面的数字,具体要根据自己的项目进行修改>

  1. /**
  2. * smallWidth:即能容下弹框的最小宽度=document.body.clientWidth<浏览器窗口宽度> - 210<左侧导航菜单宽度> - params.event.offsetX<当前点击距离左上角宽度>
  3. * smallHeight:即能容下弹框的最小高度=document.body.clientHeight<浏览器窗口高度> - 120<上方导航菜单宽度> - params.event.offsetY<当前点击距离左上角高度>
  4. * 140,在浏览器中console.log打印,差不多smallWidth小于140的时候,右键菜单宽度就放不下了
  5. * 256,在浏览器中console.log打印,差不多smallHeight小于256的时候,右键菜单高度就放不下了
  6. * 接下来右键菜单的显示,差不多就是当前点击的位置加或减去右键菜单宽或高的长度
  7. * */
  8. const smallWidth = document.body.clientWidth - 210 - params.event.offsetX
  9. const smallHeight = document.body.clientHeight - 120 - params.event.offsetY
  10. // console.log('smallWidth', smallWidth)
  11. // console.log('smallHeight', smallHeight)
  12. if (smallHeight <= 256 && smallWidth > 140) { // 高度不够,宽度够
  13. // 让自定义菜单随鼠标的箭头位置移动
  14. that.$refs.rightMenu.style.left = params.event.offsetX + 45 + 'px'
  15. that.$refs.rightMenu.style.top = params.event.offsetY - 200 + 'px'
  16. } else if (smallHeight > 256 && smallWidth <= 140) { // 高度够,宽度不够
  17. that.$refs.rightMenu.style.left = params.event.offsetX - 52 + 'px'
  18. that.$refs.rightMenu.style.top = params.event.offsetY + 45 + 'px'
  19. } else if (smallHeight <= 256 && smallWidth <= 140) { // 高度不够,宽度不够
  20. that.$refs.rightMenu.style.left = params.event.offsetX - 52 + 'px'
  21. that.$refs.rightMenu.style.top = params.event.offsetY - 200 + 'px'
  22. } else { // 高度和宽度都够
  23. that.$refs.rightMenu.style.left = params.event.offsetX + 45 + 'px'
  24. that.$refs.rightMenu.style.top = params.event.offsetY + 45 + 'px'
  25. }

4.搜索

代码布局如下:

 主要代码:

  1. <el-input v-model="inputSearch" type="text" placeholder="请输入名称" style="width: 200px;" @keyup.enter.native="handleFilter"/>
  2. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleFilter">搜索</el-button>
inputSearch: '', // 搜索框
  1. /**
  2. * 搜索,输入名称时,实现搜索功能
  3. * */
  4. handleFilter() {
  5. const that = this
  6. var num = 0; // 记录查询到节点的数量
  7. function readNodes(nodes) {
  8. for (const item of nodes) { // js遍历树形数组结构
  9. if (item.children && item.children.length) {
  10. readNodes(item.children)
  11. }
  12. // 查询,名称中包含输入值就修改label颜色和字体大小
  13. if (item.name.indexOf(that.inputSearch) >= 0 && that.inputSearch != '') {
  14. item.label = {
  15. color: 'red',
  16. fontSize: '15'
  17. };
  18. num++;
  19. } else { // 否则为默认颜色和大小
  20. item.label = {
  21. color: '#666',
  22. fontSize: '9'
  23. };
  24. }
  25. }
  26. }
  27. readNodes(this.dataTree); // 调用时,要给data加[],因为原本的data不是一个正常的数组结构
  28. if (num > 0) { // 查询节点数量大于0的时候展开所有层级
  29. this.defaultOpt.series[0].initialTreeDepth = -1;
  30. } else { // 否则恢复初始的层级
  31. this.defaultOpt.series[0].initialTreeDepth = 2;
  32. }
  33. const chart = this.$refs.main
  34. if (chart) {
  35. echarts.init(chart).setOption(this.defaultOpt); // 重新设置一遍树图,不然不起效果
  36. }
  37. },

5.导出PNG

代码布局:

主要代码:

  1. toolbox: {
  2. show: true,
  3. top: 20,
  4. left: 20,
  5. feature: {
  6. restore: {
  7. title: '刷新'// 刷新echarts图标
  8. },
  9. saveAsImage: {
  10. title: '下载图片', // 鼠标悬停在下载图标上时,显示的文字
  11. name: 'network-topology'// 下载图片的文件名为network-topology.png
  12. }
  13. }
  14. },

6.高亮:这个高亮的效果我感觉不是很好,但是如果有朋友需要的话,就是下面用法

代码布局:

主要代码:

  1. emphasis: { // 高亮,个人感觉这个高亮的效果不是很好
  2. focus: 'descendant'
  3. },

7.查看节点是否还有子节点

代码布局:

主要代码:

  1. symbol: function(params, params1) {
  2. /**
  3. * 自定义图片后,不知道是否还有子节点未展开
  4. * 通过打印,发现没有展开的节点,和最后一层子节点的collapsed属性都为true
  5. * 所以判断collapsed为true,并且没有孩子节点的时候,就是还有没展开的子节点
  6. * 修改label的样式,来判断是否还存在子节点没展开
  7. */
  8. // console.log('params11111111111111111', params1)
  9. if (params1.collapsed == true && params1.data.children.length > 0) {
  10. params1.data.label = {
  11. color: '#0099cc',
  12. fontSize: '13',
  13. fontWeight: 'bold'
  14. }
  15. }
  16. },
  1. /**
  2. * 鼠标单机节点
  3. * (1)隐藏右键菜单
  4. * (2)判断子节点是否展开,修改label的样式
  5. */
  6. echarts.init(chart).on('click', function(params) {
  7. that.contextmenu = false
  8. that.$refs.rightMenu.style.display = 'none';
  9. // 解决树图点击展开图片不显示问题
  10. // echarts.init(chart).resize()
  11. console.log('params', params)
  12. if (params.collapsed == true && params.data.children.length > 0) {
  13. params.data.label = {
  14. color: '#0099cc',
  15. fontSize: '13',
  16. fontWeight: 'bold'
  17. }
  18. } else {
  19. params.data.label = {
  20. color: '#ffffff',
  21. fontSize: '9',
  22. fontWeight: 'normal'
  23. }
  24. }
  25. echarts.init(chart).resize()
  26. });

8.修改树图的展示方式

注:起初只是简单的实现了切换的效果,并没有注重细节主要如下:

代码布局:

主要代码:

  1. <el-button size="mini" @click="lrClick('LR')">左到右</el-button>
  2. <el-button size="mini" @click="rlClick('RL')">右到左</el-button>
  3. <el-button size="mini" @click="tbClick('TB')">上到下</el-button>
  4. <el-button size="mini" @click="btClick('BT')">下到上</el-button>
  1. /**
  2. * 左到右按钮点击
  3. * @param option 树图属性LR
  4. * */
  5. lrClick(option) {
  6. this.treeDisplay(option)
  7. },
  8. /**
  9. * 右到左按钮点击
  10. * @param option 树图属性RL
  11. * */
  12. rlClick(option) {
  13. this.treeDisplay(option)
  14. },
  15. /**
  16. * 上到下按钮点击
  17. * @param option 树图属性TB
  18. * */
  19. tbClick(option) {
  20. this.treeDisplay(option)
  21. },
  22. /**
  23. * 下到上按钮点击
  24. * @param option 树图属性BT
  25. * */
  26. btClick(option) {
  27. this.treeDisplay(option)
  28. },
  29. /**
  30. * 左到右,右到左,上到下,下到上按钮点击时,对拓扑属性的设置
  31. * */
  32. treeDisplay(option) {
  33. this.defaultOpt.series[0].orient = option;
  34. const chart = this.$refs.main
  35. if (chart) {
  36. echarts.init(chart).setOption(this.defaultOpt);
  37. }
  38. }

后期在实际开发中,发现,切换后,结点的label会挤在一起,非常丑,所以要对每种展示方式的label也要做调整,具体如下:

代码布局:

主要代码:

  1. type: 'tree',
  2. data: this.dataTree,
  3. left: '10%',
  4. right: '2%',
  5. top: '16%',
  6. bottom: '8%',
  7. label: {
  8. position: 'top',
  9. rotate: -90,
  10. verticalAlign: 'middle',
  11. align: 'right',
  12. color: '#fff',
  13. fontSize: 9
  14. },
  15. orient: 'TB',
  16. leaves: {
  17. label: {
  18. position: 'bottom',
  19. rotate: -90,
  20. verticalAlign: 'middle',
  21. align: 'left'
  22. }
  23. },
  24. symbolSize: [30, 30],
  1. /**
  2. * 左到右按钮点击
  3. * @param option 树图属性LR
  4. * */
  5. lrClick(option) {
  6. this.treeDisplay(option)
  7. },
  8. /**
  9. * 右到左按钮点击
  10. * @param option 树图属性RL
  11. * */
  12. rlClick(option) {
  13. this.treeDisplay(option)
  14. },
  15. /**
  16. * 上到下按钮点击
  17. * @param option 树图属性TB
  18. * */
  19. tbClick(option) {
  20. this.treeDisplay(option)
  21. },
  22. /**
  23. * 下到上按钮点击
  24. * @param option 树图属性BT
  25. * */
  26. btClick(option) {
  27. this.treeDisplay(option)
  28. },
  29. /**
  30. * 左到右,右到左,上到下,下到上按钮点击时,对拓扑属性的设置
  31. * orient:修改拓扑图展示的方向
  32. * label、leaves修改结点旁边的文字展示
  33. * */
  34. treeDisplay(option) {
  35. this.defaultOpt.series[0].orient = option;
  36. // console.log('option', option)
  37. if (option == 'LR') {
  38. this.defaultOpt.series[0].label = {
  39. position: 'left',
  40. rotate: 0,
  41. verticalAlign: 'middle',
  42. align: 'right',
  43. color: '#fff',
  44. fontSize: 9
  45. }
  46. this.defaultOpt.series[0].leaves = {
  47. label: {
  48. position: 'right',
  49. rotate: 0,
  50. verticalAlign: 'middle',
  51. align: 'left'
  52. }
  53. }
  54. }
  55. if (option == 'RL') {
  56. this.defaultOpt.series[0].label = {
  57. position: 'right',
  58. rotate: 0,
  59. verticalAlign: 'middle',
  60. align: 'left',
  61. color: '#fff',
  62. fontSize: 9
  63. }
  64. this.defaultOpt.series[0].leaves = {
  65. label: {
  66. position: 'left',
  67. rotate: 0,
  68. verticalAlign: 'middle',
  69. align: 'right'
  70. }
  71. }
  72. }
  73. if (option == 'TB') {
  74. this.defaultOpt.series[0].label = {
  75. position: 'top',
  76. rotate: -90,
  77. verticalAlign: 'middle',
  78. align: 'right',
  79. color: '#fff',
  80. fontSize: 9
  81. }
  82. this.defaultOpt.series[0].leaves = {
  83. label: {
  84. position: 'bottom',
  85. rotate: -90,
  86. verticalAlign: 'middle',
  87. align: 'left'
  88. }
  89. }
  90. }
  91. if (option == 'BT') {
  92. this.defaultOpt.series[0].label = {
  93. position: 'bottom',
  94. rotate: 90,
  95. verticalAlign: 'middle',
  96. align: 'right',
  97. color: '#fff',
  98. fontSize: 9
  99. }
  100. this.defaultOpt.series[0].leaves = {
  101. label: {
  102. position: 'top',
  103. rotate: 90,
  104. verticalAlign: 'middle',
  105. align: 'left'
  106. }
  107. }
  108. }
  109. const chart = this.$refs.main
  110. if (chart) {
  111. echarts.init(chart).setOption(this.defaultOpt);
  112. }
  113. },

 完整代码:

 topoTree.js

  1. import Mock from 'mockjs'
  2. const tree = [
  3. {
  4. 'id': '0',
  5. 'name': '外部网络',
  6. 'type': 'Internet',
  7. 'children': [
  8. {
  9. 'id': '1',
  10. 'name': '交换机',
  11. 'type': 'switch',
  12. 'IP': '192.168.30.126',
  13. 'MAC': 'b0:98:6e:bf:6r:4c',
  14. 'deviceType': '交换机',
  15. 'deviceNum': 'HUAWEI',
  16. 'children': [
  17. {
  18. 'id': '2',
  19. 'name': '笔记本',
  20. 'type': 'switch',
  21. 'IP': '192.168.30.126',
  22. 'MAC': 'b0:98:6e:bf:6r:4c'
  23. },
  24. {
  25. 'id': '3',
  26. 'name': '计算机',
  27. 'type': 'computer',
  28. 'IP': '192.168.30.126',
  29. 'MAC': 'b0:98:6e:bf:6r:4c',
  30. 'children': [
  31. {
  32. 'id': '4',
  33. 'name': '计算机1',
  34. 'type': 'computer',
  35. 'IP': '192.168.30.126',
  36. 'MAC': 'b0:98:6e:bf:6r:4c'
  37. },
  38. {
  39. 'id': '5',
  40. 'name': '计算机2',
  41. 'type': 'computer',
  42. 'IP': '192.168.30.126',
  43. 'MAC': 'b0:98:6e:bf:6r:4c'
  44. },
  45. {
  46. 'id': '6',
  47. 'name': '计算机3',
  48. 'type': 'computer',
  49. 'IP': '192.168.30.126',
  50. 'MAC': 'b0:98:6e:bf:6r:4c'
  51. },
  52. {
  53. 'id': '7',
  54. 'name': '计算机4',
  55. 'type': 'computer',
  56. 'IP': '192.168.30.126',
  57. 'MAC': 'b0:98:6e:bf:6r:4c',
  58. 'lastLoginTime': '2020-8-26'
  59. }
  60. ]
  61. },
  62. {
  63. 'id': '8',
  64. 'name': '路由器',
  65. 'type': 'rooter',
  66. 'IP': '192.168.30.126',
  67. 'MAC': 'b0:98:6e:bf:6r:4c',
  68. 'deviceType': '路由器'
  69. },
  70. {
  71. 'id': '9',
  72. 'name': '服务器',
  73. 'type': 'service',
  74. 'IP': '192.168.30.126',
  75. 'MAC': 'b0:98:6e:bf:6r:4c',
  76. 'deviceType': '服务器'
  77. },
  78. {
  79. 'id': '10',
  80. 'name': '打印机',
  81. 'type': 'print',
  82. 'IP': '192.168.30.126',
  83. 'MAC': 'b0:98:6e:bf:6r:4c',
  84. 'deviceType': '打印机'
  85. },
  86. {
  87. 'id': '11',
  88. 'name': '计算机',
  89. 'type': 'computer',
  90. 'IP': '192.168.30.126',
  91. 'MAC': 'b0:98:6e:bf:6r:4c',
  92. 'lastLoginTime': '2020-8-26'
  93. }
  94. ]
  95. },
  96. {
  97. 'id': '12',
  98. 'name': '无线交换机',
  99. 'type': 'switch',
  100. 'IP': '192.168.30.126',
  101. 'MAC': 'b0:98:6e:bf:6r:4c',
  102. 'deviceType': '交换机',
  103. 'deviceNum': 'HUAWEI',
  104. 'children': [
  105. {
  106. 'id': '13',
  107. 'name': '手机',
  108. 'type': 'phone',
  109. 'IP': '192.168.30.126',
  110. 'MAC': 'b0:98:6e:bf:6r:4c',
  111. 'deviceType': '手机'
  112. },
  113. {
  114. 'id': '14',
  115. 'name': '平板',
  116. 'type': 'phone',
  117. 'IP': '192.168.30.126',
  118. 'MAC': 'b0:98:6e:bf:6r:4c',
  119. 'deviceType': '平板'
  120. }
  121. ]
  122. },
  123. {
  124. 'id': '15',
  125. 'name': 'hub',
  126. 'type': 'hub',
  127. 'children': [
  128. {
  129. 'id': '16',
  130. 'name': '计算机',
  131. 'type': 'computer',
  132. 'IP': '192.168.30.126',
  133. 'MAC': 'b0:98:6e:bf:6r:4c',
  134. 'deviceType': '计算机'
  135. },
  136. {
  137. 'id': '17',
  138. 'name': '笔记本',
  139. 'type': 'phone',
  140. 'IP': '192.168.30.126',
  141. 'MAC': 'b0:98:6e:bf:6r:4c',
  142. 'deviceType': '手机'
  143. },
  144. {
  145. 'id': '18',
  146. 'name': '打印机',
  147. 'type': 'print',
  148. 'IP': '192.168.30.126',
  149. 'MAC': 'b0:98:6e:bf:6r:4c',
  150. 'deviceType': '打印机'
  151. },
  152. {
  153. 'id': '19',
  154. 'name': '路由器',
  155. 'type': 'rooter',
  156. 'IP': '192.168.30.126',
  157. 'MAC': 'b0:98:6e:bf:6r:4c',
  158. 'deviceType': '路由器'
  159. }
  160. ]
  161. }
  162. ]
  163. }
  164. ]
  165. Mock.mock('/topo/tree', 'get', tree)
  166. export default [
  167. ]

index.vue

  1. <template>
  2. <div class="app-container" @contextmenu.prevent="" @click="mainClick">
  3. <el-input v-model="inputSearch" type="text" placeholder="请输入名称" style="width: 200px;" @keyup.enter.native="handleFilter"/>
  4. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleFilter">搜索</el-button>
  5. <el-button size="mini" @click="lrClick('LR')">左到右</el-button>
  6. <el-button size="mini" @click="rlClick('RL')">右到左</el-button>
  7. <el-button size="mini" @click="tbClick('TB')">上到下</el-button>
  8. <el-button size="mini" @click="btClick('BT')">下到上</el-button>
  9. <div ref="main" class="app-container"></div>
  10. <!--右键弹出菜单-->
  11. <div ref="rightMenu" class="menu" style="display: none;">
  12. <ul>
  13. <li>下线</li>
  14. <li>上线</li>
  15. <li>掉线</li>
  16. </ul>
  17. </div>
  18. </div>
  19. </template>
  20. <script>
  21. import * as echarts from 'echarts'
  22. require('echarts/theme/macarons')
  23. import axios from 'axios'
  24. export default {
  25. name: 'TopologyDisplay',
  26. props: {
  27. },
  28. data() {
  29. return {
  30. inputSearch: '', // 搜索框
  31. dataTree: [], // data数据
  32. defaultOpt: {}, // 拓扑图属性设置
  33. contextmenu: false // 鼠标右键事件
  34. }
  35. },
  36. watch: {
  37. },
  38. mounted: function() {
  39. this.setOptions()
  40. this.day_init()
  41. },
  42. created() {
  43. },
  44. methods: {
  45. /**
  46. * 实现自适应
  47. * */
  48. day_init() {
  49. const self = this; // 因为箭头函数会改变this指向,指向windows。所以先把this保存
  50. const todaypieId = this.$refs.main
  51. if (!todaypieId) {
  52. return false;
  53. } else {
  54. setTimeout(() => {
  55. window.onresize = function() {
  56. // self.chart = echarts.init(self.$refs.myEchart);
  57. self.chart_today = echarts.init(
  58. todaypieId
  59. );
  60. self.chart_today.resize();
  61. };
  62. }, 20);
  63. }
  64. },
  65. /**
  66. * 遍历树节点信息
  67. * @param nodes 拓扑图节点数据
  68. * 通过某种状态区分显示拓扑图的节点图片或连线颜色
  69. * */
  70. readNodes(nodes) {
  71. for (const item of nodes) { // js遍历树形数组结构
  72. if (item.children && item.children.length) {
  73. this.readNodes(item.children)
  74. }
  75. if (item.name == '服务器') {
  76. item.symbol = 'image://' + require('@/assets/topo/server_online_unreg.png');
  77. // 修改连线颜色
  78. item.lineStyle = {
  79. color: '#ff0000'
  80. }
  81. } else {
  82. item.symbol = 'image://' + require('@/assets/topo/internet.png');
  83. item.lineStyle = {
  84. color: '#2E8B57'
  85. }
  86. }
  87. }
  88. },
  89. /**
  90. * 对拓扑图属性的设置
  91. * */
  92. setOptions() {
  93. axios.get('/topo/tree').then(res => {
  94. console.log('树节点数据', JSON.parse(JSON.stringify(res.data)));
  95. this.dataTree = res.data
  96. if (this.dataTree) {
  97. this.readNodes(this.dataTree) // 在设置属性之前遍历显示图片,否则节点图片不生效
  98. this.defaultOpt = {
  99. tooltip: {
  100. trigger: 'item',
  101. triggerOn: 'mousemove',
  102. enterable: true, // 鼠标是否可进入提示框浮层中
  103. formatter: this.formatterHover// 修改鼠标悬停显示的内容
  104. },
  105. toolbox: {
  106. show: true,
  107. top: 20,
  108. left: 20,
  109. feature: {
  110. restore: {
  111. title: '刷新'// 刷新echarts图标
  112. },
  113. saveAsImage: {
  114. title: '下载图片', // 鼠标悬停在下载图标上时,显示的文字
  115. name: 'network-topology'// 下载图片的文件名为network-topology.png
  116. }
  117. }
  118. },
  119. series: [
  120. {
  121. type: 'tree',
  122. data: this.dataTree,
  123. left: '10%',
  124. right: '2%',
  125. top: '16%',
  126. bottom: '8%',
  127. label: {
  128. position: 'top',
  129. rotate: -90,
  130. verticalAlign: 'middle',
  131. align: 'right',
  132. color: '#fff',
  133. fontSize: 9
  134. },
  135. orient: 'TB',
  136. leaves: {
  137. label: {
  138. position: 'bottom',
  139. rotate: -90,
  140. verticalAlign: 'middle',
  141. align: 'left'
  142. }
  143. },
  144. symbolSize: [30, 30],
  145. symbol: function(params, params1) {
  146. /**
  147. * 自定义图片后,不知道是否还有子节点未展开
  148. * 通过打印,发现没有展开的节点,和最后一层子节点的collapsed属性都为true
  149. * 所以判断collapsed为true,并且没有孩子节点的时候,就是还有没展开的子节点
  150. * 修改label的样式,来判断是否还存在子节点没展开
  151. */
  152. // console.log('params11111111111111111', params1)
  153. if (params1.collapsed == true && params1.data.children.length > 0) {
  154. params1.data.label = {
  155. color: '#0099cc',
  156. fontSize: '13',
  157. fontWeight: 'bold'
  158. }
  159. }
  160. },
  161. edgeForkPosition: '72%',
  162. emphasis: { // 高亮,个人感觉这个高亮的效果不是很好
  163. focus: 'descendant'
  164. },
  165. initialTreeDepth: 2, // 树图初始展开层级
  166. roam: true, // 鼠标缩放,拖拽整颗树
  167. expandAndCollapse: true, // 无关的子树折叠收起
  168. animationDuration: 550,
  169. animationDurationUpdate: 750,
  170. width: '85%'// 组件宽度
  171. }
  172. ]
  173. }
  174. const chart = this.$refs.main
  175. if (chart) {
  176. const that = this
  177. // console.log('echarts', echarts)
  178. echarts.init(chart).setOption(this.defaultOpt) // 将画布添加到页面中
  179. /**
  180. * 鼠标右键,显示右键菜单
  181. */
  182. echarts.init(chart).on('contextmenu', function(params) {
  183. // console.log('params', params)
  184. that.contextmenu = true
  185. // 去掉悬停
  186. that.$refs.main.children[1].style.display = 'none'
  187. that.$refs.rightMenu.style.display = 'block';
  188. // // //让自定义菜单随鼠标的箭头位置移动
  189. that.$refs.rightMenu.style.left = params.event.offsetX + 45 + 'px'
  190. that.$refs.rightMenu.style.top = params.event.offsetY + 45 + 'px'
  191. });
  192. /**
  193. * 鼠标单机节点
  194. * (1)隐藏右键菜单
  195. * (2)判断子节点是否展开,修改label的样式
  196. */
  197. echarts.init(chart).on('click', function(params) {
  198. that.contextmenu = false
  199. that.$refs.rightMenu.style.display = 'none';
  200. // 解决树图点击展开图片不显示问题
  201. // echarts.init(chart).resize()
  202. // console.log('params.collapsed', params.collapsed)
  203. if (params.collapsed == true) {
  204. params.data.label = {
  205. color: '#0099cc',
  206. fontSize: '13',
  207. fontWeight: 'bold'
  208. }
  209. } else {
  210. params.data.label = {
  211. color: '#ffffff',
  212. fontSize: '9',
  213. fontWeight: 'normal'
  214. }
  215. }
  216. echarts.init(chart).resize() // 不写修改的样式不生效
  217. });
  218. // 解决树图首次加载图片不显示问题
  219. echarts.init(chart).resize()
  220. }
  221. }
  222. })
  223. },
  224. /**
  225. * 点击页面其它地方时,隐藏右键菜单
  226. * */
  227. mainClick() {
  228. this.contextmenu = false
  229. this.$refs.rightMenu.style.display = 'none';
  230. },
  231. /**
  232. * 鼠标悬停显示详情
  233. * @param params
  234. * @returns {string}
  235. */
  236. formatterHover(params) {
  237. // console.log(params);
  238. var deviceType = params.data.type;
  239. var imgPath = params.data.symbol;
  240. // 图片地址截取,因为echarts修改图片的时候有一个------image://---前缀,前缀后面的才是图片真正的地址
  241. var imgPathSrc = imgPath.split('image://')[1];
  242. // console.log('str',imgPathSrc);
  243. if (deviceType === 'Internet' || deviceType === 'hub') {
  244. return "<img src='" + imgPathSrc + " ' width='30px' height='30px'>" + '<span style="padding:0 5px;font-size: 14px;">' + params.data.name + '</span>';
  245. } if (deviceType === 'switch') {
  246. return "<img src='" + imgPathSrc + " ' width='30px' height='30px'>" + '<span style="padding: 0 5px;font-size: 14px;">设备类型:' + params.data.name + '</span>' + '<br>' +
  247. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">IP:' + params.data.IP + '</span>' + '<br>' +
  248. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">MAC:' + params.data.MAC + '</span>' + '<br>';
  249. // +'<button style="padding:2px 5px;border:none;outline:none;color:#ffffff;border-radius: 5px;background:rgba(0,0,0,0.5);"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> IP列表</button>'
  250. // +'<button style="padding:2px 5px;border:none;outline:none;color:#ffffff;border-radius: 5px;background:rgba(0,0,0,0.5);margin-left: 10px;"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> MAC列表</button>';
  251. } else {
  252. return "<img src='" + imgPathSrc + " ' width='30px' height='30px'>" + '<span style="padding: 0 5px;font-size: 14px;">设备类型:' + params.data.name + '</span>' + '<br>' +
  253. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">IP:' + params.data.IP + '</span>' + '<br>' +
  254. '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">MAC:' + params.data.MAC + '</span>' + '<br>';
  255. }
  256. },
  257. /**
  258. * 搜索,输入名称时,实现搜索功能
  259. * */
  260. handleFilter() {
  261. const that = this
  262. var num = 0; // 记录查询到节点的数量
  263. function readNodes(nodes) {
  264. for (const item of nodes) { // js遍历树形数组结构
  265. if (item.children && item.children.length) {
  266. readNodes(item.children)
  267. }
  268. // 查询,名称中包含输入值就修改label颜色和字体大小
  269. if (item.name.indexOf(that.inputSearch) >= 0 && that.inputSearch != '') {
  270. item.label = {
  271. color: 'red',
  272. fontSize: '15'
  273. };
  274. num++;
  275. } else { // 否则为默认颜色和大小
  276. item.label = {
  277. color: '#666',
  278. fontSize: '9'
  279. };
  280. }
  281. }
  282. }
  283. readNodes(this.dataTree); // 调用时,要给data加[],因为原本的data不是一个正常的数组结构
  284. if (num > 0) { // 查询节点数量大于0的时候展开所有层级
  285. this.defaultOpt.series[0].initialTreeDepth = -1;
  286. } else { // 否则恢复初始的层级
  287. this.defaultOpt.series[0].initialTreeDepth = 2;
  288. }
  289. const chart = this.$refs.main
  290. if (chart) {
  291. echarts.init(chart).setOption(this.defaultOpt); // 重新设置一遍树图,不然不起效果
  292. }
  293. },
  294. /**
  295. * 左到右按钮点击
  296. * @param option 树图属性LR
  297. * */
  298. lrClick(option) {
  299. this.treeDisplay(option)
  300. },
  301. /**
  302. * 右到左按钮点击
  303. * @param option 树图属性RL
  304. * */
  305. rlClick(option) {
  306. this.treeDisplay(option)
  307. },
  308. /**
  309. * 上到下按钮点击
  310. * @param option 树图属性TB
  311. * */
  312. tbClick(option) {
  313. this.treeDisplay(option)
  314. },
  315. /**
  316. * 下到上按钮点击
  317. * @param option 树图属性BT
  318. * */
  319. btClick(option) {
  320. this.treeDisplay(option)
  321. },
  322. /**
  323. * 左到右,右到左,上到下,下到上按钮点击时,对拓扑属性的设置
  324. * orient:修改拓扑图展示的方向
  325. * label、leaves修改结点旁边的文字展示
  326. * */
  327. treeDisplay(option) {
  328. this.defaultOpt.series[0].orient = option;
  329. // console.log('option', option)
  330. if (option == 'LR') {
  331. this.defaultOpt.series[0].label = {
  332. position: 'left',
  333. rotate: 0,
  334. verticalAlign: 'middle',
  335. align: 'right',
  336. color: '#fff',
  337. fontSize: 9
  338. }
  339. this.defaultOpt.series[0].leaves = {
  340. label: {
  341. position: 'right',
  342. rotate: 0,
  343. verticalAlign: 'middle',
  344. align: 'left'
  345. }
  346. }
  347. }
  348. if (option == 'RL') {
  349. this.defaultOpt.series[0].label = {
  350. position: 'right',
  351. rotate: 0,
  352. verticalAlign: 'middle',
  353. align: 'left',
  354. color: '#fff',
  355. fontSize: 9
  356. }
  357. this.defaultOpt.series[0].leaves = {
  358. label: {
  359. position: 'left',
  360. rotate: 0,
  361. verticalAlign: 'middle',
  362. align: 'right'
  363. }
  364. }
  365. }
  366. if (option == 'TB') {
  367. this.defaultOpt.series[0].label = {
  368. position: 'top',
  369. rotate: -90,
  370. verticalAlign: 'middle',
  371. align: 'right',
  372. color: '#fff',
  373. fontSize: 9
  374. }
  375. this.defaultOpt.series[0].leaves = {
  376. label: {
  377. position: 'bottom',
  378. rotate: -90,
  379. verticalAlign: 'middle',
  380. align: 'left'
  381. }
  382. }
  383. }
  384. if (option == 'BT') {
  385. this.defaultOpt.series[0].label = {
  386. position: 'bottom',
  387. rotate: 90,
  388. verticalAlign: 'middle',
  389. align: 'right',
  390. color: '#fff',
  391. fontSize: 9
  392. }
  393. this.defaultOpt.series[0].leaves = {
  394. label: {
  395. position: 'top',
  396. rotate: 90,
  397. verticalAlign: 'middle',
  398. align: 'left'
  399. }
  400. }
  401. }
  402. const chart = this.$refs.main
  403. if (chart) {
  404. echarts.init(chart).setOption(this.defaultOpt);
  405. }
  406. }
  407. }
  408. }
  409. </script>
  410. <style scoped>
  411. .menu{
  412. /*这个样式不写,右键弹框会一直显示在画布的左下角*/
  413. position: absolute;
  414. background: rgba(255, 255, 255, 1);
  415. border-radius: 5px;
  416. left: -99999px;
  417. top: -999999px;
  418. }
  419. .menu ul{
  420. list-style: none;
  421. padding: 0;
  422. margin: 0;
  423. }
  424. .menu ul li{
  425. cursor: pointer;
  426. padding: 5px 10px;
  427. color: #000000;
  428. border-bottom: 1px dashed #a9a9a9;
  429. font-size: 14px;
  430. }
  431. .menu ul li:last-child{
  432. border-bottom: none;
  433. }
  434. </style>
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/427595
推荐阅读
相关标签
  

闽ICP备14008679号