el-tree 从叶子节点入手解决选中问题

0. 缘起

第一次用el-tree,是一个很阴间的任务,不过可以抄小伙伴的,快乐抄了过来结果发现我这边出了个BUG,省市区,市级选中其下所有的区也被选中。但我一看请求返回值,它的是否选中状态是正确的。所以我需要抓抓代码BUG。

1. 封装el-tree

整体封装组件代码见附录

      <self-tree
        :tree-data="treeData"
        :is-check="true"
        :use-plus="true"
        :defaultProps="defaultProps"
        :is-search="false"
        :defaultCheckedKeys="defaultCheckedKeys"
        @check="handleNodeCheck"
        :show-indicator="true"
      />

props传进去一些当前的value名

      defaultProps: {
        children: "list",
        label: "name",
        isLeaf: "leaf",
      },

这一步非常关键,要记住el-tree判断是否选中要从叶子结点看!如果单纯以是否选中(tick为true),就会出现非叶节点选中,其下级全部选中的BUG。

resolveData(data, newArr, level, regionStr)这个递归函数深度搜索,如果到达该分支叶节点,就进行下一分支的查找。

data :递归数组 ---newArr: 记 id ---level:深度 --- regionStr:

    // 数据处理
    resolveData(data, newArr, level, regionStr) {
      level++;
      data.forEach((item) => {
        if (level <= 3) {
          if (level === 1) {
            regionStr = "";
            item.region = item.id + "";
          } else {
            item.region = regionStr + "," + item.id;
          }
          item.type = "region";
        }
          
// important step
        if (item.tick && level === 3) {
          newArr.push("" + item.id);
          this.checkedIdList.push(item.region);
        }
        if (item.list) {
          this.resolveData(item.list, newArr, level, item.region);
        }
      });
    },

获取生成树的数据,同时也是递归开始

    async getAreaConnectList() {
      let res = await getAreaConnectList(this.chosen);
      if (res.code === 200) {
        this.treeDataAll = res.data || [];
      } else {
        this.$message.error("获取区域关联数据失败", res.code);
      }
      let arr = [];
      let level = 0;
      let regionStr = "";
      this.resolveData(this.treeDataAll, arr, level, regionStr);
      this.defaultCheckedKeys = arr || [];
    },

        
    handleNodeCheck(val) {
      this.checkedIdList = val.checkedNodes.map((item) => {
        return item.region;
      });
    },

2. 心得

这几天搞什么级联选择器 树结构,和数组打交道很多。要注意数据格式和回显方式,想清楚怎么来 怎么发送。

el-tree判断是否选中要从叶子结点看!如果单纯以是否选中(tick为true),就会出现非叶节点选中,其下级全部选中的BUG

附录

<!--基于el-tree的树形组件-->
<template>
  <div class="self-tree">
    <el-input v-if="isSearch" ref="treeInputRef" v-model="search" size="small" placeholder="搜索" @change="inputChange"
              clearable>
      <em slot="suffix" class="el-input__icon el-icon-search"></em>
    </el-input>
    <el-tree :class="['self-tree-common',usePlus ?'self-plus-icon-tree':'',showIndicator?'self-indicator-tree':'']"
             ref="treeRef"
             style="margin-top: 10px"
             :data="treeData"
             :props="defaultProps"
             :filter-node-method="filterNode"
             :show-checkbox="isCheck"
             :node-key="nodeKey"
             :indent="showIndicator ? 0 : 16"
             :icon-class="usePlus ? 'el-icon-circle-plus-outline':''"
             :default-checked-keys="defaultCheckedKeys"
             :expand-on-click-node="expandOnClickNode"
             :default-expand-all="defaultExpandAll"
             :lazy="lazy"
             :load="lazyLoad"
             :draggable="draggable"
             @node-click="handleNodeClick"
             @check="handleNodeCheck"
             @node-contextmenu="handleContextMenu"
             @node-drag-start="handleDragStart"
             @node-drag-enter="handleDragEnter"
             @node-drag-leave="handleDragLeave"
             @node-drag-over="handleDragOver"
             @node-drag-end="handleDragEnd"
             @node-drop="handleNodeDrop"
             :allow-drop="allowDrop"
             :allow-drag="allowDrag"
             highlight-current>
      <span slot-scope="{node,data}">
        <!--添加图标-->
        <span><vab-icon :icon="['fas',data.icon]"></vab-icon>{{ node.label }} <i class="el-icon-circle-close"
                                                                                 style="margin-left: 10px"
                                                                                 @click="removeSelectNode(node,data)"
                                                                                 v-if="node.level !== unDragLevel && showNodeClose"></i></span>
      </span>
    </el-tree>
  </div>
</template>

<script>
export default {
  name: "selfTree",
  props: {
    // 节点数据
    treeData: {
      type: Array,
      default: () => []
    },
    // 是否显示搜索框
    isSearch: {
      type: Boolean,
      default: () => true
    },
    // 默认搜索名
    defaultSearch: {
      type: String,
      default: () => "浙江省"
    },
    // 是否开启复选框
    isCheck: {
      type: Boolean,
      default: () => false
    },
    // 节点key值
    nodeKey: {
      type: String,
      default: () => "id"
    },
    // 是否在点击节点的时候展开或者收缩节点
    expandOnClickNode: {
      type: Boolean,
      default: () => true
    },
    // 默认选中的节点
    defaultCheckedKeys: {
      type: Array,
      default: () => []
    },
    // 使用+/- icon图标
    usePlus: {
      type: Boolean,
      default: () => false
    },
    // 显示指示器
    showIndicator: {
      type: Boolean,
      default: () => false
    },
    // 使用懒加载方式
    lazy: {
      type: Boolean,
      default: () => false
    },
    // 懒加载函数
    lazyLoad: {
      type: Function,
      default: () => 1
    },
    defaultProps: {
      type: Object,
      default: () => {
        return {
          children: "children",
          label: "name",
          isLeaf: "leaf"
        };
      }
    },
    // 是否允许拖拽
    draggable: {
      type: Boolean,
      default: () => false
    },
    // 不可拖拽的层级
    unDragLevel: {
      type: Number,
      default: () => 1
    },
    allowDrag: {
      type: Function,
      default: () => true
    },
    allowDrop: {
      type: Function,
      default: () => true
    },
    // 是否默认全部展开
    defaultExpandAll: {
      type: Boolean,
      default: () => false
    },
    // 是否显示节点删除按钮
    showNodeClose: {
      type: Boolean,
      default: () => false
    }
  },
  data() {
    return {
      search: ""
    };
  },
  watch: {
    search(val) {
      this.$refs?.treeRef?.filter(val);
    }
  },
  methods: {
    // 输入框值变化
    inputChange() {
      this.$emit("inputChange", this.search);
    },
    filterNode(value, data) {
      if (!value) return true;
      return data.label ? data.label.indexOf(value) !== -1 : data.name.indexOf(value) !== -1;
    },
    // 处理节点点击事件
    handleNodeClick(data, node, $el) {
      // 复选框模式下,点击事件不触发
      if (this.isCheck) return;
      this.$emit("node-click", data, node);
    },
    // 处理节点复选事件
    handleNodeCheck(data, list) {
      this.$emit("check", list);
    },
    // 处理右键点击事件
    handleContextMenu(event, data, node, $el) {
      this.$emit("node-contextmenu", {event, data, node, el: $el});
    },
    // 新增节点
    appendNode(data, key) {
      let parentNode = this.$refs.treeRef.getNode(key);
      parentNode.isLeaf = false;
      if (parentNode.expanded) {
        if (!parentNode.children) {
          parentNode.children = [];
        }
        this.$refs.treeRef.append(data, parentNode);
      } else {
        parentNode.expand();
      }
    },
    // 删除节点
    removeNode(key) {
      let node = this.$refs.treeRef.getNode(key);
      let parentNode = node.parent;
      this.$refs.treeRef.remove(node);
      parentNode.isLeaf = !(parentNode.childNodes && parentNode.childNodes.length);
      parentNode.expand();
    },
    // 删除选中节点
    removeSelectNode(node, data) {
      this.$emit("on-remove-node", node, data);
    },
    // 更新节点名称
    refreshNode(name, key) {
      let node = this.$refs.treeRef.getNode(key);
      node.data.label = name;
      node.data.name = name;
    },
    handleDragStart(node, ev) {
      this.$emit("node-drag-start", node, ev)
    },
    handleDragEnter(draggingNode, dropNode, ev) {
      this.$emit("node-drag-enter", draggingNode, dropNode, ev)
    },
    handleDragLeave(draggingNode, dropNode, ev) {
      this.$emit("node-drag-leave", draggingNode, dropNode, ev)
    },
    handleDragOver(draggingNode, dropNode, ev) {
      this.$emit("node-drag-over", draggingNode, dropNode, ev)
    },
    handleDragEnd(draggingNode, dropNode, dropType, ev) {
      this.$emit("node-drag-end", draggingNode, dropNode, dropType, ev)
    },
    handleNodeDrop(draggingNode, dropNode, dropType, ev) {
      this.$emit("node-drop", draggingNode, dropNode, dropType, ev)
    }
  },
  mounted() {
    if (this.isSearch) {
      setTimeout(() => {
        this.search = this.defaultSearch;
      }, 500);
    }
  }
};
</script>

<style lang="scss">
.self-indicator-tree {
  .el-tree-node {
    position: relative;
    padding-left: 16px; // 缩进量
  }

  .el-tree-node__children {
    padding-left: 16px; // 缩进量
  }

  // 竖线
  .el-tree-node::before {
    content: "";
    height: 100%;
    width: 1px;
    position: absolute;
    left: -3px;
    top: -26px;
    border-width: 1px;
    border-left: 1px dashed #52627C;
  }

  // 当前层最后一个节点的竖线高度固定
  .el-tree-node:last-child::before {
    height: 38px; // 可以自己调节到合适数值
  }

  // 横线
  .el-tree-node::after {
    content: "";
    width: 24px;
    height: 20px;
    position: absolute;
    left: -3px;
    top: 12px;
    border-width: 1px;
    border-top: 1px dashed #52627C;
  }

  // 去掉最顶层的虚线,放最下面样式才不会被上面的覆盖了
  & > .el-tree-node::after {
    border-top: none;
  }

  & > .el-tree-node::before {
    border-left: none;
  }

  // 展开关闭的icon
  .el-tree-node__expand-icon {
    font-size: 16px;

    &.is-leaf {
      color: transparent;
    }
  }
}

.self-plus-icon-tree {
  .el-tree-node__expand-icon.expanded {
    transform: rotateX(0deg);
  }

  .el-tree-node__expand-icon.expanded:before {
    content: '\e722';
  }
}
</style>

posted @ 2022-02-24 15:40  乐盘游  阅读(1320)  评论(0编辑  收藏  举报