三級聯動(省→市→區/縣)是表單設計常見需求。本文提供基於 PHP + MySQL + 原生 AJAX (XMLHttpRequest) + DOM 操作的完整實現,無依賴第三方庫。
CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`parent_id` int(11) DEFAULT 0, -- 0=省級, 省id=市級, 市id=區級
`level` tinyint(1) DEFAULT 1, -- 1=省, 2=市, 3=區
PRIMARY KEY (`id`),
KEY `parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
<?php
header('Content-Type: application/json; charset=utf-8');
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', '');
$parentId = isset($_GET['parent_id']) ? (int)$_GET['parent_id'] : 0;
$stmt = $pdo->prepare('SELECT id, name FROM areas WHERE parent_id = ? ORDER BY id');
$stmt->execute([$parentId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['code' => 200, 'data' => $rows]);
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>三級聯動</title></head><body>
<select id="province" onchange="loadChildren(this.value, 'city')"></select> 省
<select id="city" onchange="loadChildren(this.value, 'district')"></select> 市
<select id="district"></select> 區/縣
<script>
function loadChildren(parentId, targetSelectId) {
const target = document.getElementById(targetSelectId);
// 清空現有選項
target.innerHTML = '<option value="0">-- 請選擇 --</option>';
// 重置下級選單
if (targetSelectId === 'city') {
document.getElementById('district').innerHTML = '<option value="0">-- 請選擇 --</option>';
}
if (!parentId) return;
// AJAX 請求
const xhr = new XMLHttpRequest();
xhr.open('GET', `ajax-area-server.php?parent_id=${parentId}`, true);
xhr.onload = function() {
if (xhr.status === 200) {
const resp = JSON.parse(xhr.responseText);
if (resp.code === 200) {
resp.data.forEach(item => {
const opt = document.createElement('option');
opt.value = item.id;
opt.textContent = item.name;
target.appendChild(opt);
});
}
}
};
xhr.send();
}
// 頁面載入時初始化省份
window.onload = function() {
loadChildren(0, 'province');
};
</script>
</body></html>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script>
function loadChildren(parentId, targetId) {
const $target = $('#' + targetId);
$target.html('<option value="0">-- 請選擇 --</option>');
if (!parentId) return;
$.getJSON('ajax-area-server.php', {parent_id: parentId}, function(resp) {
if (resp.code === 200) {
$.each(resp.data, function(i, item) {
$target.append(`<option value="${item.id}">${item.name}</option>`);
});
}
});
}
</script>
localStorage 快取xhr.abort()parent_id 欄位必須加索引parent_id 合法性,防止注入Content-Type: application/json此架構前後端分離清晰,易於擴展為 RESTful API、支援多語言、整合 Vue/React 等現代框架。