一、element-ui级联选择器
很多时候 ,我们需要做多级关联时,会用到级联选择器。比如element-ui的级联选择器。
而级联选择器的数据结构又比较复杂,在前后端分离的今天,大多数设备比较高端,这个时候某些数据完全可以在前端处理,减少服务器压力。当然数据处理这块大多数还是交给后端同学来做,但后端同学一般情况下又很忙,毕竟一个后端对接三个前端。这个时候这个数据就需要前端去处理。
二、级联选择器的数据结构
我们先来看看element-ui级联选择器需要的数据结构。
{
value: 'zhinan', //一级
label: '指南',
children: [{
value: 'shejiyuanze', //二级
label: '设计原则',
children: [{
value: 'yizhi', //三级
label: '一致'
}, {
value: 'fankui',
label: '反馈'
}]
}, {
value: 'daohang', //一级
label: '导航',
children: [{
value: 'cexiangdaohang', //二级
label: '侧向导航'
}, {
value: 'dingbudaohang',
label: '顶部导航'
}]
}]
}
三、递归处理数据
后端返回的数据
let data = [
{id: 2, pid: 0, label: "指南"},
{id: 3, pid: 0, label: "导航"},
{id: 4,pid: 2, label: "设计原则"},
{id: 5, pid: 3, label: "侧向导航"},
{id: 6, pid: 7, label: "Basic"},
{id: 7, pid: 0, label: "组件"},
{id: 8, pid: 4, label: "一致"},
{id: 9, pid: 4, label: "反馈"},
{id: 10, pid: 4, label: "效率"},
{id: 11, pid: 4, label: "可控"},
{id: 12, pid: 6, label: "Form"},
];
根据约定,pid等于0是最顶级,也就是第一级,而pid等于id时,就是他的子级。也就是说id为2的“指南”是顶级,id为4且pid为2的“设计原则”是他的子级,id等于8pid等于4的“一致”是设计原则的子级。
这样的格式最方便的方式就是递归,接下来我们将使用递归处理数据格式。
//这里是后端同学传的数据
let data = [
{id: 2, pid: 0, label: "指南"},
{id: 3, pid: 0, label: "导航"},
{id: 4,pid: 2, label: "设计原则"},
{id: 5, pid: 3, label: "侧向导航"},
{id: 6, pid: 7, label: "Basic"},
{id: 7, pid: 0, label: "组件"},
{id: 8, pid: 4, label: "一致"},
{id: 9, pid: 4, label: "反馈"},
{id: 10, pid: 4, label: "效率"},
{id: 11, pid: 4, label: "可控"},
{id: 12, pid: 6, label: "Form"},
];
// 声明变量
let pid = "pid",pId = 0,id = "id",label = "label";
// 创建一个内部函数selector
const selector = (pId, arr) => {
// 声明一个空数组,用于储存处理后的数据
const res = [];
// 这里使用forEach循环
arr.forEach((i) => {
// 如果某对象的pid等于传进来的数 这里第一次传进来的pId = 0也就是查找最顶级
if (i[pid] === pId) {
// 自己调用自己并传入当前位置的对象的id和整个数组对象
// 由于查出来的是子级 所以赋值给children
const children = selector(i[id], data);
// 原来的数据不符合element要求的格式,所以这里进行重构
const obj = {
label: i[label],
value: i[id],
};
// 如果有 children 则插入 obj 中
if (children.length) {
obj.children = children;
}
// 将obj插入数组最后一位
res.push(obj);
}
});
// 返回生成的数据
return res;
};
// 调用函数,传入pId和数组并赋值给selector
let disposeData= selector(pId, data);
// 打印selector查看数据是否符合要求
console.log(disposeData)
由此我们使用递归完成了element-ui级联选择器的数据处理。
但是基于复用原则,我们应该将其封装。
四、封装函数
新建一个js文件
packagingFunction.js
将以下代码封装
export const packagingSelector = (res) => {
// 取出我们传入的数据
const { pId, data, pid, label, id } = res
// 创建一个内部函数selector
const selector = (pId, arr) => {
// 声明一个空数组,用于储存处理后的数据
const res = []
// 这里使用forEach循环
arr.forEach(i => {
// 如果某对象的pid等于传进来的数 这里第一次传进来的pId = 0也就是查找最顶级
if (i[pid] === pId) {
// 自己调用自己并传入当前位置的对象的id和整个数组对象
// 由于查出来的是子级 所以赋值给children
const children = selector(i[id], data)
// 原来的数据不符合element要求的格式,所以这里进行重构
const obj = {
label: i[label],
value: i[id]
}
// 如果有 children 则插入 obj 中
if (children.length) {
obj.children = children
}
// 将obj插入数组最后一位
res.push(obj)
}
})
// 返回生成的数据
return res
}
// 调用并返回函数,传入pId和数组并赋值给selector
return selector(pId, data)
}
使用方式
在要调用的页面内先声明
import { packagingSelector } from "../plugins/packagingFunction";
在需要修改数据的地方调用方法
const disposeData= packagingSelector({
data: data, // 传入数组
pId: 0, // 顶级的 pid 为 0
pid: "pid", // 传入pid
id: "id", // 传入 id
label: "label" // 传入label
});
console.log(disposeData);
五、特殊情况
也许有时候,后端同学不会只返回一个数组。
就像这样
let arrOne = [
{ id: 1, value: "zhinan", label: "指南" },
{ id: 2, value: "zujian", label: "组件" },
{ id: 3, value: "ziyuan", label: "资源" },
{ id: 4, value: "ziyuana", label: "资源" },
];
let arrTwo = [
{ id: 1, arrOneId: 1, value: "shejiyuanze", label: "设计原则" },
{ id: 2, arrOneId: 1, value: "daohang", label: "导航" },
{ id: 3, arrOneId: 2, value: "basic", label: "Basic" },
{ id: 4, arrOneId: 2, value: "from", label: "From" },
{ id: 5, arrOneId: 3, value: "axure", label: "Axure Components" },
{ id: 6, arrOneId: 3, value: "sketch", label: "Sketch Templates" },
{ id: 7, arrOneId: 3, value: "jiaohu", label: "组件交互文档" },
];
let arrThree = [
{ id: 1, arrTwoId: 1, value: "yizhi", label: "一致" },
{ id: 2, arrTwoId: 1, value: "fankui", label: "反馈" },
{ id: 3, arrTwoId: 1, value: "xiaolu", label: "效率" },
{ id: 4, arrTwoId: 1, value: "kekong", label: "可控" },
{ id: 5, arrTwoId: 2, value: "cexiangdaohang", label: "侧向导航" },
{ id: 6, arrTwoId: 2, value: "kekong", label: "顶部导航" },
];
下面将代码封装进 packagingFunction.js
export const packagingSelectorSpecial = (res) =>{
// 取出我们传入的数据
const { oneId, twoIds, twoId, threeId, arrOneData, arrTwoData,arrThreeData } = res
// 用于存放唯一Id
let arrTwos = [];
// 最终返回数组
let result = [];
// 第一次循环出子级
arrTwoData.forEach((j) => {
// 循环出孙子
arrThreeData.forEach((k) => {
// 声明第三级 方便后面使用
let objArr = {
value: k.value,
label: k.label,
};
// 如果第二级的id等于第三级的threeId
if (j[twoIds] == k[threeId]) {
// includes查询是否存在,存在返回true,否则返回false
// 如果arrTwos不存在j.id 表示第一次循环到该id
if (arrTwos.includes(j[twoIds]) == false) {
// 声明第二级 方便后面使用
let obj = {
twoId: j[twoId],
value: j.value,
label: j.label,
// children 存放相同threeId的第三级
children: [],
};
// 将第三级插入第二级的children中
obj.children.push(objArr);
// 将处理好的obj插入result数组中
result.push(obj);
// 在arrTwos添加当前第二级id
arrTwos.push(j[twoIds]);
} else { //如果存在id则说明已经执行过,直接将第三级数据插入第二级children中
// indexOf 查询当前j.id在arrTwos中的位置,让数据正确插入
let index = arrTwos.indexOf(j[twoIds]);
result[index].children.push(objArr);
}
}
});
// 第二级没有子级的情况
// 判断是否执行过,没有则执行
if (arrTwos.includes(j[twoIds]) == false) {
let obj = {
twoId: j[twoId],
value: j.value,
label: j.label
};
// 直接将第二级插入result中
result.push(obj);
}
});
// 用于存放唯一Id
let arrOnes = [];
// 最终返回数组
let resultFaters = [];
// 第二次循环 将第二级插入对应的第一级中
arrOneData.forEach((i) => {
result.forEach((k) => {
// 如果第一级id等于第二级twoId
if (i[oneId] == k.twoId) {
let ArrResult = {
value: k.value,
label: k.label,
children: k.children,
};
// 如果arrOnes不存在j.id 表示第一次循环到该id
if (arrOnes.includes(i[oneId]) == false) {
let obj = {
value: i.value,
label: i.label,
children: [],
};
obj.children.push(ArrResult);
resultFaters.push(obj);
arrOnes.push(i[oneId]);
} else { //如果存在id则说明已经执行过,直接将第三级数据插入第二级children中
let index = arrOnes.indexOf(i[oneId]);
resultFaters[index].children.push(ArrResult);
}
}
});
// 第一级没有子级的情况
// 判断是否执行过,没有则执行
if (arrOnes.includes(i[oneId]) == false) {
let obj = {
value: i.value,
label: i.label
};
resultFaters.push(obj);
}
});
return resultFaters;
}
使用方式
在要调用的页面内先声明
import { packagingSelectorSpecial } from "../plugins/packagingFunction";
在需要修改数据的地方调用方法
let arrOne = [
{ id: 1, value: "zhinan", label: "指南" },
{ id: 2, value: "zujian", label: "组件" },
{ id: 3, value: "ziyuan", label: "资源" },
{ id: 4, value: "ziyuana", label: "资源" },
];
let arrTwo = [
{ id: 1, arrOneId: 1, value: "shejiyuanze", label: "设计原则" },
{ id: 2, arrOneId: 1, value: "daohang", label: "导航" },
{ id: 3, arrOneId: 2, value: "basic", label: "Basic" },
{ id: 4, arrOneId: 2, value: "from", label: "From" },
{ id: 5, arrOneId: 3, value: "axure", label: "Axure Components" },
{ id: 6, arrOneId: 3, value: "sketch", label: "Sketch Templates" },
{ id: 7, arrOneId: 3, value: "jiaohu", label: "组件交互文档" },
];
let arrThree = [
{ id: 1, arrTwoId: 1, value: "yizhi", label: "一致" },
{ id: 2, arrTwoId: 1, value: "fankui", label: "反馈" },
{ id: 3, arrTwoId: 1, value: "xiaolu", label: "效率" },
{ id: 4, arrTwoId: 1, value: "kekong", label: "可控" },
{ id: 5, arrTwoId: 2, value: "cexiangdaohang", label: "侧向导航" },
{ id: 6, arrTwoId: 2, value: "kekong", label: "顶部导航" },
];
const disposeData = packagingSelectorSpecial({
oneId:'id', //第一级id
twoIds:'id', //第二级id
twoId:'arrOneId', //第二级关联第一级的id
threeId:'arrTwoId', //第三级关联第二级的id
arrOneData: arrOne, //第一级数据
arrTwoData: arrTwo, //第二级数据
arrThreeData: arrThree //第三级数据
})
最后
至此前端处理两种类型的数据完成。