[React]Antd Cascader组件地区选择

前言

表单中添加一个地区选择功能,要求支持增删改查功能。

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;
};

总结

  1. 新增时通过pid依次获取下一级菜单
  2. 回显时通过递归获取菜单
  3. 后端还是要好好设计一下,这太麻烦了~
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值