省市区三级联动需用只读input作显示层,背后由select或JS数据驱动;禁用直接输入,预处理扁平化数据,严格按顺序清空重置下级选项,并校验选择完整性。

用 input 标签做省市区三级联动,本质是“假输入 + 真选择”
纯靠 <input> 自身无法实现联动,它只是个文本框。真正可行的路径是:用 <input> 作为显示层(只读),背后绑定 <select> 或 JS 数据结构驱动下拉逻辑。强行给 <input> 加 oninput 去匹配省市数据,会遇到模糊搜索歧义、拼音首字母冲突、多音字等一堆问题,不现实。
常见错误现象:input 绑了 onchange 却没触发;用户手动输入后选中项丢失;切换省份时城市列表没清空导致叠加渲染。
- 必须把
<input>的readonly属性设为true,禁用直接输入 - 每个
input要配一个隐藏的<select>或用data-value存真实编码(如110000) - 点击
input时,聚焦对应下拉容器(不是select元素本身,而是自定义弹层)
fetch 加载省市区 JSON 数据时要注意跨域和结构扁平化
国内常用数据源(如国家统计局或第三方 API)返回的往往是嵌套很深的树形结构,直接遍历渲染效率低,且 fetch 默认不带 CORS 头。本地开发时容易卡在 TypeError: Failed to fetch。
推荐做法是预处理数据:把三级结构压平成数组,每项带 code、name、level、parentCode 字段。例如:
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
[
{ "code": "110000", "name": "北京市", "level": 1 },
{ "code": "110100", "name": "市辖区", "level": 2, "parentCode": "110000" },
{ "code": "110101", "name": "东城区", "level": 3, "parentCode": "110100" }
]
- 加载失败时 fallback 到本地 JSON 文件(避免白屏),路径用相对路径如
./area-data.json - 不要在每次点击都
fetch全量数据,首次加载后存到const areaData = []全局变量里 - 注意部分接口返回的是 GB/T 2260 编码,末尾两位为
00表示省级,00且倒数第三位非0是市级,否则是区级
监听 change 事件更新下一级时,必须重置后续层级值
用户选完省份后,城市和区要清空并置灰,否则会出现“广东省→杭州市→西湖区”这种错乱组合。很多实现只更新 options,但忘了清掉 value 和 disabled 状态。
关键逻辑顺序不能错:
- 先设城市
<select>的innerHTML = '',再设disabled = true - 再设区级同理,且要清空关联的
input显示值(input.value = '') - 最后才根据新选中的省份 code 过滤出对应城市列表并注入
- 如果用自定义弹层,还要调用
hideAllDropdowns()避免多个弹层同时打开
移动端适配中 click 和 touchstart 混用会导致点透或延迟
在 iOS Safari 或微信内置浏览器里,只绑 click 可能有 300ms 延迟,但全换成 touchstart 又容易触发点透(穿透到下层元素)。更麻烦的是,某些安卓 WebView 对 focus() 调用不响应,导致弹层打不开。
- 对
input同时绑定touchstart和click,并在 handler 开头加event.preventDefault() - 弹层容器用
position: fixed+z-index,避免被overflow: hidden截断 - 不要依赖
input.focus()来唤起键盘 —— 三级联动根本不需要键盘,readonly已经屏蔽了它
最易被忽略的一点:省市区编码在提交表单前必须校验完整性。比如用户只选了省,没动城市,后端收到 province=110000&city=&district=,这时应前端拦截提示“请完整选择地址”,而不是交由后端报 400。


















