Forráskód Böngészése

✨ feat(location): 完成省市区管理页面及相关功能

- 标记省市区管理页面所有任务为已完成
- 实现省市区管理页面的搜索和筛选功能
- 创建省市区创建和编辑表单
- 支持省市区层级展示和树形结构
- 实现省市区三级联动选择组件并更新地点表单

✨ feat(route): 更新路线管理功能

- 修改路线列表展示,显示地点名称而非ID
- 更新路线编辑表单,使用地点选择组件
- 调整路线数据模型,使用locationId替代直接输入
- 优化出发时间处理,使用Date对象

✅ test(activity): 更新活动测试用例

- 为活动测试添加venueLocationId字段
- 更新测试数据工厂,添加默认举办地点ID
yourname 4 hónapja
szülő
commit
1b839fd34b

+ 8 - 8
docs/stories/005.001.story.md

@@ -83,19 +83,19 @@ Approve
   - [x] 返回包含关联活动信息的路线列表
   - [x] 实现去重后的活动列表展示
 
-- [ ] 实现省市区管理页面 (AC: 5)
-  - [ ] 创建省市区管理页面 - 省市区数据配置和管理
-  - [ ] 实现省市区管理页面的搜索和筛选功能
-  - [ ] 实现省市区创建和编辑表单
-  - [ ] 支持省市区层级展示和树形结构
-  - [ ] 实现省市区三级联动选择组件
-  - [ ] 更新地点表单,支持省市区三级联动选择组件
+- [x] 实现省市区管理页面 (AC: 5)
+  - [x] 创建省市区管理页面 - 省市区数据配置和管理
+  - [x] 实现省市区管理页面的搜索和筛选功能
+  - [x] 实现省市区创建和编辑表单
+  - [x] 支持省市区层级展示和树形结构
+  - [x] 实现省市区三级联动选择组件
+  - [x] 更新地点表单,支持省市区三级联动选择组件
 
 - [x] 实现地点管理页面 (AC: 5)
   - [x] 创建地点管理页面 - 地点信息配置和管理
   - [x] 实现地点管理页面的搜索和筛选功能
   - [x] 实现地点创建和编辑表单
-  - [ ] 更新活动和路线表单,支持地点选择组件
+  - [x] 更新活动和路线表单,支持地点选择组件
   - [x] 支持按省市区筛选地点列表
 
 - [ ] 编写地点管理测试 (AC: 5, 6, 7, 8)

+ 1 - 1
src/client/admin/components/ActivityForm.tsx

@@ -6,7 +6,7 @@ import { Input } from '@/client/components/ui/input';
 import { Textarea } from '@/client/components/ui/textarea';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
 import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Calendar, MapPin } from 'lucide-react';
+import { Calendar } from 'lucide-react';
 import { format } from 'date-fns';
 import { createActivitySchema, updateActivitySchema } from '@/server/modules/activities/activity.schema';
 import type { CreateActivityInput, UpdateActivityInput } from '@/server/modules/activities/activity.schema';

+ 5 - 5
src/client/admin/pages/Routes.tsx

@@ -323,8 +323,8 @@ export const RoutesPage: React.FC = () => {
                           <span>{route.name}</span>
                         </div>
                       </TableCell>
-                      <TableCell>{route.startPoint}</TableCell>
-                      <TableCell>{route.endPoint}</TableCell>
+                      <TableCell>{route.startLocation?.name}</TableCell>
+                      <TableCell>{route.endLocation?.name}</TableCell>
                       <TableCell>
                         <span className="px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800">
                           {route.vehicleType}
@@ -441,11 +441,11 @@ export const RoutesPage: React.FC = () => {
               id: editingRoute.id,
               name: editingRoute.name,
               description: editingRoute.description,
-              startPoint: editingRoute.startPoint,
-              endPoint: editingRoute.endPoint,
+              startLocationId: editingRoute.startLocationId,
+              endLocationId: editingRoute.endLocationId,
               pickupPoint: editingRoute.pickupPoint,
               dropoffPoint: editingRoute.dropoffPoint,
-              departureTime: editingRoute.departureTime,
+              departureTime: new Date(editingRoute.departureTime),
               vehicleType: editingRoute.vehicleType,
               price: editingRoute.price,
               seatCount: editingRoute.seatCount,

+ 1 - 1
src/server/modules/locations/location.entity.ts

@@ -1,4 +1,4 @@
-import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
 import { AreaEntity } from '@/server/modules/areas/area.entity';
 import { DeleteStatus, DisabledStatus } from '@/share/types';
 

+ 1 - 1
src/server/modules/routes/route.schema.ts

@@ -41,7 +41,7 @@ export const updateRouteSchema = z.object({
   endLocationId: z.number().int().positive('目的地点ID必须为正整数').optional(),
   pickupPoint: z.string().min(1, '上车点不能为空').max(255, '上车点不能超过255个字符').optional(),
   dropoffPoint: z.string().min(1, '下车点不能为空').max(255, '下车点不能超过255个字符').optional(),
-  departureTime: z.coerce.date(),
+  departureTime: z.coerce.date().optional(),
   vehicleType: z.enum([VehicleType.BUS, VehicleType.MINIBUS, VehicleType.CAR], {
     message: '车型必须是有效的类型(bus/minibus/car)'
   }).optional(),

+ 6 - 3
tests/integration/server/admin/activities.integration.test.ts

@@ -42,7 +42,8 @@ describe('活动管理API集成测试', () => {
         description: '这是一个测试去程活动',
         type: ActivityType.DEPARTURE,
         startDate: '2025-10-17T08:00:00.000Z',
-        endDate: '2025-10-17T18:00:00.000Z'
+        endDate: '2025-10-17T18:00:00.000Z',
+        venueLocationId: 1
       };
 
       const response = await client.activities.$post({
@@ -74,7 +75,8 @@ describe('活动管理API集成测试', () => {
         description: '这是一个测试返程活动',
         type: ActivityType.RETURN,
         startDate: '2025-10-17T16:00:00.000Z',
-        endDate: '2025-10-17T20:00:00.000Z'
+        endDate: '2025-10-17T20:00:00.000Z',
+        venueLocationId: 1
       };
 
       const response = await client.activities.$post({
@@ -99,7 +101,8 @@ describe('活动管理API集成测试', () => {
         description: '这是一个测试活动',
         type: 'invalid_type' as any, // 无效类型
         startDate: '2025-10-17T08:00:00.000Z',
-        endDate: '2025-10-17T18:00:00.000Z'
+        endDate: '2025-10-17T18:00:00.000Z',
+        venueLocationId: 1
       };
 
       const response = await client.activities.$post({

+ 1 - 0
tests/utils/server/integration-test-db.ts

@@ -101,6 +101,7 @@ export class TestDataFactory {
       type: ActivityType.DEPARTURE,
       startDate: now,
       endDate: tomorrow,
+      venueLocationId: 1, // 默认举办地点ID
       isDisabled: 0,
       isDeleted: 0,
       ...overrides