vue目录树的封装

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>

封装一个多级目录树组件

在这里插入图片描述

方法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

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3中实现富文本目录的方法如下: 1. 首先,你需要安装并引入一个适用于Vue3的富文本编辑器插件,例如tinymce或quill。 2. 在Vue组件中,使用该富文本编辑器插件,并将其绑定到一个数据属性上,例如`content`。 3. 在组件中,创建一个用于存储目录的数据属性,例如`treeData`。 4. 监听`content`数据属性的变化,当内容发生变化时,解析富文本内容,提取标题和对应的层级关系,生成目录数据。 5. 在组件的模板中,使用递归组件或者循环遍历的方式,将目录数据渲染成目录的结构。 下面是一个示例代码: ```vue <template> <div> <div class="editor"> <rich-text-editor v-model="content" @change="handleContentChange"></rich-text-editor> </div> <div class="tree"> <tree-node :data="treeData"></tree-node> </div> </div> </template> <script> import RichTextEditor from 'tinymce' // 富文本编辑器插件 import TreeNode from './TreeNode.vue' // 目录节点组件 export default { components: { RichTextEditor, TreeNode }, data() { return { content: '', // 富文本内容 treeData: [] // 目录数据 } }, methods: { handleContentChange() { // 解析富文本内容,生成目录数据 // 这里需要根据具体的富文本编辑器插件进行解析 // 提取标题和对应的层级关系,生成目录数据 // 将生成的目录数据赋值给treeData } } } </script> ``` 请注意,上述代码中的`rich-text-editor`和`tree-node`是示意组件,你需要根据具体的富文本编辑器插件和目录组件进行替换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值