一种级联前后端实现方案

概述

在网站业务开发中,经常遇到的一个需求就是,级联选择。

实现

先给出实现后的效果:
在这里插入图片描述

后端

后端业务Service类实现如下:

public List<CmdbVo> listDepartment() {
    String url = cmdbUrl + String.format(DEPARTMENTS, cmdbKey);
    try {
        Resp resp = JSONObject.parseObject(HttpUtil.doGet(url), Resp.class);
        if (resp != null && resp.getCode() == 0) {
            List<CmdbVo> cmdbVoList = Lists.newArrayListWithCapacity(resp.getContent().size());
            resp.getContent().forEach(x -> {
                CmdbVo vo = new CmdbVo();
                BeanUtils.copyProperties(x, vo);
                cmdbVoList.add(vo);
            });
            return cmdbVoList;
        }
    } catch (Exception e) {
        log.error("listDepartment failed: ", e);
    }
    return Collections.emptyList();
}

HttpUtil.doGet(url)实现的主要逻辑很简单,就是调用一下外部接口,http://100.111.55.67:9999/cmdb/v0.2.0/departments?page_size=1000,接口返回数据格式如下:

{
    "code": 0,
    "content": [
        {
            "id": "4561",
            "level": 1,
            "name": "业务后台",
            "parent_id": "1",
            "parent_name": "信仰科技"
        }
    ],
    "msg": "success"
}

CmdbModel实例类POJO用于接收返回的数据。

@Data
public class CmdbModel {
    private Integer id;
    private String name;
    @JSONField(name = "parent_id")
    private Integer parentId;
}

CmdbVo是暴露给前端的视图类。和上面的实体类字段一模一样,至于个中原因,可查看文档:FastJson序列化和反序列化问题记录

@Data
public class CmdbVo {
    private Integer id;
    private String name;
    private Integer parentId;
    // 级联效果关键点
    List<CmdbVo> children;
}

现在要实现级联效果,仅仅依赖上面的方法是不够的。

此处打断一下,让我们从前端角度来思考。鉴于我们使用的组件是ant design,官方给出的文档:Cascader级联选择。官方实例很简单,需要value,label,children 3个字段数据,label就是页面看到的问题,value是label标签对于的文本数据,children是子集。
在这里插入图片描述
基于此,我们的改造如下:

  1. CmdbVo增加一个字段List<CmdbVo> children;
  2. 返回return cmdbVoList;变成:return this.getFatherNode(cmdbVoList);

需要的工具类方法

/**
 * 获取父节点
 */
public List<CmdbVo> getFatherNode(List<CmdbVo> cmdbVoList) {
    List<CmdbVo> newTreeDataList = new ArrayList<>();
    for (CmdbVo item : cmdbVoList) {
        if (item.getParentId() == null || item.getParentId() <= 1) {
            // 获取父节点下的子节点
            item.setChildren(getChildrenNode(item.getId(), cmdbVoList));
            List<CmdbVo> children = item.getChildren();
            if (CollectionUtils.isEmpty(children)) {
                item.setChildren(Collections.emptyList());
            }
            newTreeDataList.add(item);
        }
    }
    return newTreeDataList;
}

/**
 * 获取子节点
 */
private static List<CmdbVo> getChildrenNode(Integer pid, List<CmdbVo> treeDataList) {
    List<CmdbVo> newTreeDataList = new ArrayList<>();
    for (CmdbVo item : treeDataList) {
        if (item.getParentId() == null) {
            continue;
        }
        // 这是一个子节点
        if (item.getParentId().equals(pid)) {
            // 递归获取子节点下的子节点
            item.setChildren( getChildrenNode(item.getId(), treeDataList));
            List<CmdbVo> children = item.getChildren();
            if (CollectionUtils.isEmpty(children)) {
                item.setChildren(Collections.emptyList());
            }
            newTreeDataList.add(item);
        }
    }
    return newTreeDataList;
}

数据表

另外值得一提的是,数据表域对象PO实体类属性定义为String,直接把层级以数组的形式存储下来:
在这里插入图片描述
故而有一个前端JSON解析过程。

前端

import {getDepartmentIdList,} from '@/pages/Board/service'
import ViCascader from "@/components/Customize/Vi_Cascader";

  const [departmentIds, setDepartmentIds] = useState<any>([])

  useEffect(() => {
    // 部门域
    getDepartmentIdList().then((res: any) => {
      setDepartmentIds(res.data)
    })
  }, [])
  
  // 搜索
  const filter: any = (inputValue: string, path: any) => {
    return path.some(
      (option: any) => option.name?.toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
    );
  };

  useEffect(() => {
    if (modalData.type !== 'add') {
      let param: any = {};
      param = {
        ...modalData.data,
        // 编辑时需要解析一下数据
        departmentId: modalData?.data?.departmentId ? JSON.parse(modalData?.data?.departmentId) : [],
      };
    }
  }, []);

  return (
    <div style={{height: 'calc(100vh - 148px)', overflow: 'auto'}}>
      <Form
        form={form}
        name="filterForm"
        colon={false}
        hideRequiredMark
        style={{height: '100%'}}
      >
          <Form.Item
          name="departmentId"
          label={
            <div>
              部门域&nbsp;
              <Tooltip
                title={<span style={{color: "#3F81EE"}}>打标签规则:选择“四级目录”(一般指二级部门)进行标注,如果没有四级目录则选择最深的一级目录标注。</span>}>
                <InfoCircleOutlined/>
              </Tooltip>
            </div>
          }
          rules={[{required: true, message: '请选择资产标记-部门域'}]}
        >
          <ViCascader
            options={departmentIds}
            placeholder="请选择资产标记-部门域"
            fieldNames={{label: 'name', value: 'id'}}
            showSearch={{filter}}
            style={{width: 500}}
            allowClear
            changeOnSelect
          />
        </Form.Item>
      </Form>
    </div>
  )

参考

Cascader级联选择

posted @ 2022-01-15 17:23  johnny233  阅读(25)  评论(0编辑  收藏  举报  来源