Procházet zdrojové kódy

✨ feat(areas): 完善区域管理功能

- 后端:允许parentId为null值,增强数据灵活性
- 前端:添加父级区域层级信息查询功能,优化区域选择体验
- 前端:实现区域列表排序功能,默认按ID升序排列
- 前端:优化表单初始化逻辑,根据父级区域自动设置层级关系

♻️ refactor(areas): 重构区域查询参数处理

- 统一使用filters对象处理查询条件,提高代码可读性
- 优化搜索参数构建逻辑,减少冗余代码
- 添加类型定义SearchAreaRequest,增强类型安全
yourname před 3 měsíci
rodič
revize
78e41a2010

+ 1 - 1
packages/server/src/modules/areas/area.schema.ts

@@ -53,7 +53,7 @@ export const updateAreaSchema = z.object({
 // 省市区获取Schema
 export const getAreaSchema = z.object({
   id: z.number().int().positive('ID必须为正整数'),
-  parentId: z.number().int().min(0, '父级ID不能为负数'),
+  parentId: z.number().int().min(0, '父级ID不能为负数').nullable(),
   name: z.string().min(1, '区域名称不能为空').max(100, '区域名称不能超过100个字符'),
   level: z.nativeEnum(AreaLevel, {
     message: '层级必须是1(省/直辖市)、2(市)或3(区/县)'

+ 71 - 4
web/src/client/admin/components/AreaForm.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
 import { useForm } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { Button } from '@/client/components/ui/button';
@@ -10,6 +10,8 @@ import type { CreateAreaInput, UpdateAreaInput } from '@d8d/server/modules/areas
 import { AreaLevel } from '@d8d/server/modules/areas/area.entity';
 import { DisabledStatus } from '@/share/types';
 import { AreaSelect } from './AreaSelect';
+import { useQuery } from '@tanstack/react-query';
+import { areaClient } from '@/client/api';
 
 interface AreaFormProps {
   area?: UpdateAreaInput & { id?: number };
@@ -25,6 +27,54 @@ export const AreaForm: React.FC<AreaFormProps> = ({
   isLoading = false
 }) => {
   const isEditing = !!area;
+  const [parentAreaInfo, setParentAreaInfo] = useState<{
+    provinceId?: number;
+    cityId?: number;
+  }>({});
+
+  // 查询父级区域的完整层级信息
+  const { data: parentAreaData } = useQuery({
+    queryKey: ['area', 'parent', area?.parentId],
+    queryFn: async () => {
+      if (!area?.parentId || area.parentId === 0) return null;
+      const res = await areaClient[':id'].$get({
+        param: { id: area.parentId }
+      });
+      if (res.status !== 200) throw new Error('获取父级区域信息失败');
+      return await res.json();
+    },
+    enabled: isEditing && !!area?.parentId && area.parentId > 0,
+    staleTime: 5 * 60 * 1000,
+    gcTime: 10 * 60 * 1000,
+  });
+
+  // 根据父级区域信息设置层级关系
+  useEffect(() => {
+    if (parentAreaData) {
+      const parentArea = parentAreaData;
+      if (parentArea.level === AreaLevel.PROVINCE) {
+        // 父级是省份,当前区域是城市
+        setParentAreaInfo({
+          provinceId: parentArea.id
+        });
+      } else if (parentArea.level === AreaLevel.CITY) {
+        // 父级是城市,当前区域是区县,需要查询城市的父级省份
+        const fetchProvinceId = async () => {
+          const res = await areaClient[':id'].$get({
+            param: { id: parentArea.parentId! }
+          });
+          if (res.status === 200) {
+            const provinceResult = await res.json();
+            setParentAreaInfo({
+              provinceId: provinceResult.id,
+              cityId: parentArea.id
+            });
+          }
+        };
+        fetchProvinceId();
+      }
+    }
+  }, [parentAreaData]);
 
   const form = useForm<CreateAreaInput | UpdateAreaInput>({
     resolver: zodResolver(isEditing ? updateAreaSchema : createAreaSchema),
@@ -93,6 +143,25 @@ export const AreaForm: React.FC<AreaFormProps> = ({
               const level = form.watch('level');
               const showParentSelect = level !== AreaLevel.PROVINCE;
 
+              // 根据当前层级和编辑状态设置AreaSelect的值
+              const getAreaSelectValue = () => {
+                if (!isEditing || !area) {
+                  return {};
+                }
+
+                if (level === AreaLevel.CITY) {
+                  // 城市级别:需要省份ID
+                  return { provinceId: parentAreaInfo.provinceId };
+                } else if (level === AreaLevel.DISTRICT) {
+                  // 区县级别:需要省份ID和城市ID
+                  return {
+                    provinceId: parentAreaInfo.provinceId,
+                    cityId: parentAreaInfo.cityId
+                  };
+                }
+                return {};
+              };
+
               return (
                 <FormItem>
                   <FormLabel>父级区域</FormLabel>
@@ -100,9 +169,7 @@ export const AreaForm: React.FC<AreaFormProps> = ({
                     <>
                       <FormControl>
                         <AreaSelect
-                          value={{
-                            districtId: level === AreaLevel.DISTRICT ? field.value || undefined : undefined
-                          }}
+                          value={getAreaSelectValue()}
                           onChange={(value) => {
                             // 根据层级设置父级ID
                             if (level === AreaLevel.CITY) {

+ 9 - 3
web/src/client/admin/components/AreaSelect.tsx

@@ -46,7 +46,9 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
           filters: JSON.stringify({
             level: 1,
             isDisabled: 0
-          })
+          }),
+          sortBy: 'id',
+          sortOrder: 'ASC'
         }
       });
       if (res.status !== 200) throw new Error('获取省份列表失败');
@@ -69,7 +71,9 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
             level: 2,
             parentId: selectedProvince,
             isDisabled: 0
-          })
+          }),
+          sortBy: 'id',
+          sortOrder: 'ASC'
         }
       });
       if (res.status !== 200) throw new Error('获取城市列表失败');
@@ -93,7 +97,9 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
             level: 3,
             parentId: selectedCity,
             isDisabled: 0
-          })
+          }),
+          sortBy: 'id',
+          sortOrder: 'ASC'
         }
       });
       if (res.status !== 200) throw new Error('获取区县列表失败');

+ 10 - 4
web/src/client/admin/pages/Areas.tsx

@@ -20,6 +20,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/client/components/ui
 
 // 类型提取规范
 type AreaResponse = InferResponseType<typeof areaClient.$get, 200>['data'][0];
+type SearchAreaRequest = InferRequestType<typeof areaClient.$get>['query'];
 type CreateAreaRequest = InferRequestType<typeof areaClient.$post>['json'];
 type UpdateAreaRequest = InferRequestType<typeof areaClient[':id']['$put']>['json'];
 
@@ -73,13 +74,18 @@ export const AreasPage: React.FC = () => {
   const [selectedArea, setSelectedArea] = useState<AreaResponse | null>(null);
 
   // 构建搜索参数
-  const searchParams = {
+  const filters: Record<string, any> = {};
+  if (level && level !== 'all') filters.level = Number(level);
+  if (parentId) filters.parentId = Number(parentId);
+  if (isDisabled && isDisabled !== 'all') filters.isDisabled = Number(isDisabled);
+
+  const searchParams:SearchAreaRequest = {
     page,
     pageSize,
     keyword: keyword || undefined,
-    level: level && level !== 'all' ? Number(level) : undefined,
-    parentId: parentId ? Number(parentId) : undefined,
-    isDisabled: isDisabled && isDisabled !== 'all' ? Number(isDisabled) : undefined,
+    filters: Object.keys(filters).length > 0 ? JSON.stringify(filters) : undefined,
+    sortBy: 'id',
+    sortOrder: 'ASC'
   };
 
   // 查询省市区列表