组件中需要组件调用自身,这就是递归组件,递归组件需要声明name和自己引入自己并声明注册为组件,并且需要使用props进行传参,其还需要一个停止条件,否则会进入死循环。
vue的递归组件和其v-for指令之间的关系和js中的for循环和递归函数一样,一个是有尽头,一个是不知道到底嵌套了多少层级。在vue中我们是数据驱动界面,根据数据来渲染界面,而很多数据我们是从后台获取,事先并不知道它会嵌套多少层,这时候使用v-for指令就不能满足我们的要求了,需要使用递归组件,这就和js中的递归函数一个道理!
效果图:
如图所示,当用户操作界面添加条件时,我们就需要在相应层级的添加一行,然而我们不知道用户具体会添加多少层条件,这时候使用v-for指令来遍历数据进行循环就不怎么合适了,这时候就时递归组件大展身手的时候!
父组件代码:
<div class="filter-context">
<div class='contain ' :class='{borderShow:item.list.length>0}' v-for="(item,index) in filterData" :key='index'>
<s-table ref='tabled' @change='change' :item='item' :filterData='filterData'></s-table>
</div>
</div>
子组件代码:
<template>
<div class='children' v-if='item.list'>
<div class="context-left" v-if='item.list.length>1'>
<span>{{item.con}}</span>
</div>
<div class="context-right">
<div class="context" v-for="(data,i) in item.list" :key="i">
<div class='context-every' v-if='data.id'>
<div v-show='!data.ifShowField'>
<span class='filter-field' :title='data.field' @click='fieldClick(i, item.list,item.con,data)'>{{data.field}}</span>
</div>
<div v-show='data.ifShowField'>
<span class='choose-field' @click.stop='toggle(i)'>请选择字段</span>
<div class='tree' v-show='nowIndex === i && ifTree'>
<el-tree :render-content="renderContent" :data="treeData" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</div>
</div>
</div>
<div class="context-every" v-if='data.id' v-show='data.recordEvery'>
<p class='borderStyle'></p>
</div>
<s-table :item='data' @change='change' :filterData='filterData'></s-table>
</div>
</div>
</div>
</template>
<script>
import STable from "./STable";
import uuId from "node-uuid";
export default {
name: "STable",
props: ["item", "filterData"],
data() {
return {
fieldArr: [],
con: "",
treeData: [
{
label: "1",
children: [
{
label: "2 ",
type: "1"
}
]
},
{
label: "3",
children: [
{
label: "4",
type: "2"
},
{
label: "5",
type: "3"
}
]
},
{
label: "6",
children: [
{
label: "7",
type: "1"
},
{
label: "8",
type: "1"
}
]
}
],
defaultProps: {
children: "children",
label: "label"
},
nowIndex: null, //选择字段当前的 下标
ifTree: false, //选择字段的 树状下拉列表是否展示
// ifShowField: [], //显示 字段false 还是 选择字段true 的布尔值数组
ifCondition: false, //添加条件的两种展现形式,判断展示哪个
ifCon: false, //是否展示 含有且或条件
recordIndex: null, // 每条过滤条件 文字的下标
recordField: [] //记录每条过滤条件点击的文字
// recordEvery: [] //list 列表 遍历 阴影框
};
},
components: {
STable
},
methods: {
// 添加条件的点击事件
addFilter(bool) {
this.ifCondition = bool;
},
// 请选择字段的 折叠 展开
toggle(i) {
this.ifTree = !this.ifTree;
this.nowIndex = i;
},
// 树形空间 子节点的 点击事件
handleNodeClick(data, node, itself) {
if (!data.children) {
this.item.list[this.nowIndex].field = data.label;
this.recordField[this.nowIndex] = data.label;
this.item.list[this.nowIndex].ifShowField = false;
this.item.list[this.nowIndex].recordEvery = false;
this.ifTree = false;
}
},
// 每条过滤 条件 字段的点击事件
fieldClick(index, field, con, data) {
this.fieldArr = data.id;
this.recordIndex = index;
this.ifCondition = false;
this.isData(this.filterData[0].list, this.fieldArr);
this.$emit("change", this.ifCondition, con, this.fieldArr);
},
//判断阴影框是否显示
isData(arrData, id) {
arrData.forEach((item, index) => {
if (!item.list) {
if (item.id === id) {
arrData[index].recordEvery = !arrData[index].recordEvery;
if (arrData[index].recordEvery) {
this.ifCondition = true;
} else {
this.ifCondition = false;
}
} else {
arrData[index].recordEvery = false;
}
}else{
this.isData(item.list, id);
}
});
},
//添加数据
addData(arrData, uId, id, con, value) {
arrData.forEach((item, index) => {
if (item.id) {
if (item.id === id) {
if (con === value) {
arrData.splice(index + 1, 0, {
field: "",
id: uId,
ifShowField: true,
recordEvery: false
});
arrData[index].recordEvery = false;
} else {
var fieldArr = arrData[index];
var arr = { ...fieldArr, recordEvery: false };
var obj = {
con: value,
list: [
{ ...arr },
{ field: "", id: uId, ifShowField: true, recordEvery: false }
]
};
arrData.splice(index, 1, obj);
}
}
} else {
this.addData(item.list, uId, id, con, value);
}
});
},
// 且,或 条件的点击事件
ifConClick(value, con, id) {
this.item.list[this.nowIndex].recordEvery = false;
var uId = uuId();
if (con) {
this.addData(this.item.list, uId, id, con, value);
} else {
this.filterData[0].con = value;
this.filterData[0].list.push({
field: "",
id: uId,
ifShowField: true,
recordEvery: false
});
}
this.$emit("change", false);
},
change(ifCondition, con, id) {
this.$emit("change", ifCondition, con, id);
},
// 树形图子节点 放入icon图标
renderContent(h, { node, data, store }) {
return (
<span style="float:left;width: 140px;">
<i
class={data.type == "1" ? "el-icon-circle-close" : ""}
style="float:left;line-height: 20px;"
/>
<i
class={data.type == "2" ? "el-icon-date" : ""}
style="float:left;line-height: 20px;"
/>
<i
class={data.type == "3" ? "el-icon-sold-out" : ""}
style="float:left;line-height: 20px;"
/>
<span
title={node.label}
style="float:left;margin-left: 5px;width: 100px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;"
>
{node.label}
</span>
</span>
);
}
}
};
</script>
在子组件代码中,我引入了他自身的路径并注册了组件,并且还声明了name,这些是必须的操作!
然后在父组件中我调用了子组件,而在子组件中我又调用了子组件,注意这时在子组件中的调用需要和父组件保持同步,如果在父组件中的调用有进行父子组件的通信,在子组件的调用中也必须有这些!如我在父组件中使用了(@change='change' :item='item' :filterData='filterData')这些代码,我在子组件的调用中也进行了这些操作,这也是递归组件的关键之处,就如js中函数递归的传参一样
数据格式:
filterData: [
{
con: "",
list: [
{
field: "1-1",
id: "1111",
ifShowField: true,
recordEvery: false
},
{
con: "或",
list: [
{
field: "3-3",
id: "3333",
ifShowField: true,
recordEvery: false
},
{
con: "或",
list: [
{
field: "3-3",
id: "3333",
ifShowField: true,
recordEvery: false
},
{
field: "aaaaa",
id: "7777",
ifShowField: true,
recordEvery: false
}
]
},
{
field: "aaaaa",
id: "7777",
ifShowField: true,
recordEvery: false
}
]
},
{
con: "或",
list: [
{
field: "3-3",
id: "3333",
ifShowField: true,
recordEvery: false
},
{
con: "或",
list: [
{
field: "3-3",
id: "3333",
ifShowField: true,
recordEvery: false
},
{
field: "aaaaa",
id: "7777",
ifShowField: true,
recordEvery: false
}
]
},
{
field: "aaaaa",
id: "7777",
ifShowField: true,
recordEvery: false
}
]
},
{
field: "2-2",
id: "2222",
ifShowField: true,
recordEvery: false
},
{
con: "或",
list: [
{
field: "3-3",
id: "3333",
ifShowField: true,
recordEvery: false
},
{
field: "aaaaa",
id: "7777",
ifShowField: true,
recordEvery: false
}
]
}
]
}
],