feat: PLANNING体系首版(00路线图/02任务总表/03执行协议/I-03/I-04/registry42口径)+M1止血交付

This commit is contained in:
2026-09-12 14:25:25 +08:00
commit 8242e730b4
482 changed files with 37029 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
# 地区三级联动表实现计划
## 目标
创建省份/城市/地区三级联动数据库表,在个人中心"所在位置"字段中使用级联选择,替代当前的静态硬编码数据。
## 当前状态分析
### 现状
- `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 只返回当前级别数据,前端按需加载子级
- 存储格式保持兼容:`"省/市/区"` 字符串