vue2横向树状图(可缩放,划过显示弹窗,点击节点高亮路径)

2025-11-20更新----------------

新增点击节点路径高亮功能,可以在我的gitee库中看示例,

代码拉下来pnpm i, pnpm run serve

https://gitee.com/zrr89/zrr-org-tree

2025-11-20更新完毕----------------

用js写的,主要用的h函数渲染,文件目录(图如下),放到components可直接当作组件调用

org-tree.vue

<template>
  <div class="org-tree-container outBox">
    <div
      class="org-tree"
      :class="{ horizontal, collapsable }"
      v-drag
      id="ddd"
      :style="{ transform: `scale(${zoomFactor})` }"
      @mousewheel.prevent="handleWheel"
    >
      <org-tree-node
        :data="data"
        :props="props"
        :zoomFactor="zoomFactor"
        :horizontal="horizontal"
        :label-width="labelWidth"
        :collapsable="collapsable"
        :render-content="renderContent"
        :label-class-name="labelClassName"
        @on-expand="(e, data) => $emit('on-expand', e, data)"
        @on-expand-mouseover="
          (e, data) => $emit('on-expand-mouseover', e, data)
        "
        @on-expand-mouseout="(e, data) => $emit('on-expand-mouseout', e, data)"
        @on-node-click="
          (e, data) => {
            $emit('on-node-click', e, data);
          }
        "
        @on-node-mouseover="(e, data) => $emit('on-node-mouseover', e, data)"
        @on-node-mouseout="(e, data) => $emit('on-node-mouseout', e, data)"
      />
    </div>
  </div>
</template>

<script>
import render from "./node";

export default {
  name: "OrgTree",
  components: {
    OrgTreeNode: {
      render,
      functional: true,
    },
  },
  data() {
    return {
      zoom: this.zoomFactor,
    };
  },
  props: {
    data: {
      type: Object,
      required: true,
    },
    props: {
      type: Object,
      default: () => ({
        label: "deviceName",
        expand: "expand",
        childNodeList: "childNodeList",
      }),
    },
    zoomFactor: Number,
    horizontal: Boolean,
    collapsable: Boolean,
    renderContent: Function,
    labelWidth: [String, Number],
    labelClassName: [Function, String],
  },
  directives: {
    drag: {
      // 以下代码大家可以自己提出去封装一下
      inserted: function (el) {
        el.style.cursor = "move";
        let firstTime = "";
        let lastTime = "";
        el.onmousedown = function (e) {
          let disx = e.pageX - el.offsetLeft;
          let disy = e.pageY - el.offsetTop;
          document.getElementById("ddd").setAttribute("data-flag", false);
          firstTime = new Date().getTime();
          document.onmousemove = function (e) {
            let x = e.pageX - disx;
            let y = e.pageY - disy;
            let maxX =
              document.body.clientWidth -
              parseInt(window.getComputedStyle(el).width);
            let maxY =
              document.body.clientHeight -
              parseInt(window.getComputedStyle(el).height);

            el.style.left = x + "px";
            el.style.top = y + "px";

            if (x < 0) {
              x = 0;
            } else if (x > maxX) {
              x = maxX;
            }

            if (y < 0) {
              y = 0;
            } else if (y > maxY) {
              y = maxY;
            }

            let et = e || window.event;
            et.preventDefault();
          };
          document.onmouseup = function () {
            lastTime = new Date().getTime();
            if (lastTime - firstTime < 150) {
              document.getElementById("ddd").setAttribute("data-flag", true);
            }
            document.onmousemove = document.onmouseup = null;
          };
        };
      },
    },
  },
  methods: {
    handleWheel(event) {
      this.zoom = this.zoomFactor;
      event.preventDefault();
      const delta = event.wheelDelta ? event.wheelDelta : -event.deltaY;
      if (delta > 0) {
        this.zoom = this.zoom + 0.05;
      } else {
        this.zoom = this.zoom > 0 ? this.zoom - 0.05 : 0;
      }
      this.$emit("on-mousewheel", this.zoom);
    },
  },
};
</script>
<style lang="less" scoped>
.outBox {
  height: 100%;
  width: 100%;
  position: relative;
  overflow: hidden;
}
#ddd {
  width: 100%;
  height: 100%;
  position: absolute;
  overflow: hidden;
}
.org-tree-container {
  position: relative;
  display: inline-block;
  padding: 15px;
  background-color: #fff;
}
.org-tree {
  position: relative;
  display: table;
  text-align: center;
}
.org-tree:before,
.org-tree:after {
  content: "";
  display: table;
}
.org-tree:after {
  clear: both;
}
.org-tree-node,
.org-tree-node-childNodeList {
  position: relative;
  margin: 0;
  padding: 0;
  list-style-type: none;
}
.org-tree-node:before,
.org-tree-node-childNodeList:before,
.org-tree-node:after,
.org-tree-node-childNodeList:after {
  transition: all 0.35s;
}
.org-tree-node-label {
  position: relative;
  display: inline-block;
}
.org-tree-node-label .org-tree-node-label-inner {
  text-align: center;
  border-radius: 3px;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
  // tree节点不换行
  // padding: 6px 20px;
  // white-space: nowrap;
  // tree节点换行
  padding: 6px 10px;
  width: 150px;
  word-wrap: break-word;
}
.org-tree-node-btn {
  position: absolute;
  top: 100%;
  left: 50%;
  width: 20px;
  height: 20px;
  z-index: 10;
  margin-left: -11px;
  margin-top: 9px;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 50%;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.15);
  cursor: pointer;
  transition: all 0.35s ease;
}
.org-tree-node-btn:hover {
  background-color: #e7e8e9;
  transform: scale(1.15);
}
.org-tree-node-btn:before,
.org-tree-node-btn:after {
  content: "";
  position: absolute;
}
.org-tree-node-btn:before {
  top: 50%;
  left: 4px;
  right: 4px;
  height: 0;
  border-top: 1px solid #ccc;
}
.org-tree-node-btn:after {
  top: 4px;
  left: 50%;
  bottom: 4px;
  width: 0;
  border-left: 1px solid #ccc;
}
.org-tree-node-btn.expanded:after {
  border: none;
}
.org-tree-node {
  padding-top: 20px;
  display: table-cell;
  vertical-align: top;
}
.org-tree-node.is-leaf,
.org-tree-node.collapsed {
  padding-left: 10px;
  padding-right: 10px;
}
.org-tree-node:before,
.org-tree-node:after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 50%;
  height: 19px;
}
.org-tree-node:after {
  left: 50%;
  border-left: 1px solid #ddd;
}
.org-tree-node:not(:first-child):before,
.org-tree-node:not(:last-child):after {
  border-top: 1px solid #ddd;
}
.collapsable .org-tree-node.collapsed {
  padding-bottom: 30px;
}
.collapsable .org-tree-node.collapsed .org-tree-node-label:after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  width: 50%;
  height: 20px;
  border-right: 1px solid #ddd;
}
.org-tree > .org-tree-node {
  padding-top: 0;
}
.org-tree > .org-tree-node:after {
  border-left: 0;
}
.org-tree-node-childNodeList {
  padding-top: 20px;
  display: table;
}
.org-tree-node-childNodeList:before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 0;
  height: 20px;
  border-left: 1px solid #ddd;
}
.org-tree-node-childNodeList:after {
  content: "";
  display: table;
  clear: both;
}
.horizontal .org-tree-node {
  display: table-cell;
  float: none;
  padding-top: 0;
  padding-left: 20px;
}
.horizontal .org-tree-node.is-leaf,
.horizontal .org-tree-node.collapsed {
  padding-top: 10px;
  padding-bottom: 10px;
}
.horizontal .org-tree-node:before,
.horizontal .org-tree-node:after {
  width: 19px;
  height: 50%;
}
.horizontal .org-tree-node:after {
  top: 50%;
  left: 0;
  border-left: 0;
}
.horizontal .org-tree-node:only-child:before {
  top: 1px;
  border-bottom: 1px solid #ddd;
}
.horizontal .org-tree-node:not(:first-child):before,
.horizontal .org-tree-node:not(:last-child):after {
  border-top: 0;
  border-left: 1px solid #ddd;
}
.horizontal .org-tree-node:not(:only-child):after {
  border-top: 1px solid #ddd;
}
.horizontal .org-tree-node .org-tree-node-inner {
  display: table;
}
.horizontal .org-tree-node-label {
  display: table-cell;
  vertical-align: middle;
}
.horizontal.collapsable .org-tree-node.collapsed {
  padding-right: 30px;
}
.horizontal.collapsable .org-tree-node.collapsed .org-tree-node-label:after {
  top: 0;
  left: 100%;
  width: 20px;
  height: 50%;
  border-right: 0;
  border-bottom: 1px solid #ddd;
}
.horizontal .org-tree-node-btn {
  top: 50%;
  left: 100%;
  margin-top: -11px;
  margin-left: 9px;
}
.horizontal > .org-tree-node:only-child:before {
  border-bottom: 0;
}
.horizontal .org-tree-node-childNodeList {
  display: table-cell;
  padding-top: 0;
  padding-left: 20px;
}
.horizontal .org-tree-node-childNodeList:before {
  top: 50%;
  left: 0;
  width: 20px;
  height: 0;
  border-left: 0;
  border-top: 1px solid #ddd;
}
.horizontal .org-tree-node-childNodeList:after {
  display: none;
}
.horizontal .org-tree-node-childNodeList > .org-tree-node {
  display: block;
}

/* 第一层 */
.org-tree > .org-tree-node > .org-tree-node-label > .org-tree-node-label-inner {
  display: flex;
  position: relative;
  justify-content: center;
  color: #000;
  background: #fff;
  cursor: pointer;
  user-select: none;
  width: 150px;
  height: 170px;
}
/* 2 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #36a4fd;
  color: #fff;
}

/* 3 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #c2d4ff;
  color: #4665b0;
}
/* 4 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #9dd4e9;
  color: #4794b1;
}
/* 第5层 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #e7f1fd;
  color: #5b9be5;
}
/* 第6层 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #fecece;
  color: #f47171;
}
/* 第7层 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #ffe4b4;
  color: #dea540;
}
/* 第8层 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #c8efef;
  color: #76bebe;
}
/* 第9层 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #e6d7f3;
  color: #bd99dd;
}
/* 第10层 */
.org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-childNodeList
  > .org-tree-node
  > .org-tree-node-label
  > .org-tree-node-label-inner {
  background-color: #f4f7fe;
  color: #9eb1dd;
}
/* 每层多.org-tree-node-childNodeList>.org-tree-node */
/* 节点.org-tree-node-label>.org-tree-node-label-inner*/
</style>

node.js

// 判断是否叶子节点
const isLeaf = (data, prop) => {
  return !(Array.isArray(data[prop]) && data[prop].length > 0);
};
// console.info('Thank you for using vue-tree-color \nIf you have any questions about this plug-in, please contact me in the following ways \nWeChat: yanjiahui12345 \nWeChat official number: Web_Miao')
// 创建 node 节点
export const renderNode = (h, data, context) => {
  const { props } = context;
  const cls = ["org-tree-node"];
  const childNodes = [];
  const childNodeList = data[props.props.childNodeList];

  if (isLeaf(data, props.props.childNodeList)) {
    cls.push("is-leaf");
  } else if (props.collapsable && !data[props.props.expand]) {
    cls.push("collapsed");
  }

  childNodes.push(renderLabel(h, data, context));

  if (!props.collapsable || data[props.props.expand]) {
    childNodes.push(renderChildren(h, childNodeList, context));
  }

  return h(
    "div",
    {
      domProps: {
        className: cls.join(" "),
      },
    },
    childNodes
  );
};

// 创建展开折叠按钮
export const renderBtn = (h, data, { props, listeners }) => {
  const expandHandler = listeners["on-expand"];
  const expandMouseover = listeners["on-expand-mouseover"];
  const expandMouseout = listeners["on-expand-mouseout"];

  let cls = ["org-tree-node-btn"];

  if (data[props.props.expand]) {
    cls.push("expanded");
  }

  return h("span", {
    domProps: {
      className: cls.join(" "),
    },
    on: {
      click: (e) => expandHandler && expandHandler(e, data),
      mouseover: (e) => expandMouseover && expandMouseover(e, data),
      mouseout: (e) => expandMouseout && expandMouseout(e, data),
    },
  });
};

// 创建 label 节点
export const renderLabel = (h, data, context) => {
  const { props, listeners } = context;
  const renderContent = props.renderContent;
  // event handlers
  const clickHandler = listeners["on-node-click"];
  const mouseOverHandler = listeners["on-node-mouseover"];
  const mouseOutHandler = listeners["on-node-mouseout"];

  const childNodes = [];
  if (typeof renderContent === "function") {
    let vnode = renderContent(h, data);
    let str = ``;
    if (vnode.deviceNo) {
      str = `${vnode.deviceName}<br/>${vnode.deviceNo}`;
    } else {
      str = `${vnode.deviceName}`;
    }
    vnode && childNodes.push(str);
  }
  if (props.collapsable && !isLeaf(data, props.props.childNodeList)) {
    childNodes.push(renderBtn(h, data, context));
  }

  const cls = ["org-tree-node-label-inner"];
  let {
    labelWidth,
    labelClassName,
    selectedClassName,
    selectedKey,
    judge,
    NodeClass,
  } = props;

  if (typeof labelWidth === "number") {
    labelWidth += "px";
  }

  if (typeof labelClassName === "function") {
    labelClassName = labelClassName(data);
  }

  labelClassName && cls.push(labelClassName);

  // add selected class and key from props
  if (typeof selectedClassName === "function") {
    selectedClassName = selectedClassName(data);
  }

  selectedClassName &&
    selectedKey &&
    data[selectedKey] &&
    cls.push(selectedClassName);

  return h(
    "div",
    {
      domProps: {
        className: "org-tree-node-label",
      },
    },
    [
      h("div", {
        domProps: {
          className:
            ChangeTheColor(data, judge, NodeClass) +
            " org-tree-node-label-inner",
          innerHTML: childNodes[0],
        },
        style: {
          width: labelWidth,
        },
        on: {
          click: (e) => clickHandler && clickHandler(e, data),
          mouseover: (e) => mouseOverHandler && mouseOverHandler(e, data),
          mouseout: (e) => mouseOutHandler && mouseOutHandler(e, data),
        },
      }),
      childNodes[1],
    ]
  );
};

function ChangeTheColor(e, judge, NodeClass) {
  if (
    judge !== "" &&
    judge !== undefined &&
    judge !== null &&
    judge.swtich !== false
  ) {
    for (var k in judge) {
      var a = eval("e." + k);
      if (NodeClass) {
        for (let c = 0; c < NodeClass.length; c++) {
          if (a === NodeClass[c]) return NodeClass[c];
          else if (NodeClass.length - 1 == c) return "";
        }
      } else {
        return "";
      }
    }
  } else {
    return "";
  }
}
// 创建 node 子节点
export const renderChildren = (h, list, context) => {
  if (Array.isArray(list) && list.length) {
    const childNodeList = list.map((item) => {
      return renderNode(h, item, context);
    });

    return h(
      "div",
      {
        domProps: {
          className: "org-tree-node-childNodeList",
        },
      },
      childNodeList
    );
  }
  return "";
};

export const render = (h, context) => {
  const { props } = context;

  return renderNode(h, props.data, context);
};

export default render;

index.js

import OrgTree from "./org-tree";

const install = (Vue) => {
  if (install.installed) {
    return;
  }

  install.installed = true;

  Vue.component(OrgTree.name, OrgTree);
};

OrgTree.install = install;

if (typeof window !== "undefined" && window.Vue) {
  window.Vue.use(OrgTree);
}

export default OrgTree;

调用方法:

index.vue

<template>
  <div>
    <!-- 查询工作栏 -->
    <el-form
      :model="queryParams"
      ref="queryForm"
      size="small"
      :inline="true"
      v-show="showSearch"
    >
      <el-form-item label="设备编号" prop="deviceNo">
        <el-input
          v-model="queryParams.deviceNo"
          placeholder="请输入设备编号"
          clearable
        />
      </el-form-item>
      <el-form-item label="设备名称" prop="deviceName">
        <el-input
          v-model="queryParams.deviceName"
          placeholder="请输入设备名称"
          clearable
        />
      </el-form-item>
      <el-form-item>
        <el-button
          type="primary"
          class="search-button"
          icon="el-icon-search"
          @click="handleQuery"
          >查询</el-button
        >
        <el-button
          type="primary"
          class="search-button"
          icon="el-icon-refresh"
          @click="resetQuery"
          >重置</el-button
        >
      </el-form-item>
    </el-form>
    <div class="svg-content">
      <div class="svg-header">
        <div class="svg-header-legend"></div>
        <div class="svg-header-scale">
          <i @click="changeZoom(true)" class="el-icon-plus"></i>
          <i class="number">{{ percent }}%</i>
          <i @click="changeZoom(false)" class="el-icon-minus"></i>
        </div>
      </div>
      <div class="svg-tree">
        <org-tree
          id="orgTreeNode"
          :data="org"
          :horizontal="horizontal"
          :zoomFactor="zoomFactor"
          collapsable
          :render-content="renderContent"
          @on-expand="onExpand"
          @on-mousewheel="onMousewheel"
          @on-node-mouseover="onMouseover"
          @on-node-mouseout="onMouseout"
          @on-expand-mouseover="Mouseover"
          @on-expand-mouseout="Mouseout"
          @on-node-click="cccc"
        />
        <div
          class="childNodeList-submenu"
          ref="floating"
          v-show="isShowF && bd"
          @mouseover="isShowF = true"
          @mouseout="isShowF = false"
          @mousewheel.prevent="mouseWheelDia"
        >
          <div v-for="m in subList">
            <span class="label">{{ m.label }}</span>
            <span class="value" :title="subItem[m.value] || '暂无'">
              {{ subItem[m.value] || "暂无" }}
              {{ subItem[m.unit] || "" }}
            </span>
          </div>
          <div class="more">查看更多</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import OrgTree from "@/components/org-tree";

export default {
  name: "app",
  components: {
    OrgTree,
  },
  directives: {
    focus: {
      inserted: function (el) {
        el.querySelector("input").focus();
      },
    },
  },
  data() {
    return {
      // 遮罩层
      loading: true,
      // 显示查询条件
      showSearch: true,
      // 查询参数
      queryParams: {
        deviceNo: "",
        deviceName: "",
      },
      zoomFactor: 1,
      bd: true,
      isShowF: false,
      org: {
        deviceName: "主分支",
        expand: true,
        childNodeList: [],
      },
      defaultData: {},
      subList: [
        { label: "名称", value: "deviceName" },
        { label: "读数", value: "lastNum", unit: "unitName" },
      ],
      subItem: {},
      horizontal: true,
      collapsable: true,
      labelClassName: "bg-white",
    };
  },
  computed: {
    percent() {
      return Math.round(this.zoomFactor * 100);
    },
  },
  mounted() {
    this.org.childNodeList = [
      {
        deviceNo: "1",
        deviceName: "设备1",
        id: 1,
        lastNum: 333.62,
        unitName: "kWh",
        childNodeList: [
          {
            deviceNo: "1-1",
            deviceName: "设备1-1",
            id: 2,
            lastNum: 222.21,
            unitName: "kWh",
            childNodeList: [],
          },
        ],
      },
      {
        deviceNo: "2",
        deviceName: "设备2",
        id: 8,
        lastNum: 44444.66,
        unitName: "t",
        childNodeList: [
          {
            deviceNo: "2-1",
            deviceName: "设备2-1",
            id: 10,
            lastNum: 11.62,
            unitName: "t",
            childNodeList: [],
          },
          {
            deviceNo: "2-2",
            deviceName: "设备2-2",
            id: 11,
            lastNum: 2.03,
            unitName: "t",
            childNodeList: [],
          },
          {
            deviceNo: "2-3",
            deviceName: "设备2-3",
            id: 12,
            lastNum: 66.98,
            unitName: "t",
            childNodeList: [],
          },
        ],
      },
      {
        deviceNo: "3",
        deviceName: "设备3",
        id: 16,
        lastNum: 3.34,
        unitName: "kWh",
        childNodeList: [],
      },
    ];
    this.toggleExpand(this.org.childNodeList, true); // 调用全部展开
    // 默认数据存储
    this.defaultData = this.org;
  },
  methods: {
    handleQuery() {
      this.org = { ...this.defaultData };
      let list = this.filterTreeLabel(
        this.org.childNodeList,
        this.queryParams.deviceName,
        this.queryParams.deviceNo
      );
      this.org.childNodeList = list;
      this.org.expand = true;
      this.toggleExpand(this.org.childNodeList, true); // 调用全部展开
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    renderContent(h, data) {
      return {
        deviceName: data.deviceName,
        deviceNo: data.deviceNo,
      };
    },
    filterTreeLabel(arr, deviceName, deviceNo) {
      let result = [];
      arr.forEach((m) => {
        if (
          String(m.deviceName).toLowerCase().includes(deviceName) &&
          String(m.deviceNo).toLowerCase().includes(deviceNo)
        ) {
          result.push(m);
        } else {
          if (m.childNodeList && m.childNodeList.length) {
            let childNodeList = this.filterTreeLabel(
              m.childNodeList,
              deviceName,
              deviceNo
            );
            let obj = { ...m, childNodeList };
            if (childNodeList && childNodeList.length) {
              result.push(obj);
            }
          }
        }
      });
      return result;
    },
    changeZoom(delta) {
      if (delta) {
        // 向上滚动,放大
        this.zoomFactor += 0.05;
      } else {
        // 向下滚动,缩小
        this.zoomFactor = this.zoomFactor > 0 ? this.zoomFactor - 0.05 : 0;
      }
    },

    cccc(a, b) {
      // console.log(a);
      // console.log(b);
    },
    mouseWheelDia() {
      this.isShowF = false;
    },
    Mouseover() {
      // 鼠标移入缩放icon
      this.bd = false;
    },
    Mouseout() {
      // 鼠标移出缩放icon
      this.bd = true;
    },
    onMouseover(e, data) {
      if (data.deviceNo) {
        // 鼠标移入树节点
        this.subItem = data; // 鼠标移入,该节点的所有数据
        this.isShowF = true;
        let floating = this.$refs.floating;
        // 调整悬浮框位置 e.clientX - 100
        floating.style.left = e.clientX + "px";
        floating.style.top = e.clientY + "px";
      }
    },
    onMouseout(e, data) {
      // 鼠标移出树节点
      this.isShowF = false;
    },
    onExpand(e, data) {
      // 节点展开收缩
      if (data.leave == 1 && data.expand == true) {
        let dom = document.getElementById("ddd");
        dom.style.top = "50%";
        dom.style.left = "8%";
      }
      if ("expand" in data) {
        data.expand = !data.expand;
        if (!data.expand && data.childNodeList) {
          this.collapse(data.childNodeList);
        }
      } else {
        this.$set(data, "expand", true);
      }
    },
    collapse(list) {
      list.forEach((child) => {
        if (child.expand) {
          child.expand = false;
        }
        child.childNodeList && this.collapse(child.childNodeList);
      });
    },
    toggleExpand(data, val) {
      // 节点默认全部展开
      let _this = this;
      if (Array.isArray(data)) {
        data.forEach(function (item) {
          _this.$set(item, "expand", val);
          if (item.childNodeList) {
            _this.toggleExpand(item.childNodeList, val);
          }
        });
      } else {
        this.$set(data, "expand", val);
        if (data.childNodeList) {
          _this.toggleExpand(data.childNodeList, val);
        }
      }
    },
    onMousewheel(data) {
      this.zoomFactor = data;
    },
  },
};
</script>
<style lang="scss" scoped>
.svg-content {
  margin: 20px;
  padding: 20px;
  background-color: #fff;
  user-select: none;
  p {
    margin: 0;
    padding: 0;
  }
  .svg-header {
    display: flex;
    justify-content: space-between;
    .svg-header-legend {
    }
    .svg-header-scale {
      display: flex;
      align-items: center;
      padding: 10px 20px;
      border: 1px solid #15dbf5;
      border-radius: 12px;
      background-color: #132f6c;
      i {
        color: #15dbf5;
        font-weight: 600;
        cursor: pointer;
        user-select: none;
        display: inline-block;
        font-style: normal;
      }
      .number {
        color: #fff;
        text-align: center;
        width: 50px;
      }
    }
  }
  .svg-tree {
    position: relative;
    font-size: 14px;
    width: 100%;
    height: calc(100vh - 320px);
  }
}
.childNodeList-submenu {
  position: absolute;
  width: 200px;
  height: 200px;
  left: 0;
  top: 0;
  background-color: rgba($color: #000, $alpha: 0.8);
  color: #fff;
  font-size: 14px;
  border-radius: 10px;
  padding: 16px;
  border: 1px solid #132f6c;
  font-weight: 600;
  > div {
    padding-top: 8px;
    display: flex;
  }
  span {
    display: inline-block;
  }
  .label {
    width: 38%;
    color: #15dbf5;
    margin-right: 5px;
  }
  .value {
    width: 60%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .more {
    text-align: center;
    justify-content: center;
    color: red;
  }
}
</style>
<style lang="scss">
.addOrEditDia,
.delDia {
  padding: 14px 18px 10px 18px;
  border-radius: 4px !important;
  .el-dialog__header {
    display: flex;
    justify-content: space-between;
  }
  .el-dialog__body {
    margin-top: 37px;
  }
  .el-dialog__footer {
    padding: 27px 0 9px 0 !important;
  }
  .el-dialog__headerbtn {
    position: static !important;
  }
}
.bg-white {
  background-color: white;
}
.bg-orange {
  background-color: orange;
}
.bg-gold {
  background-color: gold;
}
.bg-gray {
  background-color: gray;
}
.bg-lightpink {
  background-color: lightpink;
}
.bg-chocolate {
  background-color: chocolate;
}
.bg-tomato {
  background-color: tomato;
}
</style>

跟原版差别在于不能编辑删除,可直接从原创代码复制过来

原创指路: vue实现可新增、编辑、删除、拖拽、缩放、纵横的树形tree结构_vue思维导图插件可新增删除拖拽-CSDN博客

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值