Просмотр исходного кода

✨ feat(areas): 完善区域选择功能,添加街道查询支持

- 重命名City接口为Area,优化parentId类型为number | null
- 修改API调用从cityClient迁移到areaClient,优化查询参数传递方式
- 添加街道(towns)查询接口及相关Schema验证
- 实现getAreasByLevelAndParent方法,优化区域数据查询性能
- 更新区域选择器组件,支持街道级别的选择
- 调整queryKey命名,从cities改为areas,统一命名规范
- 移除冗余的sortOrder和sortBy参数,使用默认排序
- 优化省份、城市、区县数据查询逻辑,提高加载效率
yourname 1 месяц назад
Родитель
Сommit
32b0958632

+ 28 - 35
mini/src/components/ui/city-selector.tsx

@@ -1,14 +1,13 @@
-import React, { useState, useEffect } from 'react'
+import React, { useEffect } from 'react'
 import { View, Picker, Text } from '@tarojs/components'
 import { useQuery } from '@tanstack/react-query'
-import { cityClient } from '@/api'
-import { InferResponseType } from 'hono'
+import { areaClient } from '@/api'
 
-interface City {
+interface Area {
   id: number
   name: string
   level: number
-  parentId: number
+  parentId: number | null
 }
 
 interface CitySelectorProps {
@@ -36,81 +35,75 @@ export const CitySelector: React.FC<CitySelectorProps> = ({
   disabled = false,
   showLabels = true,
 }) => {
-  // 获取省份数据 (level=1)
+  // 获取省份数据
   const { data: provinces } = useQuery({
-    queryKey: ['cities', 'provinces'],
+    queryKey: ['areas', 'provinces'],
     queryFn: async () => {
-      const res = await cityClient.$get({
+      const res = await areaClient.provinces.$get({
         query: {
           page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 1 }),
-          sortOrder: 'ASC',
-          sortBy: 'id'
+          pageSize: 100
         },
       })
       if (res.status !== 200) throw new Error('获取省份数据失败')
       const data = await res.json()
-      return data.data as City[]
+      return data.data.provinces as Area[]
     },
   })
 
-  // 获取城市数据 (level=2)
+  // 获取城市数据
   const { data: cities } = useQuery({
-    queryKey: ['cities', 'cities', provinceValue],
+    queryKey: ['areas', 'cities', provinceValue],
     queryFn: async () => {
       if (!provinceValue) return []
-      const res = await cityClient.$get({
+      const res = await areaClient.cities.$get({
         query: {
+          provinceId: provinceValue,
           page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 2, parentId: provinceValue }),
-          sortOrder: 'ASC',
+          pageSize: 100
         },
       })
       if (res.status !== 200) throw new Error('获取城市数据失败')
       const data = await res.json()
-      return data.data as City[]
+      return data.data.cities as Area[]
     },
     enabled: !!provinceValue,
   })
 
-  // 获取区县数据 (level=3)
+  // 获取区县数据
   const { data: districts } = useQuery({
-    queryKey: ['cities', 'districts', cityValue],
+    queryKey: ['areas', 'districts', cityValue],
     queryFn: async () => {
       if (!cityValue) return []
-      const res = await cityClient.$get({
+      const res = await areaClient.districts.$get({
         query: {
+          cityId: cityValue,
           page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 3, parentId: cityValue }),
-          sortOrder: 'ASC',
+          pageSize: 100
         },
       })
       if (res.status !== 200) throw new Error('获取区县数据失败')
       const data = await res.json()
-      return data.data as City[]
+      return data.data.districts as Area[]
     },
     enabled: !!cityValue,
   })
 
-  // 获取街道数据 (level=4)
+  // 获取街道数据
   const { data: towns } = useQuery({
-    queryKey: ['cities', 'towns', districtValue],
+    queryKey: ['areas', 'towns', districtValue],
     queryFn: async () => {
       if (!districtValue) return []
-      const res = await cityClient.$get({
+      const res = await areaClient.towns.$get({
         query: {
+          districtId: districtValue,
           page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 4, parentId: districtValue }),
-          sortOrder: 'ASC',
+          pageSize: 100
         },
       })
       if (res.status !== 200) throw new Error('获取街道数据失败')
       const data = await res.json()
-      return data.data as City[]
+      return data.data.towns as Area[]
     },
     enabled: !!districtValue,
   })
@@ -205,7 +198,7 @@ export const CitySelector: React.FC<CitySelectorProps> = ({
       {showLabels && (
         <View className="text-sm font-medium text-gray-700">所在地区</View>
       )}
-      
+
       <View className="space-y-3">
         {/* 省份选择器 */}
         <View>

+ 96 - 8
packages/geo-areas/src/api/areas/index.ts

@@ -48,6 +48,22 @@ const getDistrictsSchema = z.object({
   })
 });
 
+// 街道查询参数Schema
+const getTownsSchema = z.object({
+  districtId: z.coerce.number<number>().int().positive('区县ID必须为正整数').openapi({
+    example: 3401,
+    description: '区县ID'
+  }),
+  page: z.coerce.number<number>().int().min(1).default(1).openapi({
+    example: 1,
+    description: '页码'
+  }),
+  pageSize: z.coerce.number<number>().int().min(1).max(100).default(50).openapi({
+    example: 50,
+    description: '每页数量'
+  })
+});
+
 // 省市区响应Schema
 const areaResponseSchema = z.object({
   id: z.number(),
@@ -102,6 +118,21 @@ const districtsResponseSchema = z.object({
   message: z.string()
 });
 
+// 街道列表响应Schema
+const townsResponseSchema = z.object({
+  success: z.boolean(),
+  data: z.object({
+    towns: z.array(areaResponseSchema),
+    pagination: z.object({
+      page: z.number(),
+      pageSize: z.number(),
+      total: z.number(),
+      totalPages: z.number()
+    })
+  }),
+  message: z.string()
+});
+
 // 错误响应Schema
 const errorSchema = z.object({
   code: z.number(),
@@ -183,14 +214,39 @@ const getDistrictsRoute = createRoute({
   }
 });
 
+// 创建街道查询路由
+const getTownsRoute = createRoute({
+  method: 'get',
+  path: '/towns',
+  request: {
+    query: getTownsSchema
+  },
+  responses: {
+    200: {
+      description: '获取街道列表成功',
+      content: {
+        'application/json': { schema: townsResponseSchema }
+      }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: errorSchema } }
+    },
+    500: {
+      description: '获取街道列表失败',
+      content: { 'application/json': { schema: errorSchema } }
+    }
+  }
+});
+
 const app = new OpenAPIHono()
   .openapi(getProvincesRoute, async (c) => {
     try {
       const { page, pageSize } = c.req.valid('query');
       const areaService = new AreaService(AppDataSource);
 
-      // 获取所有省份数据
-      const provinces = await areaService.getAreaTreeByLevel(AreaLevel.PROVINCE);
+      // 使用高效查询方法获取省份数据
+      const provinces = await areaService.getAreasByLevelAndParent(AreaLevel.PROVINCE);
 
       // 分页
       const startIndex = (page - 1) * pageSize;
@@ -223,9 +279,8 @@ const app = new OpenAPIHono()
       const { provinceId, page, pageSize } = c.req.valid('query');
       const areaService = new AreaService(AppDataSource);
 
-      // 获取指定省份下的所有城市
-      const allCities = await areaService.getAreaTreeByLevel(AreaLevel.CITY);
-      const cities = allCities.filter(city => city.parentId === provinceId);
+      // 使用高效查询方法获取指定省份下的城市数据
+      const cities = await areaService.getAreasByLevelAndParent(AreaLevel.CITY, provinceId);
 
       // 分页
       const startIndex = (page - 1) * pageSize;
@@ -258,9 +313,8 @@ const app = new OpenAPIHono()
       const { cityId, page, pageSize } = c.req.valid('query');
       const areaService = new AreaService(AppDataSource);
 
-      // 获取指定城市下的所有区县
-      const allDistricts = await areaService.getAreaTreeByLevel(AreaLevel.DISTRICT);
-      const districts = allDistricts.filter(district => district.parentId === cityId);
+      // 使用高效查询方法获取指定城市下的区县数据
+      const districts = await areaService.getAreasByLevelAndParent(AreaLevel.DISTRICT, cityId);
 
       // 分页
       const startIndex = (page - 1) * pageSize;
@@ -287,6 +341,40 @@ const app = new OpenAPIHono()
         message: error instanceof Error ? error.message : '获取区县列表失败'
       }, 500);
     }
+  })
+  .openapi(getTownsRoute, async (c) => {
+    try {
+      const { districtId, page, pageSize } = c.req.valid('query');
+      const areaService = new AreaService(AppDataSource);
+
+      // 使用高效查询方法获取指定区县下的街道数据
+      const towns = await areaService.getAreasByLevelAndParent(AreaLevel.TOWN, districtId);
+
+      // 分页
+      const startIndex = (page - 1) * pageSize;
+      const endIndex = startIndex + pageSize;
+      const paginatedTowns = towns.slice(startIndex, endIndex);
+
+      return c.json({
+        success: true,
+        data: {
+          towns: paginatedTowns,
+          pagination: {
+            page,
+            pageSize,
+            total: towns.length,
+            totalPages: Math.ceil(towns.length / pageSize)
+          }
+        },
+        message: '获取街道列表成功'
+      }, 200);
+    } catch (error) {
+      console.error('获取街道列表失败:', error);
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '获取街道列表失败'
+      }, 500);
+    }
   });
 
 export const areaRoutes = app;

+ 24 - 0
packages/geo-areas/src/modules/areas/area.service.ts

@@ -160,4 +160,28 @@ export class AreaService {
       }
     });
   }
+
+  /**
+   * 根据层级和父级ID获取区域列表(高效查询)
+   */
+  async getAreasByLevelAndParent(level: AreaLevel, parentId?: number): Promise<AreaEntity[]> {
+    const where: any = {
+      level,
+      isDeleted: 0,
+      isDisabled: DisabledStatus.ENABLED
+    };
+
+    // 如果指定了父级ID,则添加父级条件
+    if (parentId !== undefined) {
+      where.parentId = parentId;
+    }
+
+    return this.areaRepository.find({
+      where,
+      order: {
+        id: 'ASC',
+        name: 'ASC'
+      }
+    });
+  }
 }