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