:::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/` | `sys:city:detail` | 城市详情 | | GET | `/api/v1/city/child/` | - | 子级城市列表(级联选择器用) | | 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/` | `sys:city:delete` | 删除城市 | ## 数据字典 | 字典编码 | 用途 | 字典项示例 | |:---|:---|:---| | `city_level` | 城市级别名称 | 0=省份, 1=城市, 2=县区, 3=街道, 4=社区 | ## 总结 行政区划模块采用树形结构(`pid` 字段),支持省/市/区/街道四级管理。`before_add` 钩子自动补全 `full_name`(沿 pid 链拼接)和 `pinyin`(由简称生成),`before_delete` 钩子校验子级城市引用。用户表通过 `area_code` 字段关联城市表,前端使用级联选择器。