فهرست منبع

✨ feat(address): add town field support in address form

- add town field to address schema and form state
- update city selector component to handle town selection
- include town in address data transformation for API requests

🔧 fix(submit): correct form submission handler

- fix onSubmit handler invocation in button click event

🔒 feat(auth): add authentication to area API endpoints

- add auth middleware to provinces, cities and districts routes
- update API tests to include authentication headers
- add 401 response schema for authentication failures

✅ test(areas): enhance API integration tests

- add authentication headers to all API test requests
- expand test entity setup to include required auth-related entities
yourname 1 ماه پیش
والد
کامیت
425a6a8457

+ 7 - 10
mini/src/pages/address-edit/index.tsx

@@ -16,9 +16,6 @@ import { Switch } from '@/components/ui/switch'
 import { useAuth } from '@/utils/auth'
 import { CitySelector } from '@/components/ui/city-selector'
 
-type Address = InferResponseType<typeof deliveryAddressClient[':id']['$get'], 200>
-type CreateAddressRequest = InferRequestType<typeof deliveryAddressClient.$post>['json']
-type UpdateAddressRequest = InferRequestType<typeof deliveryAddressClient[':id']['$put']>['json']
 
 const addressSchema = z.object({
   name: z.string().min(1, '请输入收货人姓名'),
@@ -26,6 +23,7 @@ const addressSchema = z.object({
   province: z.number().positive('请选择省份'),
   city: z.number().positive('请选择城市'),
   district: z.number().positive('请选择区县'),
+  town: z.number().optional(),
   address: z.string().min(1, '请输入详细地址'),
   isDefault: z.boolean().optional()
 })
@@ -70,6 +68,7 @@ export default function AddressEditPage() {
       province: 0,
       city: 0,
       district: 0,
+      town: 0,
       address: '',
       isDefault: false
     }
@@ -84,6 +83,7 @@ export default function AddressEditPage() {
         province: address.receiverProvince,
         city: address.receiverCity,
         district: address.receiverDistrict,
+        town: address.receiverTown || 0,
         address: address.address,
         isDefault: address.isDefault === 1
       })
@@ -99,6 +99,7 @@ export default function AddressEditPage() {
         receiverProvince: data.province,
         receiverCity: data.city,
         receiverDistrict: data.district,
+        receiverTown: data.town || 0,
         address: data.address,
         userId: user?.id,
         isDefault: data.isDefault ? 1 : 0
@@ -135,12 +136,6 @@ export default function AddressEditPage() {
     }
   })
 
-  // 处理地区变化
-  const handleCityChange = (provinceId: number, cityId: number, districtId: number) => {
-    form.setValue('province', provinceId)
-    form.setValue('city', cityId)
-    form.setValue('district', districtId)
-  }
 
   const onSubmit = (data: AddressFormData) => {
     saveAddressMutation.mutate(data)
@@ -192,9 +187,11 @@ export default function AddressEditPage() {
                       provinceValue={form.watch('province')}
                       cityValue={form.watch('city')}
                       districtValue={form.watch('district')}
+                      townValue={form.watch('town')}
                       onProvinceChange={(value) => form.setValue('province', value)}
                       onCityChange={(value) => form.setValue('city', value)}
                       onDistrictChange={(value) => form.setValue('district', value)}
+                      onTownChange={(value) => form.setValue('town', value)}
                       showLabels={false}
                     />
                   </View>
@@ -237,7 +234,7 @@ export default function AddressEditPage() {
 
               <Button
                 className="w-full"
-                onClick={form.handleSubmit(onSubmit)}
+                onClick={() => form.handleSubmit(onSubmit)()}
                 disabled={saveAddressMutation.isPending}
               >
                 {saveAddressMutation.isPending ? '保存中...' : '保存地址'}

+ 15 - 0
packages/geo-areas-mt/src/api/areas/index.mt.ts

@@ -149,6 +149,7 @@ const errorSchema = z.object({
 const getProvincesRoute = createRoute({
   method: 'get',
   path: '/provinces',
+  middleware: [ authMiddleware ],
   request: {
     query: getProvincesSchema
   },
@@ -159,6 +160,10 @@ const getProvincesRoute = createRoute({
         'application/json': { schema: provincesResponseSchema }
       }
     },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: errorSchema } }
+    },
     500: {
       description: '获取省份列表失败',
       content: { 'application/json': { schema: errorSchema } }
@@ -170,6 +175,7 @@ const getProvincesRoute = createRoute({
 const getCitiesRoute = createRoute({
   method: 'get',
   path: '/cities',
+  middleware: [ authMiddleware ],
   request: {
     query: getCitiesSchema
   },
@@ -184,6 +190,10 @@ const getCitiesRoute = createRoute({
       description: '参数错误',
       content: { 'application/json': { schema: errorSchema } }
     },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: errorSchema } }
+    },
     500: {
       description: '获取城市列表失败',
       content: { 'application/json': { schema: errorSchema } }
@@ -195,6 +205,7 @@ const getCitiesRoute = createRoute({
 const getDistrictsRoute = createRoute({
   method: 'get',
   path: '/districts',
+  middleware: [ authMiddleware ],
   request: {
     query: getDistrictsSchema
   },
@@ -209,6 +220,10 @@ const getDistrictsRoute = createRoute({
       description: '参数错误',
       content: { 'application/json': { schema: errorSchema } }
     },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: errorSchema } }
+    },
     500: {
       description: '获取区县列表失败',
       content: { 'application/json': { schema: errorSchema } }

+ 39 - 2
packages/geo-areas-mt/tests/integration/areas.integration.test.ts

@@ -12,7 +12,8 @@ import { TestDataFactory } from '../utils/test-data-factory';
 import { TestQueryFactory } from '../utils/test-query-factory';
 import { AuthService } from '@d8d/auth-module-mt';
 import { UserServiceMt } from '@d8d/user-module-mt';
-import { UserEntityMt } from '@d8d/user-module-mt';
+import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
+import { FileMt } from '@d8d/file-module-mt';
 
 // 定义响应类型
 interface SuccessResponse {
@@ -45,7 +46,7 @@ interface ErrorResponse {
 }
 
 // 设置集成测试钩子
-setupIntegrationDatabaseHooksWithEntities([AreaEntityMt, UserEntityMt])
+setupIntegrationDatabaseHooksWithEntities([AreaEntityMt, UserEntityMt, FileMt, RoleMt])
 
 describe('区域API集成测试', () => {
   let client: ReturnType<typeof testClient<typeof areasRoutesMt>>;
@@ -253,6 +254,10 @@ describe('区域API集成测试', () => {
     it('应该成功获取指定省份下启用状态的城市列表', async () => {
       const response = await client.cities.$get({
         query: TestQueryFactory.createCitiesQuery(testAreas[0].id)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
 
       IntegrationTestAssertions.expectStatus(response, 200);
@@ -284,6 +289,10 @@ describe('区域API集成测试', () => {
     it('应该处理不存在的省份ID', async () => {
       const response = await client.cities.$get({
         query: TestQueryFactory.createCitiesQuery(999)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
 
       IntegrationTestAssertions.expectStatus(response, 200);
@@ -299,6 +308,10 @@ describe('区域API集成测试', () => {
     it('应该验证省份ID参数', async () => {
       const response = await client.cities.$get({
         query: TestQueryFactory.createCitiesQuery(0)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
 
       // 参数验证应该返回400错误
@@ -314,6 +327,10 @@ describe('区域API集成测试', () => {
 
       const response = await client.districts.$get({
         query: TestQueryFactory.createDistrictsQuery(chaoyangCity!.id)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
 
       IntegrationTestAssertions.expectStatus(response, 200);
@@ -344,6 +361,10 @@ describe('区域API集成测试', () => {
     it('应该处理不存在的城市ID', async () => {
       const response = await client.districts.$get({
         query: TestQueryFactory.createDistrictsQuery(999)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
 
       IntegrationTestAssertions.expectStatus(response, 200);
@@ -359,6 +380,10 @@ describe('区域API集成测试', () => {
     it('应该验证城市ID参数', async () => {
       const response = await client.districts.$get({
         query: TestQueryFactory.createDistrictsQuery(0)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
 
       // 参数验证应该返回400错误
@@ -371,6 +396,10 @@ describe('区域API集成测试', () => {
       // 测试省份API
       const provincesResponse = await client.provinces.$get({
         query: TestQueryFactory.createProvincesQuery()
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
       IntegrationTestAssertions.expectStatus(provincesResponse, 200);
       const provincesData = await provincesResponse.json();
@@ -385,6 +414,10 @@ describe('区域API集成测试', () => {
       // 测试城市API
       const citiesResponse = await client.cities.$get({
         query: TestQueryFactory.createCitiesQuery(testAreas[0].id)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
       IntegrationTestAssertions.expectStatus(citiesResponse, 200);
       const citiesData = await citiesResponse.json();
@@ -400,6 +433,10 @@ describe('区域API集成测试', () => {
       const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
       const districtsResponse = await client.districts.$get({
         query: TestQueryFactory.createDistrictsQuery(chaoyangCity!.id)
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
       });
       IntegrationTestAssertions.expectStatus(districtsResponse, 200);
       const districtsData = await districtsResponse.json();