## Features ### Admin Schedule Page (`packages/app/src/pages/admin/schedule.vue`) - Interactive date-based slot editor for managing daily schedules - Real-time slot editing: start/end times, capacity adjustments - Slot deletion with conflict warnings when bookings exist - Add new slots with modal dialog - Live booking status display (booked count, people names) - Publish/Save changes with sync feedback - Revert unsaved changes with confirmation - Skeleton loading states and empty state handling - Responsive design with optimized mobile UX ### Backend Enhancements - **New DTO** (`PublishDaySlotsDto`): Structured slot publishing with validation - Date string validation - Slot array with existing slot IDs for updates - Time and capacity validation per slot - **Schedule Preview API** (`getSchedulePreview`): - Check for existing published slots - Fallback to active WeekTemplates for unpublished dates - Unified response format with isPublished flag - **Publish Slots API** (`publishDaySlots`): - Atomic transaction for consistency - Update existing slots with new times/capacity - Create new slots from template data - Delete unpublished slots or set to CLOSED if bookings exist - Prevent capacity reduction below existing bookings - Returns all published slots for feedback ### State Management - Enhanced admin store with schedule state - Support for pending/unsaved slot changes - Optimistic UI updates with server sync ### Documentation - Comprehensive scheduling system architecture docs - Quick reference for admin workflows - Flow diagrams and state transitions - Implementation guide for future maintenance ## Breaking Changes None ## Testing Recommendations - Create slots for future dates via schedule editor - Verify booking prevention for locked/full slots - Test capacity adjustments with existing bookings - Confirm template-based schedule generation - Verify transaction rollback on publish failures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 KiB
13 KiB
Admin Scheduling Flow Diagram
Component Architecture
┌─────────────────────────────────────────────────────────┐
│ Admin Dashboard │
│ (pages/admin/index.vue) │
│ │
│ 📅 排课设置 🔧 临时调整 👥 会员 📋 订单 💳 卡 🏢 工作室
└─────────────────────────────────────────────────────────┘
│
└─► 📅 排课设置 (Week Template)
└─────────────────────────────────────────┐
│ pages/admin/week-template.vue │
│ ================================ │
│ │
│ 1. Fetch Templates (onMounted) │
│ └─ GET /admin/week-template │
│ │
│ 2. Display grouped by day (Mon-Sun) │
│ │
│ 3. Add/Edit/Delete/Toggle locally │
│ └─ isDirty flag = true │
│ │
│ 4. Save All Changes (bottom bar) │
│ └─ PUT /admin/week-template │
│ (Full template array) │
│ │
│ 5. Backend transaction: │
│ - DELETE all templates │
│ - CREATE new templates │
└────────────────────────────────────────┘
└─► 🔧 临时调整 (Slot Adjustment - 3 Tabs)
└─────────────────────────────────────────┐
│ pages/admin/slot-adjust.vue │
│ ================================ │
│ │
│ TAB 0: 新增时段 (Add Manual Slot) │
│ ├─ Date picker │
│ ├─ Time pickers │
│ ├─ Capacity input │
│ └─ POST /admin/time-slot/manual │
│ └─ Creates slot with source=MANUAL │
│ │
│ TAB 1: 关闭时段 (Close Slots) │
│ ├─ Date picker │
│ ├─ Fetch slots for date │
│ │ └─ GET /admin/time-slots?date=XXX │
│ ├─ Display with status badges │
│ │ (OPEN/FULL/CLOSED) │
│ └─ PUT /admin/time-slot/:id/close │
│ │
│ TAB 2: 批量生成 (Batch Generate) │
│ ├─ Start/end date pickers │
│ ├─ POST /admin/generate-slots │
│ └─ Backend: │
│ 1. Fetch active WeekTemplates │
│ 2. For each day in range: │
│ - Get ISO weekday (1-7) │
│ - Find matching templates │
│ - Create TimeSlot records │
│ 3. Returns { count: N } │
└────────────────────────────────────────┘
Data Flow: Template → Slots
┌──────────────────────────────────────────────────────────────────┐
│ ADMIN TEMPLATE SETUP │
│ (weeks/admin/week-template.vue) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Admin configures templates: │
│ │
│ 周一: 09:00-10:00 (10 ppl) │
│ 周一: 18:00-19:00 (8 ppl) │
│ 周三: 10:00-11:00 (12 ppl) │
│ 周五: 18:00-20:00 (15 ppl) │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ PUT /admin/week-template │
│ (All templates replaced) │
└───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ Backend: Delete all, Create new (atomic) │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ Scheduler (nightly cron or manual trigger)│
│ POST /admin/generate-slots (14 days) │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ SlotGeneratorService.generateSlots() │
│ │
│ For each active template: │
│ For date in next 14 days: │
│ If template.dayOfWeek == date.dayOfWeek:
│ CREATE TimeSlot { │
│ date, startTime, endTime, │
│ capacity, source=TEMPLATE, │
│ templateId │
│ } │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ GENERATED TIME SLOTS │
│ │
│ 2026-04-06 (Mon): │
│ 09:00-10:00 (10 ppl, OPEN) │
│ 18:00-19:00 (8 ppl, OPEN) │
│ │
│ 2026-04-08 (Wed): │
│ 10:00-11:00 (12 ppl, OPEN) │
│ │
│ 2026-04-11 (Fri): │
│ 18:00-20:00 (15 ppl, OPEN) │
│ ... (more dates) │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ Members can book available slots │
│ GET /time-slot/available?date=YYYY-MM-DD
└────────────────────────────────────────────┘
State Management
Component State (week-template.vue)
templates: LocalTemplate[] ◄─ Main data array
loading: boolean ◄─ Fetch state
saving: boolean ◄─ Save state
isDirty: boolean ◄─ "Save bar" trigger
showModal: boolean ◄─ Modal visibility
editTarget: LocalTemplate | null ◄─ Which template is being edited
form: { ◄─ Modal form data
dayIdx: number
startTime: string
endTime: string
capacityStr: string
}
grouped: Computed<Record<number, LocalTemplate[]>> ◄─ Grouped by dayOfWeek
Store State (stores/admin.ts)
weekTemplates: WeekTemplate[] ◄─ Cached from server
cardTypes: CardType[]
studioConfig: StudioConfig | null
// ...other admin state
API Endpoints Summary
Week Templates
GET /admin/week-template Fetch all templates
PUT /admin/week-template Replace all templates
Time Slots
GET /admin/time-slots?date=YYYY-MM-DD Fetch slots for date
POST /admin/time-slot/manual Create manual slot
PUT /admin/time-slot/:id/close Close a slot
POST /admin/generate-slots Generate slots from templates
Public Endpoints
GET /time-slot/available?date=YYYY-MM-DD For members
GET /time-slot/:id For members
Entity Relationships
┌─────────────────────┐
│ WeekTemplate │
├─────────────────────┤
│ id │
│ dayOfWeek (1-7) │
│ startTime │
│ endTime │
│ capacity │
│ isActive │
└─────────────────────┘
│ (1:N)
│
▼
┌─────────────────────┐ ┌──────────────────┐
│ TimeSlot │ │ Booking (M:1) │
├─────────────────────┤ ├──────────────────┤
│ id │◄─────│ timeSlotId │
│ date │ │ userId │
│ startTime │ │ status │
│ endTime │ └──────────────────┘
│ capacity │
│ bookedCount │
│ status │
│ source (TEMPLATE/ │
│ MANUAL) │
│ templateId (FK) │
└─────────────────────┘
Weekday Mapping
Frontend Picker (dayOptions)
Index 0: 周一 (Monday) ──► dayOfWeek = 1
Index 1: 周二 (Tuesday) ──► dayOfWeek = 2
Index 2: 周三 (Wednesday) ──► dayOfWeek = 3
Index 3: 周四 (Thursday) ──► dayOfWeek = 4
Index 4: 周五 (Friday) ──► dayOfWeek = 5
Index 5: 周六 (Saturday) ──► dayOfWeek = 6
Index 6: 周日 (Sunday) ──► dayOfWeek = 7
Backend Conversion (slot-generator.service.ts)
JS getDay(): 0=Sun, 1=Mon, 2=Tue, ..., 6=Sat
│
▼ toIsoWeekday()
│
ISO weekday: 1=Mon, 2=Tue, ..., 7=Sun
Timeline Example
TODAY: 2026-04-05 (Sunday)
Admin actions:
1. Sets up weekly templates for Mon-Fri
2. Taps "保存全部更改"
3. PUT /admin/week-template sent
Backend scheduler (daily at midnight):
4. Runs generateSlots(14)
5. Tomorrow is 2026-04-06 (Monday)
6. Generates slots for Apr 6-19 (next 14 days)
7. Creates TimeSlots based on active templates:
Generated slots:
2026-04-06 (Mon): 09:00-10:00, 18:00-19:00
2026-04-07 (Tue): (none if no templates)
2026-04-08 (Wed): 10:00-11:00
2026-04-09 (Thu): (none if no templates)
2026-04-10 (Fri): 18:00-20:00
2026-04-11 (Sat): (none - weekend)
2026-04-12 (Sun): (none - weekend)
...repeats until 2026-04-19
Members can book from Apr 6 onwards