Files
vscode-workbench/.trae/documents/region-cascader-plan.md
T

205 lines
6.7 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.
# 地区三级联动表实现计划
## 目标
创建省份/城市/地区三级联动数据库表,在个人中心"所在位置"字段中使用级联选择,替代当前的静态硬编码数据。
## 当前状态分析
### 现状
- `UserInfoCard.tsx` 中的 `locationOptions` 是静态硬编码的(只包含北京、上海、广东的部分区县)
- `FUser.location` 字段是 `CharField(max_length=100)`,存储格式为 `"省/市/区"` 字符串
- IP定位API (`GetIPDataView`) 返回 `regionName` 和 `city`,但没有结构化存储
- 无地区数据库表
### 问题
- 静态数据覆盖范围有限
- 无法与IP定位数据联动
- 用户体验差(需要手动输入)
## 实现方案
### 1. 创建 Region 模型
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project\user\models.py`
```python
class Region(models.Model):
"""地区三级联动表 - 省/市/区"""
REGION_LEVEL_CHOICES = [
(1, '省份'),
(2, '城市'),
(3, '区县'),
]
name = models.CharField(max_length=100, verbose_name='地区名称')
code = models.CharField(max_length=20, unique=True, verbose_name='地区编码')
level = models.IntegerField(choices=REGION_LEVEL_CHOICES, verbose_name='级别')
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', verbose_name='上级地区')
pinyin = models.CharField(max_length=200, blank=True, verbose_name='拼音')
sort_order = models.IntegerField(default=0, verbose_name='排序')
class Meta:
verbose_name = '地区'
verbose_name_plural = '地区'
ordering = ['level', 'sort_order', 'code']
indexes = [
models.Index(fields=['parent', 'level']),
models.Index(fields=['code']),
]
def __str__(self):
return self.name
```
### 2. 创建数据迁移文件
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project\user\migrations\0010_region.py`
迁移文件将:
- 创建 Region 表
- 包含初始数据种子命令的引用
### 3. 创建数据种子命令
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project\user\management\commands\seed_regions.py`
功能:
- 从JSON文件或内置数据导入全国省市区数据
- 支持增量更新(已存在则跳过)
- 数据来源:国家统计局行政区划代码
### 4. 创建 API 序列化器
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project\user\serializers\region_serializers.py`
```python
class RegionSerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = Region
fields = ['id', 'name', 'code', 'level', 'parent_id', 'children']
def get_children(self, obj):
children = obj.children.all()
if children.exists():
return RegionSerializer(children, many=True).data
return None
```
### 5. 创建 API 视图
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project\user\views\region.py`
```python
class RegionListView(APIView):
"""获取地区级联数据"""
permission_classes = [AllowAny]
def get(self, request):
# 只返回省级数据,前端按需加载子级
level = request.GET.get('level', 1)
parent_code = request.GET.get('parent_code')
if parent_code:
regions = Region.objects.filter(parent__code=parent_code)
else:
regions = Region.objects.filter(level=level)
serializer = RegionSerializer(regions, many=True)
return Response({'code': 200, 'data': serializer.data})
```
### 6. 注册路由
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project\user\urls.py`
添加:
```python
path('regions/', region_views.RegionListView.as_view(), name='region-list'),
```
### 7. 前端 API 请求
**文件**: `c:\Users\12914\Desktop\vscode\chunyu_project_react\src\utils\request.ts`
添加:
```typescript
const region = {
getList: (params?: { level?: number; parent_code?: string }) =>
request.public.get('/user/regions/', params),
};
```
### 8. 前端组件改造
**文件**: `C:\Users\12914\Desktop\vscode\chunyu_project_react\src\pages\Profile\components\UserInfoCard.tsx`
修改内容:
1. 移除静态 `locationOptions`
2. 添加 `regionOptions` 状态,从API获取省级数据
3. 改造 `Cascader` 组件为动态加载模式
4. 使用 `loadData` 属性实现按需加载子级数据
5. 保存时将地区编码数组转换为 `"省/市/区"` 格式字符串
```typescript
// 新的地区级联选择器
<Cascader
options={regionOptions}
loadData={async (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const children = await region.getList({ parent_code: targetOption.code });
targetOption.children = children;
setRegionOptions([...regionOptions]);
}}
value={location}
onChange={(v) => setLocation(v as string[])}
placeholder="请选择地区"
expandTrigger="hover"
displayRender={(labels) => labels.join(" / ")}
style={{ width: "100%" }}
/>
```
### 9. IP定位联动(可选)
登录时自动填充位置:
- 在 `_create_login_record` 中调用IP API
- 将 `regionName` 和 `city` 存入 `LoginRecord.location`
- 尝试匹配 Region 表中的地区编码
## 文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `chunyu_project/user/models.py` | 修改 | 添加 Region 模型 |
| `chunyu_project/user/migrations/0010_region.py` | 新建 | 创建 Region 表迁移 |
| `chunyu_project/user/management/commands/seed_regions.py` | 新建 | 数据种子命令 |
| `chunyu_project/user/serializers/region_serializers.py` | 新建 | 地区序列化器 |
| `chunyu_project/user/views/region.py` | 新建 | 地区API视图 |
| `chunyu_project/user/urls.py` | 修改 | 注册地区路由 |
| `chunyu_project_react/src/utils/request.ts` | 修改 | 添加 region API |
| `chunyu_project_react/src/pages/Profile/components/UserInfoCard.tsx` | 修改 | 使用动态地区数据 |
## 验证步骤
1. 运行 `python manage.py makemigrations` 生成迁移
2. 运行 `python manage.py migrate` 执行迁移
3. 运行 `python manage.py seed_regions` 导入初始数据
4. 访问 `/api/user/regions/` 验证API返回数据
5. 打开个人中心,验证地区级联选择器正常工作
6. 保存位置后验证数据库存储格式正确
## 假设与决策
### 假设
- 前端使用 Ant Design Cascader 组件的 `loadData` 属性实现懒加载
- 初始数据来源于国家统计局行政区划代码
- Region 表数据量约为 3000+ 条(省34个、市300+、区县2800+)
### 决策
- 使用 `parent` 自关联外键实现树形结构
- API 只返回当前级别数据,前端按需加载子级
- 存储格式保持兼容:`"省/市/区"` 字符串