Browse Source

✨ feat(location): 增强地点选择组件功能

- 添加搜索功能,支持按关键词筛选地点
- 重构地区数据结构,将area拆分为province、city和district三级结构
- 优化地区名称显示,使用完整的省/市/区层级名称
- 改进数据获取逻辑,通过API直接过滤而非前端过滤
- 添加错误处理,确保API调用失败时能正确抛出异常

♻️ refactor(location): 优化组件代码结构

- 移除未使用的useEffect导入
- 简化location数据处理逻辑
- 提取getAreaName函数统一处理地区名称格式化
- 优化查询键设计,包含搜索关键词以确保缓存正确更新
- 清理不必要的前端过滤逻辑
yourname 4 months ago
parent
commit
987bc99559
1 changed files with 45 additions and 25 deletions
  1. 45 25
      src/client/admin/components/LocationSelect.tsx

+ 45 - 25
src/client/admin/components/LocationSelect.tsx

@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState } from 'react';
 import { useQuery } from '@tanstack/react-query';
 import { Check, ChevronsUpDown, MapPin } from 'lucide-react';
 import { cn } from '@/client/lib/utils';
@@ -11,13 +11,26 @@ interface Location {
   id: number;
   name: string;
   address: string;
-  area: {
+  province: {
     id: number;
     name: string;
+    level: number;
     code: string;
-  };
-  latitude?: number;
-  longitude?: number;
+  } | null;
+  city: {
+    id: number;
+    name: string;
+    level: number;
+    code: string;
+  } | null;
+  district: {
+    id: number;
+    name: string;
+    level: number;
+    code: string;
+  } | null;
+  latitude?: number | null;
+  longitude?: number | null;
 }
 
 interface LocationSelectProps {
@@ -42,25 +55,23 @@ export const LocationSelect = ({
   const [open, setOpen] = useState(false);
   const [search, setSearch] = useState('');
 
-  // 获取地点列表
+  // 获取地点列表,支持搜索
   const { data: locationsData, isLoading } = useQuery({
-    queryKey: ['locations', 'select'],
-    queryFn: () => locationClient.$get({
-      query: {
-        pageSize: 100,
-        isDisabled: 0 // 只获取启用的地点
-      }
-    }),
+    queryKey: ['locations', 'select', search],
+    queryFn: async () => {
+      const res = await locationClient.$get({
+        query: {
+          pageSize: 100,
+          keyword: search || undefined,
+          filters: JSON.stringify({ isDisabled: 0 }) // 只获取启用的地点
+        }
+      });
+      if (res.status !== 200) throw new Error('获取地点列表失败');
+      return await res.json();
+    },
   });
 
-  const locations = locationsData?.data?.data || [];
-
-  // 根据搜索关键词过滤地点
-  const filteredLocations = locations.filter((location: Location) =>
-    location.name.toLowerCase().includes(search.toLowerCase()) ||
-    location.address.toLowerCase().includes(search.toLowerCase()) ||
-    location.area.name.toLowerCase().includes(search.toLowerCase())
-  );
+  const locations = locationsData?.data || [];
 
   // 获取选中的地点
   const selectedLocation = locations.find((location: Location) => location.id === value);
@@ -77,6 +88,15 @@ export const LocationSelect = ({
     setSearch('');
   };
 
+  // 获取地点完整的区域名称
+  const getAreaName = (location: Location) => {
+    const parts = [];
+    if (location.province?.name) parts.push(location.province.name);
+    if (location.city?.name) parts.push(location.city.name);
+    if (location.district?.name) parts.push(location.district.name);
+    return parts.join(' / ');
+  };
+
   return (
     <Popover open={open} onOpenChange={setOpen}>
       <PopoverTrigger asChild>
@@ -98,7 +118,7 @@ export const LocationSelect = ({
                 {selectedLocation.name}
               </span>
               <span className="text-xs text-muted-foreground truncate">
-                ({selectedLocation.area.name})
+                ({getAreaName(selectedLocation)})
               </span>
             </div>
           ) : (
@@ -119,7 +139,7 @@ export const LocationSelect = ({
               {isLoading ? "加载中..." : "未找到匹配的地点"}
             </CommandEmpty>
             <CommandGroup>
-              {filteredLocations.map((location: Location) => (
+              {locations.map((location: Location) => (
                 <CommandItem
                   key={location.id}
                   value={location.id.toString()}
@@ -135,7 +155,7 @@ export const LocationSelect = ({
                     />
                     <span className="font-medium">{location.name}</span>
                     <span className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded">
-                      {location.area.name}
+                      {getAreaName(location)}
                     </span>
                   </div>
                   <div className="text-xs text-muted-foreground mt-1 ml-6">
@@ -144,7 +164,7 @@ export const LocationSelect = ({
                 </CommandItem>
               ))}
             </CommandGroup>
-            {search && filteredLocations.length > 0 && (
+            {search && locations.length > 0 && (
               <CommandGroup>
                 <CommandItem
                   value=""