概述

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

实现

先给出实现后的效果:

一种级联前后端实现方案_级联

后端

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

一种级联前后端实现方案_json_02


基于此,我们的改造如下:

  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_03


故而有一个前端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级联选择​