vue中目录的实现方法

封装一个只有一级的单选或多选树形组件

<template>
  <div class="tree-wrapper">
      <div @click="changeExpand">
        <i class="el-icon-caret-right icon" v-show="!isExpand"></i>
        <i class="el-icon-caret-bottom icon" v-show="isExpand"></i>
        <slot name="treeName"></slot>
      </div>
    <ul>
      <li
        class="tree-item"
        v-show="isExpand"
        v-for="item in treeData"
        :key="item[valueName]"
      >
        <span
          @click="changeSelect(item[valueName])"
          :class="{ active: selectedValues.includes(item[valueName]) }"
          >{{ item[displayName] }}</span
        >
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "singleOrMultipleTree",
  props: {
    displayName: {
      type: String,
      default: "",
      required: true,
    },
    valueName: {
      type: String,
      default: "",
      required: true,
    },
    treeType: {
      type: String,
      default: "single",
    },
    treeData: {
      type: Array,
      default: () => [],
      required: true,
    },
    isExpand: {
      type: Boolean,
      default: false,
      required: true,
    },
  },
  data() {
    return {
      selectedValues: [],
    };
  },
  methods: {
    changeSelect(val) {
      const index = this.selectedValues.indexOf(val);
      if (index < 0) {
        if (this.treeType === "single") {
          this.selectedValues.splice(0, 1, val);
        } else {
          this.selectedValues.push(val);
        }
      } else {
        this.selectedValues.splice(index, 1);
      }
      this.$emit("treeSelectChagne", this.selectedValues);
    },

    changeExpand() {
      this.$emit("update:isExpand", !this.isExpand);
    },
  },
};
</script>

<style lang="less" scoped>
.tree-wrapper {
  .icon {
    cursor: pointer;
  }
  .tree-item {
    display: block;
    cursor: pointer;
  }
  .active {
    background-color: aqua;
  }
}
</style>

展开与收起只是需要一个默认的展示状态,并不需要双向数据绑定,所以这样写就行了

watch: {
    isExpand: {
      immediate: true,
      handler: function (val) {
        this.isOpen = val;
      },
    },
  },
    methods: {
    changeExpand() {
      this.isOpen = !this.isOpen;
    },
  },

使用:

<title>单选或多选树形目录</title>
      <singleMultipleTree
        displayName="name"
        valueName="code"
        :treeData="treeData"
        :isExpand.sync="isOpen"
        @treeSelectChagne="singleChange($event)"
      >
        <span slot="treeName">单选目录</span>
      </singleMultipleTree>

      <singleMultipleTree
        displayName="name"
        valueName="code"
        treeType="multiple"
        :treeData="treeData"
        :isExpand.sync="isOpen"
        @treeSelectChagne="multipleChange($event)"
      >
        <span slot="treeName">多选目录</span>
      </singleMultipleTree>

封装一个多级目录树组件

vuepress根据文件目录生成分类_递归

方法1:自己封装一个目录组件,递归思想

treeItem.vue文件:

组件treeItem通过name属性,自己引用自己

<template>
  <div class="itemBox">
    <!-- 按钮部分 -->
    <template v-if="itemText.children && itemText.children.length">
      <i
        class="el-icon-caret-right"
        v-show="!isOpen"
        @click="isOpen = !isOpen"
      ></i>
      <i
        class="el-icon-caret-bottom"
        v-show="isOpen"
        @click="isOpen = !isOpen"
      ></i>
    </template>
    <!-- 每个目录项的内容 -->
    <div
      class="catalogItem"
      @click="treeSelectChange(itemText.code)"
      :class="{ active: selectedItems == itemText.code }"
    >
      {{ itemText.title }}
    </div>
    <!-- 如果有children,就会接着渲染内层目录,内部和自己本身是一样的 -->
    <template v-if="itemText.children && itemText.children.length">
      <treeItem
        class="childrenItem"
        :class="'hello' + childItem.code"
        v-for="childItem in itemText.children"
        :key="childItem.code"
        :itemText="childItem"
        v-show="isOpen"
        :ref="childItem.code"
        v-on="$listeners"
        >{{ childItem.title }}</treeItem
      >
    </template>
  </div>
</template>

<script>
import { mapMutations } from "vuex";
export default {
  name: "treeItem",
  props: {
    itemText: {
      type: Object,
      default: () => {},
    },
  },
  data() {
    return {
      isOpen: false,
      selectedItems: "", // 每个组件就自身一个条件的选中与取消,所以是一个字符串
    };
  },
  methods: {
    treeSelectChange(code) {
      this.selectedItems = this.selectedItems == code ? "" : code;
      // 组件递归会有事件传不到父组件的问题;方法1:加v-on="$listeners"
      this.$emit("catalogChange", code);
      // 方法二:使用vuex
      // this.changeSelectedArr(code);
    },

    // ...mapMutations(["changeSelectedArr"]),
  },
};
</script>
<style scoped>
.catalogItem {
  display: inline-block;
  font-size: 12px;
  color: #565656;
  margin: 0;
}
.itemBox {
  padding: 0 0 0 15px;
  margin: 0;
}
.childrenItem {
  cursor: pointer;
}
.active {
  background-color: skyblue;
}
</style>

tree.vue文件:

<template>
  <div class="catalog-wrapper">
    <catalog
      class="catalog"
      v-for="(item, index) in list"
      :key="index"
      :itemText="item"
      @catalogChange="catalogChange($event)"
    ></catalog>
  </div>
</template>

<script>
import catalog from "./multipleTreeItem.vue";
import { mapState } from "vuex";
export default {
  data() {
    return {
      // 目录数据开始
      list: [
        {
          title: "中国",
          code: 1,
          children: [
            {
              title: "湖南",
              code: 11,
              children: [
                {
                  title: "长沙",
                  code: 111,
                  children: [
                    { title: "天心区", code: 1111 },
                    { title: "岳麓区", code: 1112 },
                    { title: "雨花区", code: 1113 },
                    { title: "望城区", code: 1114 },
                    { title: "开福区", code: 1115 },
                  ],
                },
              ],
            },
            {
              title: "广东省",
              code: 12,
              children: [
                {
                  title: "深圳市",
                  code: 121,
                  children: [
                    {
                      title: "福田区",
                      code: 1211,
                    },
                    {
                      title: "南山区",
                      code: 1212,
                    },
                    {
                      title: "盐田区",
                      code: 1213,
                    },
                    {
                      title: "宝安区",
                      code: 1214,
                    },
                    {
                      title: "龙华区",
                      code: 1215,
                    },
                    {
                      title: "龙岗区",
                      code: 1216,
                    },
                  ],
                },
                {
                  title: "广州市",
                  code: 122,
                  children: [
                    { title: "海心区", code: 1221 },
                    { title: "白云区", code: 1222 },
                  ],
                },
              ],
            },
          ],
        },
        {
          title: "日本",
          code: 2,
          children: [
            { title: "东京", code: 21 },
            { title: "大阪", code: 22 },
          ],
        },
      ],
      suggestions: [],
    };
  },
  components: { catalog },
  methods: {
    catalogChange(e) {
      console.log("----", e);
    },
  },
};
</script>
<style scoped>
.catalog-wrapper {
  width: 300px;
  background-color: #f5f5f5;
}
</style>
方法2:使用antd的a-tree或element-ui的tree组件

他的有个问题就是只能选中最后一级的数据,上一级是不能选的

<el-tree
      :data="list"
      node-key="code"
      highlight-current
      :props="defaultProps"
       @node-click="handleNodeClick"
    >
    </el-tree>
data(){
    return {
      defaultProps: {
        children: "children",
        label: "title",
      },
    }
}
methods:{
    handleNodeClick(){}
}
遇到的一些问题及解决方案:

怎么获取当前节点的父级

在treeData中找到id为4的所有父级

function getParents(propName, id, data) {
        // 深度遍历查找
        function dfs(data, id, parents) {
          for (var i = 0; i < data.length; i++) {
            var item = data[i];
            // 找到id则返回父级id
            if (item[propName] === id) return parents;
            // children不存在或为空则不递归
            if (!item.children || !item.children.length) continue;
            // 往下查找时将当前id入栈
            parents.push({
              pid: item[propName],
              pIndex: i,
            });
            if (dfs(item.children, id, parents).length) return parents;
            // 深度遍历查找未找到时当前id 出栈
            parents.pop();
          }
          // 未找到时返回空数组
          return [];
        }
        return dfs(data, id, []);
      }
      getParents("id", 4, treeData);

怎么快速操作dom

绑定唯一的id,通过id获取最快

$ref没有那么好找,而且它还要注意this和它在哪个组件中使用的问题,只有要修改里面的数据或调用方法才使用

控制展开怎么控制

1、通过递归遍历,给每一个数据加上一个控制展开的变量,这个是最简单的操作方法,优点是对于单一节点的展开或收起好操作(主要是指非点击节点的展开与收起,比如搜索节点的点击跳转);缺点是如果收起或展开全部的话需要自己递归,虽然写起来也还简单

2、通过组件的变量去控制展开与收起,优点是对于全部的展开与收起好掌控,只需要通过父级传传递一个变量,监听这个变量就可以控制了;缺点是对于某个个节点的展开与收取,如果不是点击节点的话(比如搜索点击跳转点位),不方便找到节点

vue组件递归的时候,父级可能没有监听到事件

给递归的组添加

v-on="$listeners"

组件递归的使用需要记得传递对应的props