# 行政区划查询实战:省市区三级联动选择器前端实现
> 接口 / 接入点:行政区划查询(apiCode 1149)· 1149-1 区域查询 + 1149-2 子区域查询 · 免费服务
> 请求方式:POST / GET · 返回格式:JSON · 适用人群:前端 / 全栈工程师 · 阅读时间:约 8 分钟
## 核心要点
- 三级联动本质是个「选上级 → 用其 id 拉下级 options」的状态机。
- 省用区域查询(1149-1,`level=1`)初始化;市 / 区用子区域查询(1149-2,`parentId`)逐级驱动。
- 免费接口,前端可直接调用(AppKey 放前端仅适合内部 / 演示;生产建议走后端代理,避免泄露 AppKey)。
## Why:省市区联动是最常见的地址交互
注册、下单、收货地址几乎都要填省 / 市 / 区。手写一份静态字典不仅臃肿还容易过期;用行政区划查询实时拉,数据跟着民政部走,三级之间天然联动。这篇给出一个可跑的前端原型。
## What:交互与数据流
| 步骤 | 操作 | 调用 |
|------|------|------|
| 1 | 初始化省列表 | 1149-1,`areaName=中国`,`level=1`(或任意已知省名逐个拉) |
| 2 | 选省 → 拉市 | 1149-2,`parentId` = 省的 `id` |
| 3 | 选市 → 拉区 | 1149-2,`parentId` = 市的 `id` |
> 省列表初始化:可直接用区域查询 `level=1` 逐省查询,或预先缓存全国省级(仅 34 条,适合一次性缓存)。
## How:前端组件原型(原生 JS)
```html
<select id="prov"></select>
<select id="city"></select>
<select id="dist"></select>
<script>
const APPKEY = "YOUR_APPKEY"; // 生产环境请改走后端代理,勿在前端暴露
const base = "https://route.showapi.com";
async function loadChildren(sel, parentId) {
const url = `${base}/1149-2?appKey=${APPKEY}&parentId=${parentId}&page=1`;
const body = await fetch(url).then(r => r.json()).then(d => d.showapi_res_body);
sel.innerHTML = body.data.map(o => `<option value="${o.id}">${o.areaName}</option>`).join("");
return body;
}
async function initProvinces() {
const body = await fetch(`${base}/1149-1?appKey=${APPKEY}&areaName=北京市&level=1`).then(r => r.json()).then(d => d.showapi_res_body);
const prov = document.getElementById("prov");
prov.innerHTML = body.data.map(o => `<option value="${o.id}">${o.areaName}</option>`).join("");
prov.onchange = () => loadChildren(document.getElementById("city"), prov.value).then(b => {
if (b.data[0]) loadChildren(document.getElementById("dist"), b.data[0].id);
});
prov.dispatchEvent(new Event("change"));
}
initProvinces();
</script>
```
### 后端代理示例(Node.js,避免暴露 AppKey)
```javascript
const APPKEY = process.env.SHOWAPI_APPKEY; // 放服务端环境变量
app.get("/api/regions", async (req, res) => {
const { parentId } = req.query;
const url = `https://route.showapi.com/1149-2?appKey=${APPKEY}&parentId=${parentId}&page=1`;
const body = await fetch(url).then(r => r.json()).then(d => d.showapi_res_body);
res.json(body.data.map(o => ({ id: o.id, name: o.areaName })));
});
```
## 返回示例(市级 options)
```json
{ "showapi_res_body": { "ret_code": "0", "data": [
{"areaName": "广州市", "id": "440100000000"},
{"areaName": "深圳市", "id": "440300000000"}
], "allNum": "21", "maxSize": "20", "allPage": "2" } }
```
## 进阶 / 边界
- **AppKey 安全**:免费接口虽无直接资损,但前端暴露 AppKey 仍可被他人冒用 / 触发限流。生产请走后端代理。
- **分页**:下级 > 20 条时分页,联动拉全需循环 `page` 到 `allPage`。
- **缓存**:省级仅 34 条、市级数百条,适合前端 / 边缘缓存(详见缓存设计篇),减少重复请求。
## FAQ
**Q:省列表怎么初始化最快?**
省级数量很少(约 34 条),一次性拉全后缓存即可,不必每次实时请求。
**Q:前端直接放 AppKey 安全吗?**
免费接口虽无资损,但仍有冒用 / 限流风险;生产建议后端代理,AppKey 放服务端。
**Q:联动到区后还要镇 / 村怎么办?**
继续用子区域查询从区的 `id` 下钻;乡镇 / 村委会级也可用区域查询设 `level=4/5`。
**Q:下级超过 20 条联动会断吗?**
会少拉;联动拉全需按 `allPage` 翻页循环,或后端一次性聚合后下发。
## 相关能力 / 下一步阅读
- [行政区划查询子区域查询实战:用 parentId 逐级下钻省→市→区→街道](https://www.showapi.com/guides/region-query-subregion-1149)
- [免费接口也要缓存:行政区划查询低频更新下的缓存设计](https://www.showapi.com/guides/region-query-cache-1149)
- [地址录入与校验方案:用行政区划查询规范用户填地址](https://www.showapi.com/guides/region-query-address-1149)
- **本系列共 11 篇**:查看[行政区划查询指南总目录](https://www.showapi.com/guides/region-query-guides-1149)