二級聯動選單是表單設計常見需求:選擇「省份」後,自動載入對應的「城市」選項。本文示範使用原生 JavaScript (XMLHttpRequest) 搭配 PHP 後端,實現無刷新動態載入城市資料。
包含兩個 <select> 元素:省份下拉選單綁定 onchange="sendrequest()",城市選單初始為空 <div id="city">,等待 AJAX 回填 <option> 選項。
<select id="province" onchange="sendrequest()"> <option value="">請選擇省份</option> <option value="gd">廣東</option> <option value="bj">北京</option> <!-- 更多省份 --> </select> <div id="city"></div> <!-- 這裡將被注入 <select> 城市選單 -->
getxmlhttprequest():跨瀏覽器建立 XMLHttpRequest 物件 (現代瀏覽器 / IE ActiveX)sendrequest():取得選中的省份值,構建 URL (city.php?prov=gd),發送 GET 請求updatepage():回調函式,檢查 readyState===4 && status===200,將 responseText 寫入 city 容器<script> var xmlhttp = null; function getxmlhttprequest() { var xmlhttp = null; try { xmlhttp = new XMLHttpRequest(); } catch(e) { try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } } return xmlhttp; } function sendrequest() { var prov = document.getElementById("province").value; if (!prov) return; xmlhttp = getxmlhttprequest(); if (!xmlhttp) { alert("瀏覽器不支援 XMLHttpRequest"); return; } var url = "city.php?prov=" + encodeURIComponent(prov); xmlhttp.open("GET", url, true); xmlhttp.onreadystatechange = updatepage; xmlhttp.send(null); } function updatepage() { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { document.getElementById("city").innerHTML = xmlhttp.responseText; } } </script>
定義省份-城市對應陣列,接收 $_GET['prov'],輸出城市 <select> 選項。注意編碼轉換:頁面為 GB2312,PHP 輸出需 iconv('gb2312','utf-8',...) 或統一改用 UTF-8。
<?php $city_arr = array( "gd" => "廣州,深圳,珠海,佛山", "bj" => "北京", "sh" => "上海", // 更多省份... ); if (empty($_GET['prov'])) { echo '<font color="red">請先選擇省份</font>'; } else { $prov = $_GET['prov']; $cities = explode(',', $city_arr[$prov] ?? ''); echo '<select name="city">'; echo '<option value="">請選擇城市</option>'; foreach ($cities as $city) { echo "<option value=\"$city\">$city</option>"; } echo '</select>'; } ?>
實務專案中城市資料通常存於資料表:
CREATE TABLE `areas` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(50) NOT NULL, `parent_id` INT DEFAULT 0, `level` TINYINT DEFAULT 1 -- 1=省, 2=市, 3=區 ); -- 查詢城市 $stmt = $pdo->prepare("SELECT name FROM areas WHERE parent_id = (SELECT id FROM areas WHERE name = ? AND level = 1) AND level = 2"); $stmt->execute([$_GET['prov']]); $cities = $stmt->fetchAll(PDO::FETCH_COLUMN);
XMLHttpRequest,支援 Promise 鏈式寫法json_encode($cities),前端動態建立 <option>,分離資料與表現setTimeout 避免頻繁請求Map 快取已載入省份的城市資料,避免重複請求<select> 保留原生鍵盤操作,ARIA 標籤完善// 現代化 Fetch + JSON 版本 async function loadCities(provinceCode) { const res = await fetch(`api/cities.php?prov=${encodeURIComponent(provinceCode)}`); const cities = await res.json(); const select = document.getElementById('city-select'); select.innerHTML = '<option value="">請選擇城市</option>'; cities.forEach(city => { const opt = document.createElement('option'); opt.value = city; opt.textContent = city; select.appendChild(opt); }); }
Ajax 二級聯動選單展示了前後端非同步協作的經典模式。從原生 XMLHttpRequest 進化到 fetch/async-await、從字串拼接 HTML 到 JSON 資料驅動、從硬編碼陣列到資料庫動態查詢,每一步都提升了可維護性與使用者體驗。