# 地区三级联动表实现计划 ## 目标 创建省份/城市/地区三级联动数据库表,在个人中心"所在位置"字段中使用级联选择,替代当前的静态硬编码数据。 ## 当前状态分析 ### 现状 - `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 // 新的地区级联选择器 { 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 只返回当前级别数据,前端按需加载子级 - 存储格式保持兼容:`"省/市/区"` 字符串