一、element-ui级联选择器

很多时候 ,我们需要做多级关联时,会用到级联选择器。比如element-ui的级联选择器。

element ui 多级联动回显 elementui 级联选择_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  //第三级数据
      })

最后

至此前端处理两种类型的数据完成。