React Antd Cascader组件地区选择方式
作者:水果摊见
文章介绍了在表单中实现地区选择功能,使用Cascader组件动态加载数据,需通过loadData和递归处理实现增删改查,存储和回显需完整id数组以支持多级地区展示,强调后端设计对数据关联(id/pid)的重要性
前言
表单中添加一个地区选择功能,要求支持增删改查功能。
Cascader
使用Cascader组件动态加载地区选项。使用 loadData 实现动态加载选项,(loadData 与 showSearch 无法一起使用)。
这里使用了Form.Item组件。
<Form.Item
label={'地区'}
name={'region'}
rules={[
{
required: true,
},
]}
>
<Cascader
options={regionOptions}
loadData={loadRegionData}
changeOnSelect
/>
</Form.Item>
这里的options在每次选择后,通过loadData加载新的选项。
Cascader组件的选项每次存储的值为option中的value,展示的值为label,并通过isLeaf判断是否有子菜单
{
value: 'zhejiang',
label: 'Zhejiang',
isLeaf: false
}
新增
点击新增按钮时,会获取地区接口,并根据用户选择加载地区。
地区在数据库中是根据id和pid进行关联的。如果pid是0,代表是1级地区;pid是1,代表当前地区是id为1的地区的子地区;同理,pid是2,代表当前地区是id为2的地区的子地区。

const [regionOptions, setRegionOptions] = useState([]);
// 获取初始化的地区
// 这个接口参数为空时会返回地区的一级菜单,不为空时传递pid,返回该pid下面的子菜单
const getRegionList = async params => {
try {
const res = await selectCityInfoApi(params)
const newRes = res.map(item => {
return {
...item, //返回的值是例如{value: value, label: label, level: 1}
isLeaf: item.level === '3', //判断level, 如果是三级菜单就没有子菜单
}
})
if (!params) {
setRegionOptions(newRes);
}
return newRes;
} catch (error) {
console.log(error);
}
}
useEffect(() => {
if (isAdd === false && editId) {
// 编辑
} else {
// 新增
getRegionList(); // 接口参数为空时会返回地区的一级菜单
}
}, [isAdd]);
const loadRegionData = (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1]; // 取选择的最后一个值
getRegionList({ pid: targetOption.value }).then(res => { // 用pid去调用接口
targetOption.children = res; // 把返回的值放到children中
setRegionOptions([...regionOptions]); // 更新regionOptions
})
}
回显
表单保存之后,存储的值是选择的地区的id,这就有一个问题,回显时拿到的也是这个id。
例如如果选择:[北京、北京、东城区],那么存储的时候只会存东城的id:3,但是回显需要他们的id:[1, 2, 3] 才能回显地区,并且在用户点击时展开时还需要相关的option数据。
useEffect(() => {
if (isAdd === false && editId) {
// 编辑
setLoading(true);
getGroupResourceById(editId) // 获取当前id的数据
.then(data => { // data中有region的id和Pid
setLoading(false);
getRegionList();
getRegionDetails(data.regionPid, [String(data.regionPid), String(data.region)]).then((res) => {
form.setFieldsValue({ ...data, region: res });
buildRegionSelectOptions(res).then(selectRegions => {
setRegionOptions(selectRegions);
})
});
})
.catch(err => {
setLoading(false);
});
} else {
// 新增
getRegionList();
}
}, [isAdd]);
const getRegionDetails = async (regionId, regions = []) => { // 收集region的id
try {
const res = await selectCityInfo({ id: regionId }) // 这个接口入参是region的id,返回当前id的具体信息
if (!!res.length && res[0]?.pid && res[0].pid !== 0) {
regions.unshift(String(res[0].pid));
}
return regions;
} catch (error) {
console.error('获取地区列表失败:', error);
return regions;
}
};
// 通过一级菜单的pid去递归调用,获取regionOption
const buildRegionSelectOptions = async (regionIds) => {
const result = [...regionOptions];
if (!regionIds || regionIds.length === 0) return result;
const processRegion = async (ids, parentNode = null) => {
if (!ids || ids.length === 0) return;
const [currentId, ...restIds] = ids;
try {
const regionData = await getRegionList({ pid: currentId });
const currentNode = regionData.map(item => ({...item, isLeaf: item.level === '3'}));
parentNode.children = currentNode;
if (restIds.length > 0) {
const currentIndex = parentNode.children.findIndex(item => item.value === restIds[0]);
await processRegion(restIds, parentNode.children[currentIndex]);
}
} catch (error) {
console.error(`获取地区 ${currentId} 失败:`, error);
}
};
let currentRegion = regionOptions.find(item => item.value === regionIds[0]);
await processRegion(regionIds, currentRegion);
return result;
};
总结
- 新增时通过pid依次获取下一级菜单
- 回显时通过递归获取菜单
- 后端还是要好好设计一下,这太麻烦了~
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
