概述
在网站业务开发中,经常遇到的一个需求就是,级联选择。
实现
先给出实现后的效果:

后端
后端业务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是子集。

 基于此,我们的改造如下:
- CmdbVo增加一个字段List<CmdbVo> children;
- 返回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>
              部门域 
              <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级联选择
                










