多级联动应采用状态驱动的数据流而非嵌套事件,每级change只清空后续级、发起请求并渲染结果,需维护选中值与路径状态、唯一标识请求、局部render、后端返回扁平结构或分层数据。
多级联动不是嵌套事件,而是状态驱动的数据流
无限级分类(比如省→市→区→街道→社区→楼栋)不能靠在
里写多层
或递归绑定来实现。
layui
的 select 事件是单点触发的,你必须主动维护「当前各级选中值」和「下一级待加载路径」两个状态,否则用户快速切换时极易出现选项错乱、加载覆盖、清空遗漏等问题。
核心逻辑是:每级 select 的 change 只负责「清空它之后所有级」+「发起下一级请求」+「渲染匹配结果」。不关心上一级是否“合法”,只依赖传入的父 ID 参数是否有效。
所有 select 必须有唯一
和
(如
、
),便于精准定位和操作 DOM
禁用同步请求(
),它会阻塞 UI,且与现代 Promise 流不兼容;统一用
或
配合
控制并发
后端接口应统一设计为
,避免为每级写单独路由
如何防止第四级加载时第三级还没选完就触发请求
这是四级以上联动最常崩的点:用户连点「区」两次,发出两个请求,后返回的街道列表直接覆盖前一个正确结果,导致数据错位。Layui 的
不做顺序校验,也不管你发了多少个请求。
解决方案不是加 loading 遮罩,而是给每个请求打唯一标记并做响应比对:
为每个 select 绑定一个自增的
变量(如
),每次新请求前
并存入闭包或 data 属性
请求成功回调中,先判断
,仅匹配才执行
和
请求发起前立即禁用下一级 select:
失败时还原禁用状态,并用
提示,不要静默吞掉错误
为什么不能每次都在 change 里调
是全量重绘——它会重新扫描页面所有
元素,重建 Layui 的下拉结构。这会导致三个实际问题:
基于Layui自定义内容轮播插件
基于Layui自定义内容轮播插件是一款表格数据为ajax加载的本地json数据,本地查看会有跨域问题导致数据不能显示。
下载
已选中的其他级 select 状态丢失(尤其是带
的,输入框焦点会消失)
四级联动下平均每次耗时 12–18ms(Chrome 实测),用户连点时明显卡顿
如果某级正在异步加载,全量 render 可能提前把未完成的
渲染为空白
正确做法是局部重绘:
,其中
是该 select 所在的父容器 class 或 ID(需提前加)。这样只影响目标 select,不影响其他级状态和性能。
后端返回数据结构要适配前端递归渲染逻辑
无限级联动的难点不在前端 DOM 操作,而在如何让后端一次返回「可递归展开的扁平结构」。推荐两种方案:
方案一(推荐):后端返回带
和
的扁平数组,前端用 Map 缓存所有节点,change 时按
过滤子集,避免重复请求
方案二:后端按层级分页返回,如
,前端根据
动态创建下一级 select 并插入 DOM
无论哪种,都要求字段统一:必含
、
(或
),可选
、
标识末级节点
容易被忽略的是:末级节点(如社区)可能也需要继续展开(比如选社区后加载楼栋),所以不要在前端硬编码「最多四级」,而应通过后端返回的
字段动态控制是否渲染下一级 select 容器。
form.on('select(xxx)')if-elsenameidid="province"id="city"async: false$.ajaxfetchPromise.all/api/select/children?parent_id=123&level=3form.render()requestIdlet cityReqId = 0++cityReqIdresponse.requestId === currentReqIdhtml()form.render()layui.$('#street').prop('disabled', true).html('')layer.msg()form.render('select')form.render('select')selectlay-searchoptionlayui.form.render('select', 'street-container')street-containerparent_idlevelparent_id{data: [{value:1,name:"朝阳区"},...], next_level: "street"}next_levelvaluenamelabeldisabledis_leafhas_children: true/false