Cascader级联组件开发实战与常见问题解决方案

UX云碧 组件 阅读 1,929
赞 14 收藏
二维码
手机扫码查看
反馈

先上代码,再聊细节

最近项目里又用到了 Cascader 级联选择器,说实话这东西看着简单,真要搞顺手还是得折腾一阵子。我直接贴个最常用的写法——基于 Element Plus 的 Vue 3 实现:

Cascader级联组件开发实战与常见问题解决方案

<template>
  <el-cascader
    v-model="selectedValue"
    :options="regionOptions"
    :props="{ lazy: true, lazyLoad }"
    placeholder="请选择省市区"
    clearable
  />
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const selectedValue = ref([])

const lazyLoad = (node, resolve) => {
  const { level } = node
  // 根节点 level 为 0
  if (level === 0) {
    // 加载省份
    fetchRegions('province').then(data => resolve(data))
  } else if (level === 1) {
    // 加载城市
    const parentId = node.value
    fetchRegions('city', parentId).then(data => resolve(data))
  } else if (level === 2) {
    // 加载区县
    const parentId = node.value
    fetchRegions('district', parentId).then(data => resolve(data))
  }
}

const fetchRegions = async (type, parentId = null) => {
  const params = parentId ? { parent_id: parentId } : {}
  const res = await axios.get(https://jztheme.com/api/regions/${type}, { params })
  return res.data.map(item => ({
    value: item.id,
    label: item.name,
    leaf: type === 'district' // 区县是最后一级
  }))
}
</script>

这段代码亲测有效,而且支持懒加载(lazy),数据量大时特别有用。别小看这个 leaf 字段,它决定了是否还能继续展开——我第一次写的时候忘了加,结果所有选项都显示成可展开状态,用户点进去一片空白,差点被产品骂死。

踩坑提醒:这三点一定注意

说几个我反复栽过的坑,你最好现在就记下来:

  • value 和 label 别搞混:Cascader 内部靠 value 做唯一标识,但展示用的是 label。如果你后端返回的字段叫 codeareaId,记得在 fetchRegions 里手动映射成 value,否则选中后回显会出问题。
  • 懒加载时 leaf 必须动态判断:比如某些城市下面没有区县(比如直辖市),这时候即使 level=2,也得设 leaf: true,否则组件会尝试再去请求下一级,结果报错或空白。我的做法是在接口返回时就标好 is_leaf 字段,前端直接用。
  • v-model 的格式要对:它是个数组,比如 [110000, 110100, 110105]。如果你从后端拿到的是字符串 "110000,110100,110105",记得 split 成数组再赋值,否则组件不会自动展开到对应层级。

有一次我从缓存里读了个字符串直接塞给 v-model,页面看起来选中了,但点开 cascader 发现第一级都没展开,查了半天才发现是类型不对。这种低级错误真的能浪费你半小时。

这个场景最好用:动态表单里的联动

除了地址选择,我还经常在动态表单里用 Cascader 做分类筛选。比如商品类目、组织架构这些树形结构数据。

但有个细节:如果用户已经选了某个路径,然后你动态切换了 options(比如换了店铺导致类目变了),Cascader 不会自动清空或重置。这时候你得手动监听外部变化,强制重置 selectedValue

watch(() => props.shopId, () => {
  selectedValue.value = []
  // 同时可能还要重置 options,或者触发重新 lazyLoad
})

不然就会出现“旧类目还显示着,但点开发现数据对不上”的诡异情况。这个问题我在两个项目里都遇到过,第二次才反应过来要手动清空。

高级技巧:自定义节点内容 + 搜索优化

默认的 Cascader 只能显示 label,但有时候你想加个图标、状态标签,或者高亮搜索关键词。Element Plus 支持 scoped slot,可以这么干:

<template #default="{ node, data }">
  <span>
    {{ data.label }}
    <el-tag size="small" v-if="data.is_hot">热</el-tag>
  </span>
</template>

不过要注意,用了自定义节点后,搜索功能可能会失效——因为默认搜索只匹配原始 label。如果你启用了 filterable,又改了显示内容,得配合 props.checkStrictly 或者自己实现过滤逻辑。

说到搜索,Cascader 自带的搜索其实不太智能。比如搜“北京”,只能匹配到“北京市”这一级,不能同时把“北京市 > 朝阳区”也列出来。如果业务要求全局模糊搜,建议别硬刚 Cascader,直接换成 Select + 远程搜索,体验反而更好。

性能问题:大数据量怎么搞?

如果你的级联有上万节点(比如全国所有村镇),千万别一次性加载全量数据。哪怕用了虚拟滚动,渲染压力也很大。

我的建议就一个:坚持用懒加载。而且接口要设计成按需查询,每次只拉当前节点的子集。另外,可以加个 loading 状态提示,避免用户以为卡死了。

实在不行,考虑分步选择:第一步选省,第二步单独弹窗选市和区。虽然交互复杂了点,但胜在稳定。我之前做过一个政务系统,乡镇数据太庞大,最后就是拆成两步走的。

结尾碎碎念

以上是我这几年用 Cascader 踩过的坑和总结的套路。说实话,这组件看似简单,但细节特别多,尤其是和真实业务数据对接时,各种边界情况都能把你整懵。

目前这个方案在我手上的项目跑得还算稳,虽然偶尔还有小问题(比如移动端点击延迟),但无伤大雅。如果你有更好的处理方式,比如用 Ant Design Vue 的实现更优雅,欢迎评论区交流。

这个技术的拓展用法还有很多,比如结合地图联动、做成可编辑的路径输入框等等,后续我会继续分享这类实战博客。希望这篇能帮你少走点弯路。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论