示例图
问题描述
看到这张图,我们首先就会在脑海中构建出一种思路:利用树形表格根据后端返回的树形数据中的名称和锁的状态来渲染,并在点击锁时调取接口来刷新该列表来实现改变锁的状态。但是事与愿违,第一次从后端获取的数据格式并不会携带锁的状态标识:
[
{
name: "用户权限",
id: "1",
children: [
{ name: "删除权限", id: "101" },
{ name: "新增权限", id: "102" },
{ name: "编辑权限", id: "103" },
{
name: "查询权限",
id: "104",
children: [
{ name: "查询所有权限", id: "10401" },
{ name: "查询部分权限", id: "10402" },
],
},
],
},
{ name: "产品权限", id: "2" },
{ name: "菜单权限", id: "3" },
]
而是要通过后续调取接口来获取id和锁及其他状态
[
{ lock: true,id: "1",auth: false, },
{ lock: true, id: "2", auth: false },
{ lock: true, id: "3", auth: false },
{ lock: true, id: "101", auth: false },
{ lock: true, id: "102", auth: false },
{ lock: true, id: "103", auth: false },
{ lock: true, id: "104", auth: false },
{ lock: false, id: "10401", auth: false },
{ lock: false, id: "10402", auth: false },
],
那么这时候我们思路就要转换一下:要将后续返回的状态对现有数据进行递归(因为不知道后续是否还会再添加几层节点),并通过id找到对应的对象,并把与之对应其他的属性给它塞进去。
解决问题
直接上代码(这里先埋一个坑)
<template>
<div>
<el-row>
<el-col>
<div class="table-box">
<el-table
:data="treeData"
:show-header="false"
row-key="id"
:tree-props="{ children: 'children' }"
:row-style="{ height: '40px' }"
:cell-style="{ padding: '0' }"
>
<el-table-column prop="name" label="日期"> </el-table-column>
<el-table-column width="200px">
<template slot-scope="scope">
<el-button
class="lock"
type="text"
:icon="
scope.row.lock == true ? 'el-icon-unlock' : 'el-icon-lock'
"
:style="
scope.row.lock == true ? 'color: #409eff' : 'color: black'
"
@click="changeLock(scope.row)"
></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
<div class="btn-box">
<el-button type="primary" @click="searchAuth">获取权限</el-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
//现有数据
treeData: [
{
name: "用户权限",
id: "1",
children: [
{ name: "删除权限", id: "101" },
{ name: "新增权限", id: "102" },
{ name: "编辑权限", id: "103" },
{
name: "查询权限",
id: "104",
children: [
{ name: "查询所有权限", id: "10401" },
{ name: "查询部分权限", id: "10402" },
],
},
],
},
{ name: "产品权限", id: "2" },
{ name: "菜单权限", id: "3" },
],
//后端数据
attributes: [
{
lock: true,
id: "1",
auth: false,
},
{ lock: true, id: "2", auth: false },
{ lock: true, id: "3", auth: false },
{ lock: true, id: "101", auth: false },
{ lock: true, id: "102", auth: false },
{ lock: true, id: "103", auth: false },
{ lock: true, id: "104", auth: false },
{ lock: false, id: "10401", auth: false },
{ lock: false, id: "10402", auth: false },
],
};
},
methods: {
//开关锁
changeLock(row) {
row.lock = !row.lock;
},
//获取权限按钮
searchAuth() {
this.reduceStatus(this.treeData, this.attributes);
},
//递归向现有数据添加属性
reduceStatus(treeArr, returnArr) {
//对现有树进行遍历
for (let i = 0; i < treeArr.length; i++) {
let item = treeArr[i];
//找到对应id的对象
const target = returnArr.find((current) => current.id === item.id);
if (target) {
Object.keys(target).forEach((key) => {
//向对象中新添加属性
item[key] = target[key];
});
}
//判断childern是否存在且是否存在子节点,如果存在继续向下循环
const children = item.children || [];
if (children.length > 0) {
this.reduceStatus(children, returnArr);
}
}
},
},
};
</script>
当我以为问题到这就可以结束的时候,对示例测验时发现点击锁时并没有任何变化。
发现问题
为了检查这一问题,我们在获取权限后对这个现有的树数据进行打印
从这我们不难看到,新属性虽然已经添加进去,但是后续添加的属性并没有转换为setter/getter,原因就在于在添加属性时想当然的利用 item[key] = taget[key]
来添加属性,而这一行为导致vue并没有对后续添加的属性进行劫持转换setter/getter
最终解决方案
在reduceStatus
方法中添加属性时弃用 item[key] = taget[key]
转而使用Vue.$set()
方法来确保新属性同样是响应式的
reduceStatus(treeArr, returnArr) {
//对现有树进行遍历
for (let i = 0; i < treeArr.length; i++) {
let item = treeArr[i];
//找到对应id的对象
const target = returnArr.find((current) => current.id === item.id);
if (target) {
Object.keys(target).forEach((key) => {
this.$set(item, key, target[key]); //向对象中新添加属性并确保属性为响应式
});
}
//判断childern是否存在且是否存在子节点,如果存在继续向下循环
const children = item.children || [];
if (children.length > 0) {
this.reduceStatus(children, returnArr);
}
}
},