十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

amis NestedSelect 级联选择器:从配置到源码的完整实现指南

amis NestedSelect 级联选择器:从配置到源码的完整实现指南 amis NestedSelect 级联选择器从配置到源码的完整实现指南【免费下载链接】amis前端低代码框架通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis在 amis 低代码框架中nested-select级联选择器别名cascader-select是处理多层级树形数据选型的核心表单项无论是地区省市选择、组织架构勾选、还是商品类目圈选都可以用一份 JSON 配置完成。本文基于 amis 官方文档docs/zh-CN/components/form/nestedselect.md展开完整覆盖其基本用法、动态选项、父级/叶子节点选择策略onlyLeaf、cascade、withChildren、onlyChildren、选项搜索与标签收纳等全部配置项并结合源码packages/amis/src/renderers/Form/NestedSelect.tsx深入剖析每一项配置在底层的实现机制帮助你在配置表单的同时理解其值计算逻辑做到配置正确、取值可控。组件定位与源码结构在 amis 的渲染器注册体系中级联选择器由单一组件类NestedSelectControl同时承担两个 type源码文件NestedSelect.tsx末尾通过OptionsControl({ type: nested-select })与OptionsControl({ type: cascader-select })装饰器分别注册为NestedSelectControlRenderer与CascaderSelectControlRenderer两者共用同一套实现类型定义AMISNestedSelectSchema继承自AMISFormItemWithOptions即它除了自身独有的层级选择属性外还支持所有普通表单项属性以及选项组通用属性类型注册入口见 SchemaFull.ts 与 SchemaMinimal.ts 中nested-select: AMISNestedSelectSchema的映射保证 JSON Schema 校验与编辑器提示可用。从源码结构看该组件是一个 Class 组件核心状态为stack: ArrayArrayOption——一个“选项栈”每一层对应级联面板中的一列鼠标悬停某节点时通过onMouseEnter推入或弹出children从而实现横向逐级展开的交互移动端mobileUI下则切换为PopUp Cascader的弹窗交互这一点在 NestedSelect.tsx 中可以确认。基本用法静态 options最直接的用法是在表单项中直接内联options树形数组每个节点包含label、value子层级通过children递归声明{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] } ] }表单提交时选中值以name字段这里是nestedSelect写入提交数据。默认值格式由joinValues决定见属性表默认true时即为逗号分隔的字符串例如a,b-1,b-3。动态选项source 支持数据表达式与 API除了内联options还可以用source从上下文数据或后端 API 获取选项树。方式一引用上下文数据。先在page.data中定义好选项树再用${options}表达式注入{ type: page, data: { options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] }, body: { type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, source: ${options} } ] } }方式二直接配置 API 地址字符串或 API 对象{ type: page, body: { type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, source: /api/mock2/form/getTreeOptions } ] } }从源码看source的加载、缓存与重新拉取由 amis-core 的OptionsControl高阶组件统一负责NestedSelectControl的reload(subpath?, query?)方法NestedSelect.tsx只是转调this.props.reloadOptions——这也是动作表中reload动作的底层实现。当source返回新的 options 时componentDidUpdate会检测到prevProps.options ! this.props.options并重置stack确保级联面板回到第一列。onlyLeaf只允许选中叶子节点1.8.0 及以上版本支持更早版本可以通过不给分支节点设置value来近似实现。单选场景下设置onlyLeaf: true后即便分支节点配置了value点击它也不会被选中。对应源码逻辑非常直接——handleOptionClick中NestedSelect.tsxif (onlyLeaf this.isParentNode(option)) { return; }其中isParentNode的判定是Array.isArray(option.children) option.children.length 0。多选场景下onlyLeaf同样生效handleCheck开头有相同的拦截并且可以搭配cascade使用使父节点的勾选框点击也不产生选中动作。{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, onlyLeaf: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] } ] }多选 cascade的变体只需在上面示例基础上追加两项{ type: nested-select, name: nestedSelect, label: 级联选择器, onlyLeaf: true, cascade: true, multiple: true }官方单测 nestedSelect.test.tsx 中专门覆盖了onlyLeaf在单选与多选下的行为可作为该特性正确性的验证依据。cascade选中父节点是否自动选中子节点多选模式默认具有“父子联动”语义勾选父节点会自动勾上所有子节点。设置cascade: true后父子之间完全独立勾选父节点不会连带子节点。同一页面对比两个选择器的完整示例来自原文档{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect1, label: 默认自动选中子节点, multiple: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] }, {type: divider}, { type: nested-select, name: nestedSelect2, label: 不自动选中子节点, multiple: true, cascade: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] } ] }源码中handleCheck对“点击带 children 的父节点”分了三条路径NestedSelect.tsxcascade: true直接xor(items, [option])即只做父子独立勾选/取消不触碰子节点withChildren: true先flattenTree([option])打平整个子树若子树已全部选中则xor全量移除否则union全量加入——这就是“选父带子”的取值语义onlyChildren: true打平时只保留叶节点flattenTreeWithLeafNodes父节点自身的 value 永远不进入结果默认三者皆 false勾父节点只把父节点本身加入值中但会先把其子树内已勾选项剔除。此外还有一段“自底向上回填父节点”的补偿逻辑NestedSelect.tsx当cascade关闭时如果某父节点的全部 children 都被勾选则自动把父节点补进值里withChildren/onlyChildren模式下处理略有差异onlyChildren下父节点 value 不会被 push。这解释了为什么多选时勾选完 B-1/B-2/B-3 后提交值里会出现b。withChildren / onlyChildren选中父节点时值的构成与cascade控制“勾选行为”不同withChildren与onlyChildren控制的是“值里放什么”。三者组合起来覆盖了四种取值语义配置组合点击父节点 B 后值的变化默认仅父节点值b子节点已选项被剔除withChildren: true父节点值 全部后代值如b,b-1,b-2,b-3onlyChildren: true仅后代叶节点值如b-1,b-2,b-3不含bcascade: true仅父节点值b子节点不受影响withChildren示例两个选择器对照来自原文档{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect1, label: 默认不自动带上子节点的值, multiple: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] }, {type: divider}, { type: nested-select, name: nestedSelect2, label: 自动带上子节点的值, multiple: true, withChildren: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] } ] }onlyChildren示例使用三层树B-1 下还有 D-1/D-2/D-3用于验证“只取叶节点”时深层级的表现同时开启了clearable{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect1, label: 默认不自动带上子节点的值, multiple: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ { label: B-1, value: b-1, children: [ {label: D-1, value: d-1}, {label: D-2, value: d-2}, {label: D-3, value: d-3} ] }, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] }, {type: divider}, { type: nested-select, name: nestedSelect2, label: 只包含子节点的值, multiple: true, onlyChildren: true, clearable: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ { label: B-1, value: b-1, children: [ {label: D-1, value: d-1}, {label: D-2, value: d-2}, {label: D-3, value: d-3} ] }, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] } ] }从源码看onlyChildren的“全选外观”也有专门处理renderOptions中当某父节点自身不在selectedOptions中、但onlyChildren开启且其所有 children 均已选中时会把该节点的勾选框渲染为选中态NestedSelect.tsx避免出现“子节点全勾、父节点却是未勾选”的视觉歧义而值中依旧只有子节点。hideNodePathLabel仅展示选中节点文本多选时默认显示完整路径 label如B / B-1。设置hideNodePathLabel: true后选择框内仅展示当前选中节点自身的labelField值隐藏祖先节点的labelField{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect, label: 展示已选择节点的祖先节点的文本信息, value: a,b-1,b-3, multiple: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] }, {type: divider}, { type: nested-select, name: nestedSelect2, label: 仅展示已选择节点的文本信息, value: a,b-1,b-3, multiple: true, cascade: true, hideNodePathLabel: true, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ] } ] }对应实现位于renderValueNestedSelect.tsx开启时直接返回labelToString(option[labelField])关闭时先用getTreeAncestors求出节点的全部祖先再用 / 拼接成路径文本并把完整路径放入title属性作为悬浮提示。此外 Words.tsx 中nested-select的静态展示只读态同样遵循hideNodePathLabel决定是否输出节点路径保证编辑态与展示态一致。searchable前端选项搜索配置searchable: true后输入框内输入内容时会做前端过滤默认按value或label字段匹配若配置了valueField或labelField则以对应字段为准{ type: form, api: /api/mock2/form/saveForm, body: [ { type: nested-select, name: nestedSelect1, label: 级联选择器单选, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b1}, {label: B-2, value: b2}, {label: B-3, value: b3} ] }, {label: C, value: c} ], searchable: true, multiple: false, joinValues: true, clearable: true }, { type: nested-select, name: nestedSelect2, label: 级联选择器多选, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b1}, {label: B-2, value: b2}, {label: B-3, value: b3} ] }, {label: C, value: c} ], searchable: true, multiple: true, joinValues: true, clearable: true } ] }底层实现分为两层过滤handleInputChangeNestedSelect.tsx调用 amis-core 的filterTree借助matchSorter以CONTAINS阈值对labelField/valueField做包含匹配只要节点自身或其祖先路径命中、或节点仍有 children就会保留在过滤后的树中避免搜索导致层级丢失。结果渲染renderSearchResultNestedSelect.tsx不再展示多级面板而是把过滤后的树打平成单列结果列表逐项高亮命中关键字renderTextByKeyword空结果时显示noResultsText默认文案见 zh-CN.ts 中的noResult: 未找到任何结果。选中标签上的路径文本高亮、输入框占位提示searchPromptText默认文案为“搜索”也在同一套实现中。maxTagCount限制标签最大展示数量3.3.0 及以上版本。多选场景下选项很多时选择框会被标签撑满。maxTagCount可限制标签最大展示数量超出部分收纳进一个 Popover 浮层浮层行为通过overflowTagPopover配置TooltipObject默认{placement: top, trigger: hover, showArrow: false, offset: [0, -10]}详细字段可参考 tooltip-wrapper 属性表。注意该属性仅在multiple: true时生效。{ type: form, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, multiple: true, maxTagCount: 3, overflowTagPopover: { title: 已选项 }, value: Apple,Banana,Blackberry,Blueberry,Cherry,Carambola,Coconut,Kiwifruit,Lemon,Pineapple,Vegetables,Wheat,Rice, options: [ { label: 水果, value: Fruits, children: [ {label: 苹果, value: Apple}, {label: 香蕉, value: Banana}, {label: 黑莓, value: Blackberry}, {label: 蓝莓, value: Blueberry}, {label: 樱桃, value: Cherry}, {label: 杨桃, value: Carambola}, {label: 椰子, value: Coconut}, {label: 猕猴桃, value: Kiwifruit}, {label: 柠檬, value: Lemon}, {label: 菠萝, value: Pineapple} ] }, { label: 蔬菜, value: Vegetables, children: [ {label: 西兰花, value: Broccoli}, {label: 菠菜, value: Spinach}, {label: 南瓜, value: Pumpkin} ] }, { label: 谷物, value: Grain, children: [ {label: 小麦, value: Wheat}, {label: 水稻, value: Rice}, {label: 燕麦, value: Oats} ] } ] } ] }从源码结构看maxTagCount与overflowTagPopover被直接透传给 amis-ui 的ResultBox组件ResultBox.tsx当typeof maxTagCount number maxTagCount 0时标签列表只渲染maxTagCount个可见标签其余折叠为 N ...形态的收纳标签悬浮浮层即由overflowTagPopover的属性剔除children/content后构造。完整属性表当做选择器表单项使用时除支持普通表单项属性表中的配置外还支持以下配置属性名类型默认值说明版本optionsArrayobject或Arraystring-选项组sourcestring或 API-动态选项组delimiterbooleanfalse拼接符labelFieldbooleanlabel选项标签字段valueFieldbooleanvalue选项值字段joinValuesbooleantrue拼接值extractValuebooleanfalse提取多选值autoFillobject-自动填充cascadebooleanfalse设置true时选中父节点时不自动选择子节点withChildrenbooleanfalse设置true时选中父节点时值里面将包含子节点的值否则只会保留父节点的值onlyChildrenbooleanfalse多选时选中父节点时是否只将其子节点加入到值中searchablebooleanfalse可否搜索searchPromptTextstring输入内容进行检索搜索框占位文本noResultsTextstring未找到任何结果无结果时的文本multiplebooleanfalse可否多选hideNodePathLabelbooleanfalse是否隐藏选择框中已选择节点的路径 label 信息onlyLeafbooleanfalse只允许选择叶子节点maxTagCountnumber-标签的最大展示数量超出数量后以收纳浮层的方式展示仅在多选模式开启后生效3.3.0overflowTagPopoverTooltipObject{placement: top, trigger: hover, showArrow: false, offset: [0, -10]}收纳浮层的配置属性详细配置参考 Tooltip3.3.0补充说明源码中AMISNestedSelectSchema还定义了borderModefull/half/none边框模式与menuClassName下拉弹层 css 类两个属性NestedSelect.tsx文档属性表未单独列出但可直接在配置中使用。组件默认值在defaultProps中集中声明cascade/withChildren/onlyChildren/onlyLeaf/hideNodePathLabel均为falsecheckAll: true且全选文案取自多语言Select.checkAll。事件表与 onEvent 监听当前组件会对外派发以下事件可以通过onEvent来监听这些事件并通过actions配置执行的动作在actions中可通过${事件参数名}或${event.data.[事件参数名]}获取事件数据详见事件动作。[name]表示当前组件绑定的名称即name属性如果没有配置name则通过value取值。事件名称事件参数说明change[name]: string组件的值选中值变化时触发blur[name]: string组件的值输入框失去焦点时触发focus[name]: string组件的值输入框获取焦点时触发源码中dispatchEventNestedSelect.tsx还会把options同时以items别名传出、value、selectedItems多选为全部选中项单选为第一项注入事件数据且事件返回值带prevented时组件会放弃本次onChange——这意味着你可以在actions中拦截并改写选中行为。change事件示例{ type: form, debug: true, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2} ] }, {label: C, value: c} ], onEvent: { change: { actions: [ { actionType: toast, args: { msg: ${event.data.value|json} } } ] } } } ] }动作表与跨组件控制当前组件对外暴露以下特性动作其他组件可以通过指定actionType: 动作名称、componentId: 该组件id来触发这些动作动作配置通过args: {动作配置项名称: xxx}传参详见事件动作-触发其他组件的动作动作名称动作配置说明clear-清空reset-将值重置为初始值。6.3.0 及以下版本为resetValuereload-重新加载调用source刷新数据域数据刷新重新加载setValuevalue: string更新的值更新数据开启multiple时多个值用,分隔clear 动作{ type: form, debug: true, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ], value: a, id: clear_text }, { type: button, label: 清空, onEvent: { click: { actions: [ { actionType: clear, componentId: clear_text } ] } } } ] }reset 动作如果配置了resetValue则重置时使用resetValue的值否则使用初始值。从源码doActionNestedSelect.tsx可以看到优先级先取formStore.pristine或store.pristine中该name对应的初始值取不到再回退到resetValue最终兜底为空字符串。{ type: form, debug: true, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ], value: a, id: reset_text }, { type: button, label: 重置, onEvent: { click: { actions: [ { actionType: reset, componentId: reset_text } ] } } } ] }reload 动作只有选择器模式支持即配置了source的场景用于重新加载选择器的数据源{ type: form, debug: true, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, id: reload_type, source: /api/mock2/form/getTreeOptions, value: a }, { type: button, label: 重新加载, onEvent: { click: { actions: [ { actionType: reload, componentId: reload_type } ] } } } ] }setValue 动作{ type: form, debug: true, body: [ { type: nested-select, name: nestedSelect, label: 级联选择器, options: [ {label: A, value: a}, { label: B, value: b, children: [ {label: B-1, value: b-1}, {label: B-2, value: b-2}, {label: B-3, value: b-3} ] }, {label: C, value: c} ], value: a, id: setvalue_text }, { type: button, label: 赋值, onEvent: { click: { actions: [ { actionType: setValue, componentId: setvalue_text, args: { value: b } } ] } } } ] }小结与工程建议选型别名nested-select与cascader-select指向同一组件NestedSelect.tsx两套写法可互换值语义先想清楚多选表单中cascade/withChildren/onlyChildren三者共同决定“勾父节点时提交什么值”建议按后端期望的值格式先定配置再用debug: true观察提交载荷验证层级深、节点多时优先searchable纯前端过滤不额外请求配合hideNodePathLabel/maxTagCount控制选择框视觉密度只关心叶子节点如权限点、末级类目直接用onlyLeaf不要依赖“不给父节点配 value”的旧写法仅 1.8.0 以下兼容;版本相关onlyLeaf需 1.8.0maxTagCount/overflowTagPopover需 3.3.0reset动作名在 6.3.0 之前为resetValue升级前请核对当前 amis 版本静态只读展示static场景由 StaticHoc.tsx 接管同样识别hideNodePathLabel编辑态与展示态文案保持一致。以上所有配置均可在docs/zh-CN/components/form/nestedselect.md的在线示例中直接试跑实现细节可对照 NestedSelect.tsx、ResultBox.tsx 与单测 nestedSelect.test.tsx 进一步核查。【免费下载链接】amis前端低代码框架通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表