本文目录导读:

在PHP项目中处理省市区三级联动的后端逻辑,主要涉及数据存储和接口设计,通常前端通过Ajax请求后端,根据上一级ID获取下一级数据。
以下是一个标准、清晰的实现方案:
数据库设计
最推荐使用单表自关联的方式,结构清晰,扩展性好。
SQL 表结构示例 (regions):
CREATE TABLE `regions` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名称', `parent_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父级ID', `level` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '层级:1省 2市 3区', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序', PRIMARY KEY (`id`), KEY `parent_id` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据举例:
| id | name | parent_id | level |
|---|---|---|---|
| 1 | 广东省 | 0 | 1 |
| 2 | 广州市 | 1 | 2 |
| 3 | 天河区 | 2 | 3 |
后端接口设计 (PHP)
通常只需要一个通用接口,根据传入的 parent_id 返回子级列表。
接口路由 (示例)
GET /api/regions?parent_id=0-> 返回所有省份GET /api/regions?parent_id=1-> 返回广东省下的所有城市
PHP 代码实现 (面向过程/基础版)
<?php
// 假设已连接数据库 $db
header('Content-Type: application/json; charset=utf-8');
$parentId = isset($_GET['parent_id']) ? intval($_GET['parent_id']) : 0;
// 1. 查询数据库
$stmt = $db->prepare("SELECT id, name FROM regions WHERE parent_id = ? ORDER BY sort ASC");
$stmt->execute([$parentId]);
$list = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2. 返回JSON数据
echo json_encode([
'code' => 0,
'data' => $list,
'msg' => 'success'
]);
PHP 代码实现 (面向对象/MVC风格)
<?php
namespace App\Controller;
use App\Model\RegionModel;
class RegionController
{
public function getList()
{
$parentId = intval($_GET['parent_id'] ?? 0);
// 调用模型层
$model = new RegionModel();
$list = $model->getByParentId($parentId);
// 返回JSON
return json_encode([
'code' => 200,
'data' => $list
]);
}
}
// RegionModel.php
class RegionModel
{
public function getByParentId($parentId)
{
// 数据查询逻辑...
$sql = "SELECT id, name FROM regions WHERE parent_id = ? ORDER BY sort";
return Db::query($sql, [$parentId]);
}
}
前端联动逻辑 (示例:Ajax + jQuery)
// 1. 加载省份
$.get('/api/regions', { parent_id: 0 }, function(res) {
res.data.forEach(function(item) {
$('#province').append(`<option value="${item.id}">${item.name}</option>`);
});
});
// 2. 省份变化 -> 加载城市
$('#province').change(function() {
var pid = $(this).val();
$('#city').empty().append('<option value="">请选择</option>');
$('#district').empty().append('<option value="">请选择</option>');
if (pid) {
$.get('/api/regions', { parent_id: pid }, function(res) {
res.data.forEach(function(item) {
$('#city').append(`<option value="${item.id}">${item.name}</option>`);
});
});
}
});
// 3. 城市变化 -> 加载区县
$('#city').change(function() {
var cid = $(this).val();
$('#district').empty().append('<option value="">请选择</option>');
if (cid) {
$.get('/api/regions', { parent_id: cid }, function(res) {
res.data.forEach(function(item) {
$('#district').append(`<option value="${item.id}">${item.name}</option>`);
});
});
}
});
性能优化建议
-
加缓存:省市区数据几乎不变化,强烈建议加缓存。
// 使用 Redis 或 Memcached $cacheKey = "region_list_{$parentId}"; $list = $redis->get($cacheKey); if (!$list) { $list = $db->query("SELECT id, name FROM regions WHERE parent_id = ?", [$parentId]); $redis->set($cacheKey, json_encode($list), 86400); // 缓存1天 } -
全量加载 (一次性返回):如果数据量不大(通常全国数据约4000条),可以在页面初始化时一次性加载所有数据,前端JS自行处理筛选,这能减少HTTP请求次数,体验更好。
// 接口返回全量数据 $all = $db->query("SELECT id, name, parent_id FROM regions ORDER BY sort"); echo json_encode($all);前端用JS根据
parent_id构建树形结构或过滤列表。 -
使用 JSON 文件:如果数据量很小且不需要动态管理,可以直接把省市区数据导出为 JSON 文件(如
region.json),前端直接请求静态文件,连数据库和PHP都不需要处理,性能最优。
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 单表自关联 + 按需加载 | 中小型项目,数据可编辑 | 数据结构清晰,易于维护 | 多次请求,延迟较高(无缓存时) |
| 单表自关联 + 全量加载 | 数据量不大,追求用户体验 | 一次请求,前端响应快 | 初始加载稍慢,不适合超大级联 |
| 静态 JSON 文件 | 数据固定,不常修改 | 性能最好,零后端开销 | 扩展性差,动态更新需要重新部署 |
推荐做法:对于大多数CMS、商城项目,直接使用 全量加载 + Redis缓存 或 静态JSON文件 即可,后端压力最小,前端体验最好,如果管理员需要动态编辑省市区(如增加“区县”),则必须使用 单表自关联 + 按需请求(加缓存)。