添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

之前对 Ant Design TreeSelect 树选择控件进行了基础封装,这一篇主要讲异步数据的处理,也顺便讲了一下深度优先遍历(DFS)和广度优先遍历(BFS)

封装原因:之前部门树数据是一次性加载的, 存在着性能问题,数据量很大的时候,加载很缓慢,而且组件可能崩溃;因此改成异步加载数据,系统中用到的地方很多,不可能每个地方都改一下,进行封装统一处理。

Ant Design TreeSelect 树选择控件二次封装及原理: juejin.cn/post/700328…

  • departSource :数据源,默认展示根部门和一级部门数据
  • onLoadData :异步加载数据,根据部门 id 查询直接子部门数据,加载到数据源中
  • treeExpandedKeys :展开的树节点,默认展开根部门
  • <template>
      <a-tree-select
        v-bind="$attrs"
        v-on="selectListeners"
        class="m-width"
        placeholder="请选择"
        searchPlaceholder="请输入部门名称"
        :treeDefaultExpandAll="false"
        :treeData="departSource"
        :load-data="onLoadData"
        @treeExpand="handleExpand"
        :treeExpandedKeys="treeExpandedKeys"
        :dropdownStyle="{ maxHeight: '400px' }"
        show-search
        treeNodeFilterProp="title"
      </a-tree-select>
    </template>
    
    const tree = [
        id: 1,
        name: "根部门",
        departNumber: 6,
        children: [
            id: 11,
            pid: 1,
            name: "一级部门1",
            departNumber: 2,
            children: [
              { id: 111, pid: 11, name: "二级部门1", departNumber: 0 },
              { id: 112, pid: 11, name: "二级部门2", departNumber: 0 },
          { id: 21, pid: 1, name: "一级部门2", departNumber: 0 },
          { id: 31, pid: 1, name: "一级部门3", departNumber: 0 },
          { id: 41, pid: 1, name: "一级部门4", departNumber: 0 },
          { id: 51, pid: 1, name: "一级部门5", departNumber: 0 },
            id: 61,
            pid: 1,
            name: "一级部门6",
            departNumber: 2,
            children: [
              { id: 611, pid: 61, name: "二级部门3", departNumber: 0 },
              { id: 612, pid: 61, name: "二级部门4", departNumber: 0 },
    

    深度优先遍历(DFS)

    深度优先遍历(DFS -- Depth First Search):从根节点出发,先访问一个完整的子树,再访问相邻未被访问的子树,直到所有子树都被访问完。访问子树的操作和之前一样,直到所有节点都被访问到为止。

    简单的图形化展示:

    函数代码展示:

    * 深度优先遍历 * @params {Array} tree 树数据 * @params {Array} func 操作函数 const dfsTransFn = (tree, func) => { tree.forEach((node) => { func(node); // 如果子树存在,递归调用 if (node.children && node.children.length) { dfsTransFn(node.children, func); // 示例1:打印节点 dfsTransFn(tree, (node) => { console.log(`${node.id}...${node.name}`); // 示例2:树转化为列表 let treeList = []; dfsTransFn(tree, (node) => { treeList.push(node); console.log(treeList);

    函数运行效果展示:

    广度优先遍历(BFS)

    广度优先遍历(BFS -- Breadth First Search):从根节点出发,先访问根节点所在的初始队列,进行操作,并将该节点的所有子节点加入队列中;再依次访问队列中的第一个元素,进行操作,直到队列为空。即访问树结构的第 n+1 层前必须先访问完第 n 层,彻底搜索整个树结构,直到找到结果为止。

    简单的图形化展示:

    函数代码展示:

    * 广度优先遍历 * @params {Array} tree 树数据 * @params {Array} func 操作函数 const bfsTransFn = (tree, func) => { let node, list = [...tree]; // shift()-取第一个;pop()-取最后一个 while ((node = list.shift())) { func(node); // 如果子树存在,递归调用 node.children && list.push(...node.children); // 示例1:打印节点 bfsTransFn(tree, (node) => { console.log(`${node.id}...${node.name}`); // 示例2:树转化为列表 let treeList = []; bfsTransFn(tree, (node) => { treeList.push(node); console.log(treeList);

    函数运行效果展示:

  • 深度优先:不需要记住所有节点,占用空间小;堆栈形式,先进后出
  • 广度优先:需要记录所有节点,占用空间大;队列形式,先进先出
  • 初始化处理

    初始化时,需要请求根部门数据和一级部门数据

  • queryRootDepartDetail:该接口用于获取根部门详情数据;
  • queryChildDeparts:该接口通过部门 id 获取直接子部门数据;
  • isLeaf:是否是叶子节点,通过是否存在子部门(o.departNumber>0)判断
  • <script>
      import * as R from "ramda";
      // 转化函数:将接口字段转换成组件需要的数据
      // 主要是添加isLeaf参数
      const tranFn = (data) => {
        if (R.type(data) === "Array" && R.length(data) > 0) {
          return R.map(
            (o) => ({
              value: o.id,
              key: o.id,
              title: o.name,
              children: [],
              // 是否是叶子节点:通过是否存在子部门判断
              isLeaf: !o.departNumber,
        } else {
          return [];
      export default {
        created() {
          this.queryAsyncDeparts();
        methods: {
          async queryAsyncDeparts() {
            let res = await queryRootDepartDetail();
            // 请求根部门数据
            let departId = R.path(["data", "id"], res);
            // 请求根部门数据
            this.departSource = tranFn([res.data]);
            // 默认展开根节点
            this.treeExpandedKeys = [departId];
            // 通过根部门ID加载子部门数据,初始化展示根部门和一级部门数据
            this.queryMoreData(departId);
          queryMoreData(departId) {},
    </script>
    

    实现效果展示:

    异步加载树节点

    要在对应的部门树节点添加数据,有两种方式:深度优先遍历和广度优先遍历

    <script>
       *  异步加载数据(深度优先遍历)
       *  @params {Array} tree 树数据
       *  @params {number} departId 部门id
       *  @params {Array} childList 加载的数据
      const transTreeFn = (tree, departId, childList) => {
        return R.map((o) => {
          if (o.key === departId) {
            o.children = tranFn(childList);
          } else if (o.children && o.children.length) {
            transTreeFn(o.children, departId, childList);
          return o;
        }, tree);
      export default {
        methods: {
          // “加载更多”事件
          onLoadData(treeNode) {
            const { key } = treeNode.dataRef;
            this.queryMoreData(key);
          // 异步数据加载
          queryMoreData(departId) {
            // 通过部门ID,在数据源中找到对应的数据,填充子部门数据
            queryChildDeparts(departId).then((res) => {
              // 模拟数据
              // let childList = [
              //   { id: 1001, name: "添加部门1", departNumber: 0 },
              //   { id: 1002, name: "添加部门2", departNumber: 0 },
              // ];
              let childList = R.pathOr([], ["data"], res);
              this.departSource = transTreeFn(
                this.departSource,
                departId,
                childList
          // 控制展开节点
          handleExpand(expandedKeys) {
            this.treeExpandedKeys = expandedKeys;
    </script>
    复制代码
    分类:
    前端