244 lines
7.7 KiB
Markdown
244 lines
7.7 KiB
Markdown
:::tip 城市管理
|
||
城市管理模块用于管理行政区划数据,支持省/市/区/街道/社区五级行政区划。通过 `parent_code` 字段实现树形结构。
|
||
:::
|
||
|
||
## 城市模型
|
||
|
||
```python
|
||
# application/city/models.py
|
||
from application.models import BaseModel
|
||
|
||
class City(BaseModel):
|
||
"""城市/行政区划模型类"""
|
||
class Meta:
|
||
db_table = 'django_city'
|
||
|
||
city_code = models.CharField(max_length=6, verbose_name='城市区号')
|
||
area_code = models.CharField(max_length=20, verbose_name='行政编码')
|
||
parent_code = models.CharField(max_length=20, null=True, blank=True, verbose_name='上级行政编码')
|
||
zip_code = models.CharField(max_length=6, verbose_name='邮政编码')
|
||
level = models.IntegerField(default=0, verbose_name='城市级别:0-省份 1-城市 2-县区 3-街道 4-社区')
|
||
pid = models.IntegerField(default=0, verbose_name='上级城市ID')
|
||
name = models.CharField(max_length=150, verbose_name='城市名称')
|
||
short_name = models.CharField(max_length=150, verbose_name='城市简称')
|
||
full_name = models.CharField(max_length=150, null=True, blank=True, verbose_name='城市全称')
|
||
pinyin = models.CharField(max_length=150, null=True, blank=True, verbose_name='城市拼音')
|
||
lng = models.CharField(max_length=150, null=True, blank=True, verbose_name='城市经度')
|
||
lat = models.CharField(max_length=150, null=True, blank=True, verbose_name='城市纬度')
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `city_code` | 城市区号 | `0755` |
|
||
| `area_code` | 行政编码(唯一标识) | `440305` |
|
||
| `parent_code` | 上级行政编码 | `440300`(深圳市) |
|
||
| `zip_code` | 邮政编码 | `518000` |
|
||
| `level` | 层级:0-省 1-市 2-区 3-街道 4-社区 | `2` |
|
||
| `pid` | 上级城市 ID(关联本表 id) | `123` |
|
||
| `name` | 城市名称 | `南山区` |
|
||
| `short_name` | 城市简称 | `南山` |
|
||
| `full_name` | 城市全称 | `广东省深圳市南山区` |
|
||
| `pinyin` | 城市拼音 | `nanshanqu` |
|
||
| `lng` | 经度 | `113.9304` |
|
||
| `lat` | 纬度 | `22.5333` |
|
||
|
||
## 树形结构
|
||
|
||
城市通过 `parent_code` 和 `pid` 两个字段形成五级行政区划树形结构:
|
||
|
||
```
|
||
中国 (parent_code=null, level=0)
|
||
+-- 广东省 (parent_code=100000, level=0)
|
||
| +-- 深圳市 (parent_code=440000, level=1)
|
||
| | +-- 南山区 (parent_code=440300, level=2)
|
||
| | | +-- 粤海街道 (parent_code=440305, level=3)
|
||
| | +-- 福田区 (parent_code=440300, level=2)
|
||
| +-- 广州市 (parent_code=440000, level=1)
|
||
+-- 浙江省 (parent_code=100000, level=0)
|
||
+-- 杭州市 (parent_code=330000, level=1)
|
||
```
|
||
|
||
:::tip 行政区划与用户关联
|
||
用户表通过 `province_code`、`city_code`、`district_code`、`street_code` 四个字段关联城市表的 `area_code`,`city_info` 字段存储拼接后的城市名称(如"广东省深圳市南山区")。
|
||
:::
|
||
|
||
## API 接口
|
||
|
||
| 接口 | 方法 | 权限节点 | 说明 |
|
||
|------|------|---------|------|
|
||
| `/city/list` | GET | `sys:city:list` | 城市列表(支持按 pid 和 name 筛选) |
|
||
| `/city/detail/<int:city_id>` | GET | `sys:city:detail` | 城市详情 |
|
||
| `/city/add` | POST | `sys:city:add` | 新增城市 |
|
||
| `/city/update` | POST | `sys:city:update` | 编辑城市 |
|
||
| `/city/delete/<int:city_ids>` | POST | `sys:city:delete` | 删除城市(支持批量) |
|
||
|
||
## 列表查询
|
||
|
||
```
|
||
GET /city/list?pid=0&name=深圳
|
||
```
|
||
|
||
### 查询参数
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `pid` | int | 上级城市 ID(默认 0,查顶级) |
|
||
| `name` | str | 城市名称(模糊查询,不区分大小写) |
|
||
|
||
### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"cityCode": "0755",
|
||
"areaCode": "440305",
|
||
"parentCode": "440300",
|
||
"zipCode": "518057",
|
||
"level": 2,
|
||
"pid": 123,
|
||
"name": "南山区",
|
||
"shortName": "南山",
|
||
"fullName": "广东省深圳市南山区",
|
||
"pinyin": "nanshanqu",
|
||
"lng": "113.9304",
|
||
"lat": "22.5333"
|
||
}
|
||
],
|
||
"msg": "操作成功",
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
## 子级城市查询
|
||
|
||
根据上级城市编码获取子级城市列表,用于前端级联选择器:
|
||
|
||
```python
|
||
# application/city/services.py
|
||
def get_child_cities(city_code):
|
||
"""根据上级城市编码获取子级城市列表"""
|
||
child_list = City.objects.filter(
|
||
is_delete=False,
|
||
parent_code=city_code
|
||
).values()
|
||
return list(child_list)
|
||
```
|
||
|
||
## 添加城市
|
||
|
||
```python
|
||
# application/city/services.py
|
||
def add_city(request):
|
||
"""添加城市"""
|
||
data, error = parse_request_body(request)
|
||
if error:
|
||
return R.failed(msg=error)
|
||
|
||
# 表单验证
|
||
form = forms.CityForm(data)
|
||
if not form.is_valid():
|
||
return R.failed(msg=regular.get_err(form))
|
||
|
||
cleaned_data = form.cleaned_data
|
||
City.objects.create(
|
||
city_code=cleaned_data.get('cityCode'),
|
||
area_code=cleaned_data.get('areaCode'),
|
||
parent_code=cleaned_data.get('parentCode'),
|
||
zip_code=cleaned_data.get('zipCode'),
|
||
level=int(cleaned_data.get('level')),
|
||
pid=int(cleaned_data.get('pid')),
|
||
name=cleaned_data.get('name'),
|
||
short_name=cleaned_data.get('shortName'),
|
||
lng=cleaned_data.get('lng'),
|
||
lat=cleaned_data.get('lat'),
|
||
create_user=get_username(request),
|
||
update_user=get_username(request)
|
||
)
|
||
return R.ok(msg="创建成功")
|
||
```
|
||
|
||
## 删除城市(带子级校验)
|
||
|
||
删除城市前会检查是否存在子级城市,如果有则阻止删除:
|
||
|
||
```python
|
||
def delete_cities(city_ids):
|
||
"""删除城市(批量删除)"""
|
||
id_list = [int(id_str.strip()) for id_str in city_ids.split(',') if id_str.strip()]
|
||
|
||
# 检查是否有子级城市
|
||
has_children = City.objects.filter(pid__in=id_list, is_delete=False).exists()
|
||
if has_children:
|
||
return R.failed("请先删除子级城市")
|
||
|
||
# 批量逻辑删除
|
||
updated_count = City.objects.filter(
|
||
id__in=id_list, is_delete=False
|
||
).update(is_delete=True)
|
||
|
||
return R.ok(msg=f"本次共删除{updated_count}条数据")
|
||
```
|
||
|
||
:::warning 删除规则
|
||
城市采用**自下而上**的删除策略:必须先删除最底层的社区/街道,再删除区县,最后删除城市和省份。这保证了数据的完整性。
|
||
:::
|
||
|
||
## 前端级联选择
|
||
|
||
前端使用 `el-cascader` 组件实现省市区三级联动选择:
|
||
|
||
```vue
|
||
<el-cascader
|
||
v-model="selectedRegion"
|
||
:props="cascaderProps"
|
||
placeholder="请选择省/市/区"
|
||
/>
|
||
|
||
<script setup>
|
||
const cascaderProps = {
|
||
lazy: true,
|
||
lazyLoad: async (node, resolve) => {
|
||
const { level, value } = node;
|
||
const res = await getCityList({ pid: value || 0 });
|
||
const nodes = res.map(item => ({
|
||
value: item.areaCode,
|
||
label: item.name,
|
||
leaf: level >= 2 // 区县级为叶子节点
|
||
}));
|
||
resolve(nodes);
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
## 数据导入
|
||
|
||
行政区划数据通常通过 SQL 文件批量导入,而非手动添加。项目内置了全国行政区划数据:
|
||
|
||
```bash
|
||
# 导入行政区划数据(首次部署时执行)
|
||
python manage.py migrate
|
||
```
|
||
|
||
:::tip 数据来源
|
||
行政区划数据来源于国家统计局公开数据,覆盖全国省/市/区/街道/社区五级行政区划。数据量约 70 万条记录。
|
||
:::
|
||
|
||
## 总结
|
||
|
||
城市管理模块具备以下特点:
|
||
|
||
```
|
||
1. 五级行政区划:省/市/区/街道/社区,通过 parent_code 形成树形结构
|
||
2. 丰富字段:含经纬度、拼音、邮编等信息
|
||
3. 用户关联:用户表通过四级编码关联城市表
|
||
4. 级联选择:前端通过 el-cascader 组件实现级联选择
|
||
5. 删除校验:删除前检查子级,保证数据完整性
|
||
6. 批量导入:内置全国行政区划数据
|
||
```
|