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

6.7 KiB
Raw Blame History

地区三级联动表实现计划

目标

创建省份/城市/地区三级联动数据库表,在个人中心"所在位置"字段中使用级联选择,替代当前的静态硬编码数据。

当前状态分析

现状

  • 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

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

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

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

添加:

path('regions/', region_views.RegionListView.as_view(), name='region-list'),

7. 前端 API 请求

文件: c:\Users\12914\Desktop\vscode\chunyu_project_react\src\utils\request.ts

添加:

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. 保存时将地区编码数组转换为 "省/市/区" 格式字符串
// 新的地区级联选择器
<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 只返回当前级别数据,前端按需加载子级
  • 存储格式保持兼容:"省/市/区" 字符串