zoningjs
Version:
中华人民共和国行政区划:省市区、乡镇街道
188 lines (179 loc) • 8.46 kB
HTML
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行政区划联动示例</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
min-height: 100vh;
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
display: flex; align-items: flex-start; justify-content: center;
gap: 24px; flex-wrap: wrap;
padding: 32px 24px; color: #1f2937;
}
.card {
width: 100%; max-width: 560px; flex: 1 1 420px;
background: rgba(255, 255, 255, .96);
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, .35);
padding: 28px 32px 24px;
}
h1 { font-size: 19px; margin-bottom: 4px; }
.sub { font-size: 13px; color: #6b7280; margin-bottom: 20px; }
.grid { display: grid; gap: 12px; grid-template-columns: 1fr; }
.field label {
display: block; font-size: 12px; color: #6b7280;
margin-bottom: 6px; letter-spacing: 1px;
}
select {
width: 100%; padding: 10px 12px; font-size: 14px;
border: 1px solid #d1d5db; border-radius: 8px;
background: #fff; cursor: pointer; outline: none;
transition: border-color .15s, box-shadow .15s;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: right 10px center;
}
select:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, .15); }
select:disabled { background-color: #f3f4f6; color: #9ca3af; cursor: not-allowed; }
.result {
margin-top: 20px; padding: 14px 16px;
background: #f8fafc; border: 1px solid #e5e7eb;
border-radius: 10px; font-size: 14px; line-height: 1.9;
}
.result .path { font-weight: 600; color: #111827; }
.result .code { font-family: Consolas, monospace; color: #2563eb; }
.result .empty { color: #9ca3af; }
.status { margin-top: 12px; font-size: 12px; color: #9ca3af; }
</style>
</head>
<body>
<div class="card" id="card3">
<h1>三级联动 · 省市区</h1>
<div class="sub">数据源 0.json</div>
<div class="grid">
<div class="field"><label>省 / 直辖市 / 自治区</label><select data-k="p" disabled><option value="">加载中…</option></select></div>
<div class="field"><label>城市</label><select data-k="c" disabled><option value="">请选择</option></select></div>
<div class="field"><label>区 / 县</label><select data-k="d" disabled><option value="">请选择</option></select></div>
</div>
<div class="result"><span class="empty">请依次选择省、市、区县</span></div>
<div class="status"></div>
</div>
<div class="card" id="card4">
<h1>四级联动 · 省市区 / 乡镇街道</h1>
<div class="sub">数据源 4.json(含省市区 + 乡镇街道)</div>
<div class="grid">
<div class="field"><label>省 / 直辖市 / 自治区</label><select data-k="p" disabled><option value="">加载中…</option></select></div>
<div class="field"><label>城市</label><select data-k="c" disabled><option value="">请选择</option></select></div>
<div class="field"><label>区 / 县</label><select data-k="d" disabled><option value="">请选择</option></select></div>
<div class="field"><label>乡镇 / 街道</label><select data-k="s" disabled><option value="">请选择</option></select></div>
</div>
<div class="result"><span class="empty">请依次选择省、市、区县、乡镇街道</span></div>
<div class="status"></div>
</div>
<script>
let data0 = {}; // 0.json:省市区三级
let data4 = {}; // 4.json:省市区 + 乡镇街道(一个文件包含全部四级)
// 名称 + 括号 code:前三级补足 6 位,第四级补足 9 位
const labelOf = (x, len) => `${x.n}(${String(x.c).padEnd(len, "0")})`;
// levels: 层级键序列;src: 数据源对象(三级组用 data0,四级组用 data4)
function createCascade(cardEl, levels, src, hasStreet) {
let picked = {};
const sels = {};
cardEl.querySelectorAll("select").forEach(s => sels[s.dataset.k] = s);
const resultEl = cardEl.querySelector(".result");
// 一级 2 位、二级 4 位、三级 6 位、四级 9 位
const codeLen = k => ({ p: 2, c: 4, d: 6, s: 9 })[k] || 6;
function fill(sel, list, placeholder, len) {
sel.innerHTML = "";
sel.add(new Option(placeholder, ""));
(list || []).forEach(x => sel.add(new Option(labelOf(x, len), x.c)));
sel.disabled = !list || !list.length;
}
function resetFrom(level) {
let start = levels.indexOf(level);
for (let i = start; i < levels.length; i++) {
delete picked[levels[i]];
fill(sels[levels[i]], null, "请选择", codeLen(levels[i]));
}
}
function render() {
let names = [], codes = [];
levels.forEach(k => { if (picked[k]) { names.push(picked[k].n); codes.push(picked[k].c); } });
if (!names.length) {
resultEl.innerHTML = `<span class="empty">请依次选择${hasStreet ? "省、市、区县、乡镇街道" : "省、市、区县"}</span>`;
return;
}
// 结果区显示标准行政区划代码:三级以内补 6 位,第四级补 9 位
let code = String(codes[codes.length - 1]).padEnd(hasStreet ? 9 : 6, "0");
resultEl.innerHTML = `<div class="path">${names.join(" / ")}</div><div>行政区划代码:<span class="code">${code}</span></div>`;
}
sels.p.addEventListener("change", () => {
let v = sels.p.value;
picked.p = v ? src["0"].find(x => x.c == v) : null;
if (picked.p) fill(sels.c, src[v], "请选择", 4);
resetFrom(picked.p ? "d" : "c");
render();
});
sels.c.addEventListener("change", () => {
let v = sels.c.value;
picked.c = v ? (src[picked.p.c] || []).find(x => x.c == v) : null;
if (picked.c) fill(sels.d, src[v], "请选择", 6);
delete picked.d;
if (hasStreet) {
delete picked.s;
fill(sels.s, null, "请选择", 9);
}
render();
});
sels.d.addEventListener("change", () => {
let v = sels.d.value;
picked.d = v ? (src[picked.c.c] || []).find(x => x.c == v) : null;
if (hasStreet) {
delete picked.s;
fill(sels.s, picked.d ? src[v] : null, picked.d ? (src[v] ? "请选择" : "(无四级数据)") : "请选择", 9);
}
render();
});
if (hasStreet) {
sels.s.addEventListener("change", () => {
let v = sels.s.value;
picked.s = v ? (src[picked.d.c] || []).find(x => x.c == v) : null;
render();
});
}
return {
ready() { fill(sels.p, src["0"], "请选择", 2); },
setStatus(t) { cardEl.querySelector(".status").textContent = t; },
};
}
(async function init() {
let g3 = createCascade(document.getElementById("card3"), ["p", "c", "d"], data0, false);
let g4 = createCascade(document.getElementById("card4"), ["p", "c", "d", "s"], data4, true);
g3.setStatus("正在加载 0.json …");
g4.setStatus("正在加载 4.json …");
try {
let [r0, r4] = await Promise.all([fetch("./0.json"), fetch("./4.json")]);
Object.assign(data0, await r0.json());
Object.assign(data4, await r4.json());
g3.ready();
g4.ready();
let n0 = Object.values(data0).reduce((s, l) => s + l.length, 0);
let n4 = Object.entries(data4)
.filter(([k]) => k.length === 6)
.reduce((s, [, l]) => s + l.length, 0);
g3.setStatus(`数据加载完成:省市区 ${n0} 条(0.json)`);
g4.setStatus(`数据加载完成:乡镇街道 ${n4} 条(4.json 含省市区)`);
} catch (e) {
let msg = "数据加载失败,请通过本地 HTTP 服务访问本页面(如 npx serve 或 python -m http.server),不能用 file:// 直接打开";
g3.setStatus(msg);
g4.setStatus(msg);
console.error(e);
}
})();
</script>
</body>
</html>