Files
djangoadmin-wiki/DjangoAdmin_Flask_AntdVue/zh/volume4/business/city.md
T
2026-09-12 23:12:15 +08:00

198 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:::tip 行政区划
行政区划模块管理全国省/市/区/街道四级区域数据,支持树形结构查询、级联选择器、拼音自动生成等功能。用户表通过 `province_code`/`city_code`/`district_code`/`street_code` 四个字段关联城市表的 `area_code`。
:::
## 模块结构
```
src/modules/city/
├── models.py # 城市模型
├── schema.py # 表单验证(WTForms)
├── repository.py # 数据访问层
├── service.py # 业务逻辑层(继承 BaseService)
└── router.py # 路由控制器
```
## 城市模型
```python
# src/modules/city/models.py
class City(BaseModel, BaseDB):
"""
城市区域模型
支持省、市、县区、街道四级区域结构。
"""
__tablename__ = DB_PREFIX + "city"
__table_comment__ = "城市表"
# 城市区号
city_code = db.Column(db.String(6), nullable=False, index=True, comment="城市区号")
# 行政编码(用于用户表关联)
area_code = db.Column(db.String(20), nullable=False, index=True, comment="行政编码")
# 上级行政编码
parent_code = db.Column(db.String(20), nullable=False, index=True, comment="上级行政编码")
# 邮政编码
zip_code = db.Column(db.String(6), nullable=False, comment="邮政编码")
# 城市级别:0-省份 1-城市 2-县区 3-街道 4-社区
level = db.Column(db.Integer, default=0, server_default=text('0'), index=True,
comment="城市级别:0-省份 1-城市 2-县区 3-街道 4-社区")
# 上级城市ID
pid = db.Column(db.Integer, default=0, server_default=text('0'), index=True, comment="上级城市ID")
# 城市名称
name = db.Column(db.String(150), nullable=False, comment="城市名称")
# 城市简称
short_name = db.Column(db.String(150), nullable=False, index=True, comment="城市简称")
# 城市全称(沿 pid 链拼接,如"台湾省,云林,元长,华鹿")
full_name = db.Column(db.String(150), nullable=True, comment="城市全称")
# 城市拼音(由简称自动生成,如"TaiWan")
pinyin = db.Column(db.String(150), nullable=True, comment="城市拼音")
# 经纬度
lng = db.Column(db.String(150), nullable=True, comment="城市经度")
lat = db.Column(db.String(150), nullable=True, comment="城市纬度")
```
## 服务层特色
城市服务继承 `BaseService`,并通过钩子实现自动派生字段:
```python
# src/modules/city/service.py
class CityService(BaseService):
repository = CityRepository
form = CityForm
model_name = "城市"
@classmethod
def before_add(cls, form):
"""新增前钩子:自动补全 full_name / pinyin"""
data = form.data
return {
"full_name": cls._build_full_name(data.get("pid") or 0, data.get("name")),
"pinyin": cls._build_pinyin(data.get("shortName") or data.get("name")),
}
@classmethod
def before_delete(cls, ids):
"""删除前校验:检查是否存在子级城市"""
for cid in ids:
if CityRepository.count_children(cid, parent_field='pid') > 0:
raise BizError(f"城市ID {cid} 存在子级城市,请先删除子级城市")
@classmethod
def _detail_dict(cls, obj):
"""详情序列化:追加 parentName(父级城市名称)"""
data = model_to_dict(obj)
parent_id = getattr(obj, 'pid', None)
if parent_id:
parent = CityRepository.get_by_id(parent_id)
data['parentName'] = parent.name if parent else None
else:
data['parentName'] = None
return data
```
### 自动生成字段
| 字段 | 生成规则 | 示例 |
|:---|:---|:---|
| `full_name` | 沿 pid 链向上拼接,逗号分隔 | `台湾省,云林,元长,华鹿` |
| `pinyin` | 由简称生成无音调拼音,每音节首字母大写 | `TaiWan`、`HuaLu` |
### 删除前校验
城市采用树形结构(`pid` 字段),删除前检查是否存在子级城市:
```
广东省 (pid=0)
├── 深圳市 (pid=广东省.id)
│ ├── 南山区 (pid=深圳市.id)
│ └── 福田区 (pid=深圳市.id)
└── 广州市 (pid=广东省.id)
```
删除「深圳市」前,会检查其下是否存在「南山区」「福田区」等子级城市。
## 树形数据查询
城市模块提供树形数据接口,用于前端级联选择器:
```python
def get_city_tree():
"""获取城市树形数据,构建省-市-区三级树形结构"""
all_cities = CityRepository.list_all()
city_dicts = models_to_list(all_cities)
return _build_city_tree(city_dicts)
def _build_city_tree(city_list, parent_id=0):
"""递归构建城市树形结构"""
tree = []
for city in city_list:
if city.get("pid") == parent_id:
node = {
"id": city.get("id"),
"name": city.get("name"),
"code": city.get("area_code"),
"level": city.get("level"),
"children": _build_city_tree(city_list, city.get("id")),
}
if not node["children"]:
del node["children"]
tree.append(node)
return tree
```
## 与用户模块的关联
用户表通过四个字段关联城市表:
```python
# src/modules/system/user/models.py
class User(BaseModel, BaseDB):
# 省份编码
province_code = db.Column(db.String(30), nullable=False, comment="省份编码")
# 城市编码
city_code = db.Column(db.String(30), nullable=False, comment="城市编码")
# 县区编码
district_code = db.Column(db.String(30), nullable=False, comment="县区编码")
# 街道编码
street_code = db.Column(db.String(30), nullable=False, comment="街道编码")
# 省市区信息(拼接后的可读文本,如"广东省深圳市南山区")
city_info = db.Column(db.String(255), nullable=True, comment="省市区信息")
```
用户新增/更新时,前端通过级联选择器传入 `[省编码, 市编码, 区编码, 街道编码]` 数组,Service 层拆分到独立字段:
```python
# src/modules/system/user/service.py
def _apply_city(create_data, city):
"""将行政区划数组拆分到对应的字段"""
create_data["province_code"] = city[0]
create_data["city_code"] = city[1]
create_data["district_code"] = city[2]
create_data["street_code"] = city[3]
```
## API 接口
| 方法 | 路径 | 权限节点 | 说明 |
|:---|:---|:---|:---|
| GET | `/api/v1/city/list` | `sys:city:list` | 城市列表(支持 pid/name 筛选) |
| GET | `/api/v1/city/detail/<id>` | `sys:city:detail` | 城市详情 |
| GET | `/api/v1/city/child/<code>` | - | 子级城市列表(级联选择器用) |
| GET | `/api/v1/city/tree` | - | 城市树形数据 |
| POST | `/api/v1/city/add` | `sys:city:add` | 新增城市 |
| PUT | `/api/v1/city/update` | `sys:city:update` | 编辑城市 |
| DELETE | `/api/v1/city/delete/<id>` | `sys:city:delete` | 删除城市 |
## 数据字典
| 字典编码 | 用途 | 字典项示例 |
|:---|:---|:---|
| `city_level` | 城市级别名称 | 0=省份, 1=城市, 2=县区, 3=街道, 4=社区 |
## 总结
行政区划模块采用树形结构(`pid` 字段),支持省/市/区/街道四级管理。`before_add` 钩子自动补全 `full_name`(沿 pid 链拼接)和 `pinyin`(由简称生成),`before_delete` 钩子校验子级城市引用。用户表通过 `area_code` 字段关联城市表,前端使用级联选择器。