|
@@ -0,0 +1,1311 @@
|
|
|
|
|
+# 宠物服务管理系统 — 客户端详细设计文档
|
|
|
|
|
+
|
|
|
|
|
+## 1. 技术栈
|
|
|
|
|
+
|
|
|
|
|
+| 技术 | 版本 | 用途 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| Vue 3 | ^3.5.13 | 前端框架(Composition API + `<script setup>`) |
|
|
|
|
|
+| Vite | ^6.3.5 | 构建工具(HMR、代理配置) |
|
|
|
|
|
+| TypeScript | ~5.8.3 | 类型安全(strict: true, noUnusedLocals: true) |
|
|
|
|
|
+| Element Plus | ^2.13.6 | UI 组件库(主题色 #e34889) |
|
|
|
|
|
+| Pinia | ^3.0.4 | 状态管理(Composition API 风格) |
|
|
|
|
|
+| Pinia Persistedstate | ^4.7.1 | Store 自动持久化到 localStorage |
|
|
|
|
|
+| Vue Router | ^4.6.4 | 路由管理(嵌套路由 + 导航守卫) |
|
|
|
|
|
+| Axios | ^1.15.2 | HTTP 请求(withCredentials: true, 响应拦截) |
|
|
|
|
|
+| Orval | 7.0 | OpenAPI → TypeScript API 代码生成 |
|
|
|
|
|
+| Prettier | ^3.8.3 | 代码格式化 |
|
|
|
|
|
+
|
|
|
|
|
+## 2. 项目结构
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ui/src/
|
|
|
|
|
+├── main.ts # 入口文件(createApp, use pinia/router/element-plus)
|
|
|
|
|
+├── App.vue # 根组件(仅 <router-view/>)
|
|
|
|
|
+├── style.css # 全局样式(CSS 变量、主题色定义)
|
|
|
|
|
+├── vite-env.d.ts # Vite 环境类型声明
|
|
|
|
|
+│
|
|
|
|
|
+├── type/
|
|
|
|
|
+│ └── index.ts # 自定义类型定义
|
|
|
|
|
+│ - User { id, name, avatar?, role }
|
|
|
|
|
+│ - LoginUser { isLogin, token?, user? }
|
|
|
|
|
+│ - Pet { id, name, breed, age, weight, gender, notes? }
|
|
|
|
|
+│ - Package { id, type, name, price, description?, durationDays?, isActive }
|
|
|
|
|
+│ - BoardingRoom { id, name, capacity, pricePerNight, description? }
|
|
|
|
|
+│ - RoomStatus { date, status: 'AVAILABLE'|'OCCUPIED' }
|
|
|
|
|
+│ - Appointment { id, userId, petId, packageId, roomId?, type, startDate?,
|
|
|
|
|
+│ endDate?, appointmentTime?, status, deposit, depositPaid,
|
|
|
|
|
+│ totalPrice, cancelReason?, rejectReason?, createdAt }
|
|
|
|
|
+│ - Schedule { id, designerName, workDate, timeSlot, isAvailable }
|
|
|
|
|
+│
|
|
|
|
|
+├── util/
|
|
|
|
|
+│ ├── index.ts # 工具函数(当前为空)
|
|
|
|
|
+│ └── axios-instance.ts # Axios 实例配置
|
|
|
|
|
+│ - baseURL: '/api'(Vite 代理 /api → localhost:8080)
|
|
|
|
|
+│ - withCredentials: true(自动携带 Cookie/JSESSIONID)
|
|
|
|
|
+│ - 响应拦截器:校验 code !== 0/200 → reject;401 → window.location.href='/login'
|
|
|
|
|
+│ - customAxiosInstance<T>(config): Promise<T>(供 Orval 使用)
|
|
|
|
|
+│
|
|
|
|
|
+├── api/ # Orval 生成的 API 控制器(按标签拆分)
|
|
|
|
|
+│ ├── auth-controller.ts # getAuthController() → { login, register, logout }
|
|
|
|
|
+│ ├── user-controller.ts # getUserController() → { queryByPage, add, edit, delete, ... }
|
|
|
|
|
+│ ├── pet-controller.ts # getPetController() → { list, add, getById, update, delete, ... }
|
|
|
|
|
+│ ├── appointment-controller.ts # getAppointmentController() → { create, list, getById, cancel, payDeposit }
|
|
|
|
|
+│ ├── admin-appointment-controller.ts # getAdminAppointmentController() → { listAll, review, handleCancel }
|
|
|
|
|
+│ ├── boarding-room-controller.ts # getBoardingRoomController() → { listRooms, getStatus }
|
|
|
|
|
+│ ├── admin-boarding-room-controller.ts # getAdminBoardingRoomController() → { updateStatus, ... }
|
|
|
|
|
+│ ├── package-controller.ts # getPackageController() → { list, getById }
|
|
|
|
|
+│ ├── admin-package-controller.ts # getAdminPackageController() → { create, update, delete }
|
|
|
|
|
+│ ├── admin-schedule-controller.ts # getAdminScheduleController() → { list, create, delete }
|
|
|
|
|
+│ ├── admin-user-controller.ts # getAdminUserController() → { list, getDetail }
|
|
|
|
|
+│ ├── schedule-controller.ts # getScheduleController()(预留)
|
|
|
|
|
+│ └── models/ # Orval 生成的 TypeScript 类型
|
|
|
|
|
+│ ├── index.ts # 统一导出
|
|
|
|
|
+│ ├── response.ts # Response<T> { code, message, data }
|
|
|
|
|
+│ ├── responseUserVo.ts, responseBoolean.ts, ...
|
|
|
|
|
+│ ├── userVo.ts, userDto.ts, loginDto.ts, registerDto.ts
|
|
|
|
|
+│ ├── updateUserPasswordDto.ts, updateUserAvatarDto.ts, updateUserStatusDto.ts
|
|
|
|
|
+│ ├── deleteByIdParams.ts, queryByPageParams.ts, pageVoListUserVo.ts
|
|
|
|
|
+│ ├── appointmentDto.ts, reviewDto.ts, handleCancelDto.ts
|
|
|
|
|
+│ └── ...
|
|
|
|
|
+│
|
|
|
|
|
+├── store/ # Pinia 状态管理
|
|
|
|
|
+│ ├── index.ts # 统一导出 { useLoginUserStore, useMetaStore, useAppointmentStore, usePetStore }
|
|
|
|
|
+│ ├── user.ts # useLoginUserStore — 用户登录状态
|
|
|
|
|
+│ ├── meta.ts # useMetaStore — 站点元数据
|
|
|
|
|
+│ ├── appointment.ts # useAppointmentStore — 预约流程临时状态
|
|
|
|
|
+│ └── pet.ts # usePetStore — 宠物列表缓存
|
|
|
|
|
+│
|
|
|
|
|
+├── router/
|
|
|
|
|
+│ └── index.ts # Vue Router 配置 + 导航守卫
|
|
|
|
|
+│ - 公共路由: /login
|
|
|
|
|
+│ - 用户侧: UserLayout + children(预约/订单/宠物/个人中心)
|
|
|
|
|
+│ - 管理侧: AdminLayout + children(订单审核/房态/排班/套餐/用户)
|
|
|
|
|
+│ - beforeEach 守卫: requiresAuth → 登录校验, requiresAdmin → 角色校验
|
|
|
|
|
+│
|
|
|
|
|
+├── layout/
|
|
|
|
|
+│ ├── UserLayout.vue # 用户侧布局(水平 TopNav + 内容区)
|
|
|
|
|
+│ └── AdminLayout.vue # 商家后台侧布局(可折叠侧边栏 + 顶栏 + 内容区)
|
|
|
|
|
+│
|
|
|
|
|
+├── view/
|
|
|
|
|
+│ ├── HomeView.vue # 首页(品牌展示,快速入口卡片)
|
|
|
|
|
+│ ├── LoginView.vue # 登录/注册(Tab 切换,毛玻璃卡片,渐变背景)
|
|
|
|
|
+│ ├── AboutView.vue # 关于页面(占位)
|
|
|
|
|
+│ ├── UserView.vue # 用户管理(后台,搜索/表格/CRUD/分页/状态切换)
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ├── booking/ # 服务预约模块
|
|
|
|
|
+│ │ ├── BookingView.vue # 预约首页(选择寄养/洗护)
|
|
|
|
|
+│ │ ├── BoardingBookingView.vue # 寄养预约(4 步 Stepper:套餐→房型日期→宠物→提交)
|
|
|
|
|
+│ │ └── GroomingBookingView.vue # 洗护预约(4 步 Stepper:套餐→时段→宠物→提交)
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ├── order/ # 订单管理模块
|
|
|
|
|
+│ │ ├── OrderListView.vue # 我的订单列表(Tab 筛选,状态标签)
|
|
|
|
|
+│ │ └── OrderDetailView.vue # 订单详情(信息展示,支付/取消按钮)
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ├── pet/ # 宠物档案模块
|
|
|
|
|
+│ │ ├── PetListView.vue # 宠物列表(卡片展示,增删查改)
|
|
|
|
|
+│ │ ├── PetCreateView.vue # 新增宠物表单
|
|
|
|
|
+│ │ ├── PetDetailView.vue # 宠物详情(基本信息+疫苗记录+驱虫记录)
|
|
|
|
|
+│ │ └── PetEditView.vue # 编辑宠物表单
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ├── profile/ # 个人中心模块
|
|
|
|
|
+│ │ ├── ProfileView.vue # 个人资料展示
|
|
|
|
|
+│ │ ├── ProfileEditView.vue # 修改个人资料
|
|
|
|
|
+│ │ └── PasswordEditView.vue # 修改密码
|
|
|
|
|
+│ │
|
|
|
|
|
+│ └── admin/ # 商家后台模块
|
|
|
|
|
+│ ├── AdminOrderListView.vue # 订单审核列表(表格+筛选+审核/取消处理弹窗)
|
|
|
|
|
+│ ├── AdminOrderDetailView.vue # 订单审核详情
|
|
|
|
|
+│ ├── AdminRoomCalendarView.vue # 宠物房间管理(房型选择+月份选择+日历)
|
|
|
|
|
+│ ├── AdminScheduleListView.vue # 排班管理(表格+新增弹窗+删除)
|
|
|
|
|
+│ ├── AdminPackageListView.vue # 套餐配置列表
|
|
|
|
|
+│ ├── AdminPackageCreateView.vue # 新增套餐表单
|
|
|
|
|
+│ ├── AdminPackageEditView.vue # 编辑套餐表单
|
|
|
|
|
+│ └── AdminUserDetailView.vue # 用户详情(含宠物档案列表)
|
|
|
|
|
+│
|
|
|
|
|
+└── components/ # 公共可复用组件
|
|
|
|
|
+ ├── RoomCalendar.vue # 宠物房间日历(props: roomId/month/statusList, events: date-click)
|
|
|
|
|
+ ├── PaymentDialog.vue # 模拟支付弹窗(props: visible/amount/orderId, events: success/close)
|
|
|
|
|
+ └── PetSelector.vue # 宠物选择器(props: petList/selected, events: update:selected)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 3. 路由设计
|
|
|
|
|
+
|
|
|
|
|
+### 3.1 路由总览
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ A["/login<br/>(LoginView)"] --- B[""/""]
|
|
|
|
|
+
|
|
|
|
|
+ B --> C["UserLayout<br/>用户侧布局"]
|
|
|
|
|
+ C --> C1["/booking<br/>服务预约"]
|
|
|
|
|
+ C --> C2["/booking/boarding<br/>寄养预约*"]
|
|
|
|
|
+ C --> C3["/booking/grooming<br/>洗护预约*"]
|
|
|
|
|
+ C --> C4["/orders<br/>我的订单*"]
|
|
|
|
|
+ C --> C5["/orders/:id<br/>订单详情*"]
|
|
|
|
|
+ C --> C6["/pets<br/>宠物档案*"]
|
|
|
|
|
+ C --> C7["/pets/new<br/>新增宠物*"]
|
|
|
|
|
+ C --> C8["/pets/:id<br/>宠物详情*"]
|
|
|
|
|
+ C --> C9["/pets/:id/edit<br/>编辑宠物*"]
|
|
|
|
|
+ C --> C10["/profile<br/>个人中心*"]
|
|
|
|
|
+ C --> C11["/profile/edit<br/>修改资料*"]
|
|
|
|
|
+ C --> C12["/profile/password<br/>修改密码*"]
|
|
|
|
|
+
|
|
|
|
|
+ B --> D["AdminLayout<br/>商家后台布局*"]
|
|
|
|
|
+ D --> D1["/admin/orders<br/>订单审核"]
|
|
|
|
|
+ D --> D2["/admin/orders/:id<br/>审核详情"]
|
|
|
|
|
+ D --> D3["/admin/rooms<br/>宠物房间管理"]
|
|
|
|
|
+ D --> D4["/admin/schedules<br/>排班管理"]
|
|
|
|
|
+ D --> D5["/admin/packages<br/>套餐配置"]
|
|
|
|
|
+ D --> D6["/admin/packages/new<br/>新增套餐"]
|
|
|
|
|
+ D --> D7["/admin/packages/:id/edit<br/>编辑套餐"]
|
|
|
|
|
+ D --> D8["/admin/users<br/>用户管理"]
|
|
|
|
|
+ D --> D9["/admin/users/:id<br/>用户详情"]
|
|
|
|
|
+
|
|
|
|
|
+ style D fill:#fce7f3,stroke:#db2777
|
|
|
|
|
+ style C fill:#e8f5e9,stroke:#2e7d32
|
|
|
|
|
+ style A fill:#fff3e0,stroke:#f57c00
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> `*` 标记的路由需要 `requiresAuth: true`(登录校验)
|
|
|
|
|
+>
|
|
|
|
|
+> `/admin` 前缀下的所有路由需要 `requiresAdmin: true`(管理员角色校验)
|
|
|
|
|
+
|
|
|
|
|
+### 3.2 路由守卫
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ A["Router Navigation"] --> B{"to.matched<br/>有 requiresAuth<br/>或 requiresAdmin?"}
|
|
|
|
|
+ B -- 否 --> C["next()<br/>放行"]
|
|
|
|
|
+ B -- 是 --> D{"loginUserStore<br/>.isLogin?"}
|
|
|
|
|
+ D -- 否 --> E["ElMessage.warning('请先登录')"]
|
|
|
|
|
+ E --> F["next({ path: '/login',<br/> query: { redirect } })"]
|
|
|
|
|
+ D -- 是 --> G{"requiresAdmin &&<br/>role !== 'admin'?"}
|
|
|
|
|
+ G -- 是 --> H["ElMessage.error('无权访问')"]
|
|
|
|
|
+ H --> I["next('/booking')"]
|
|
|
|
|
+ G -- 否 --> C
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**守卫逻辑摘要:**
|
|
|
|
|
+- `requiresAuth` 标记在需要登录的路由 meta 上(预约、订单、宠物、个人中心等)
|
|
|
|
|
+- `requiresAdmin` 标记在 `/admin` 布局路由的 meta 上,子路由自动继承
|
|
|
|
|
+- `redirect` 参数在登录后导航回原始目标页面
|
|
|
|
|
+- 登录页本身无需守卫,已登录用户可自由访问
|
|
|
|
|
+
|
|
|
|
|
+### 3.3 用户侧路由表
|
|
|
|
|
+
|
|
|
|
|
+| 路径 | 名称 | 组件 | meta.title | meta.icon | requiresAuth |
|
|
|
|
|
+|------|------|------|-----------|-----------|-------------|
|
|
|
|
|
+| `/login` | `login` | LoginView | - | - | ❌ |
|
|
|
|
|
+| `/` | - | UserLayout (布局) | - | - | ❌ |
|
|
|
|
|
+| `/booking` | `booking` | BookingView | 服务预约 | Calendar | ❌ |
|
|
|
|
|
+| `/booking/boarding` | `boardingBooking` | BoardingBookingView | 寄养预约 | - | ✅ |
|
|
|
|
|
+| `/booking/grooming` | `groomingBooking` | GroomingBookingView | 洗护预约 | - | ✅ |
|
|
|
|
|
+| `/orders` | `orderList` | OrderListView | 我的订单 | Tickets | ✅ |
|
|
|
|
|
+| `/orders/:id` | `orderDetail` | OrderDetailView | 订单详情 | - | ✅ |
|
|
|
|
|
+| `/pets` | `petList` | PetListView | 宠物档案 | Avatar | ✅ |
|
|
|
|
|
+| `/pets/new` | `petCreate` | PetCreateView | 新增宠物 | - | ✅ |
|
|
|
|
|
+| `/pets/:id` | `petDetail` | PetDetailView | 宠物详情 | - | ✅ |
|
|
|
|
|
+| `/pets/:id/edit` | `petEdit` | PetEditView | 编辑宠物 | - | ✅ |
|
|
|
|
|
+| `/profile` | `profile` | ProfileView | 个人中心 | User | ✅ |
|
|
|
|
|
+| `/profile/edit` | `profileEdit` | ProfileEditView | 修改资料 | - | ✅ |
|
|
|
|
|
+| `/profile/password` | `passwordEdit` | PasswordEditView | 修改密码 | - | ✅ |
|
|
|
|
|
+
|
|
|
|
|
+### 3.4 商家后台侧路由表
|
|
|
|
|
+
|
|
|
|
|
+| 路径 | 名称 | 组件 | meta.title | meta.icon | requiresAdmin |
|
|
|
|
|
+|------|------|------|-----------|-----------|-------------|
|
|
|
|
|
+| `/admin` | `adminLayout` | AdminLayout (布局) | - | - | ✅ |
|
|
|
|
|
+| `/admin/orders` | `adminOrderList` | AdminOrderListView | 订单审核 | List | ✅ |
|
|
|
|
|
+| `/admin/orders/:id` | `adminOrderDetail` | AdminOrderDetailView | 订单审核详情 | - | ✅ |
|
|
|
|
|
+| `/admin/rooms` | `adminRooms` | AdminRoomCalendarView | 房态管理 | Calendar | ✅ |
|
|
|
|
|
+| `/admin/schedules` | `adminSchedules` | AdminScheduleListView | 排班管理 | EditPen | ✅ |
|
|
|
|
|
+| `/admin/packages` | `adminPackageList` | AdminPackageListView | 套餐配置 | Coin | ✅ |
|
|
|
|
|
+| `/admin/packages/new` | `adminPackageCreate` | AdminPackageCreateView | 新增套餐 | - | ✅ |
|
|
|
|
|
+| `/admin/packages/:id/edit` | `adminPackageEdit` | AdminPackageEditView | 编辑套餐 | - | ✅ |
|
|
|
|
|
+| `/admin/users` | `adminUsers` | UserView | 用户管理 | User | ✅ |
|
|
|
|
|
+| `/admin/users/:id` | `adminUserDetail` | AdminUserDetailView | 用户详情 | - | ✅ |
|
|
|
|
|
+
|
|
|
|
|
+## 4. 布局组件
|
|
|
|
|
+
|
|
|
|
|
+### 4.1 UserLayout
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ [Logo] 宠物服务管理系统 [服务预约] [我的订单] [宠物档案] [个人中心] │ ← el-header
|
|
|
|
|
+│ [后台管理] [用户名 ▼] [登录/注册] │
|
|
|
|
|
+├──────────────────────────────────────────────────────────────────┤
|
|
|
|
|
+│ │
|
|
|
|
|
+│ <router-view /> │ ← el-main
|
|
|
|
|
+│ │
|
|
|
|
|
+└──────────────────────────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**导航菜单渲染:**
|
|
|
|
|
+- 从 `userRoutes[0].children` 中筛选出有 `meta.icon` 的菜单项(过滤掉无图标的详情页)
|
|
|
|
|
+- 当前激活项根据 `route.path` 高亮
|
|
|
|
|
+- 管理员用户额外显示"后台管理"入口,跳转 `/admin`
|
|
|
|
|
+
|
|
|
|
|
+**用户状态区域:**
|
|
|
|
|
+- 未登录:显示「登录」「注册」两个按钮
|
|
|
|
|
+- 已登录:显示用户头像 + 用户名下拉菜单(个人信息、我的订单、宠物档案、退出登录)
|
|
|
|
|
+
|
|
|
|
|
+**退出逻辑:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+const logoutHandler = async () => {
|
|
|
|
|
+ await getAuthController().logout() // 服务端销毁 Session
|
|
|
|
|
+ loginUserStore.logoutUser() // 清除 Pinia 状态
|
|
|
|
|
+ router.push('/login')
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 4.2 AdminLayout
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ [Logo] 宠物服务管理系统 后台管理 [用户名 ▼] │ ← el-header
|
|
|
|
|
+├────────────┬─────────────────────────────────────────────────────┤
|
|
|
|
|
+│ [展开/收起]│ │
|
|
|
|
|
+│ │ <router-view /> │ ← el-main
|
|
|
|
|
+│ 订单审核 │ │
|
|
|
|
|
+│ 房态管理 │ │
|
|
|
|
|
+│ 排班管理 │ │
|
|
|
|
|
+│ 套餐配置 │ │
|
|
|
|
|
+│ 用户管理 │ │
|
|
|
|
|
+└────────────┴─────────────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**可折叠侧边栏:**
|
|
|
|
|
+- `el-aside` 宽度默认 200px,收起时 64px
|
|
|
|
|
+- `el-radio-group` 展开/收起切换(`isCollapse` ref)
|
|
|
|
|
+- 侧边栏菜单从 `adminRoutes[0].children` 中筛选有 `meta.icon` 的项
|
|
|
|
|
+
|
|
|
|
|
+**顶部栏右侧用户下拉:**
|
|
|
|
|
+- 用户名显示 + 下拉菜单(个人信息、返回前台、退出)
|
|
|
|
|
+
|
|
|
|
|
+## 5. 公共组件
|
|
|
|
|
+
|
|
|
|
|
+### 5.1 RoomCalendar.vue — 宠物房间日历
|
|
|
|
|
+
|
|
|
|
|
+**用途:** 在寄养预约和宠物房间管理页面中展示指定房型某月的每日状态。
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────┐
|
|
|
|
|
+│ < 2026年7月 > │
|
|
|
|
|
+├──────────────────────────────┤
|
|
|
|
|
+│ 日 一 二 三 四 五 六 │
|
|
|
|
|
+│ 1 2 3 4 │
|
|
|
|
|
+│ 5 6 7 8 9 10 11 │
|
|
|
|
|
+│ ... │
|
|
|
|
|
+└──────────────────────────────┘
|
|
|
|
|
+ 绿色=AVAILABLE 红色=OCCUPIED
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Props:**
|
|
|
|
|
+
|
|
|
|
|
+| prop | 类型 | 必填 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| `roomId` | `string` | ✅ | 房型 ID,用于标识当前展示的房型 |
|
|
|
|
|
+| `month` | `string` | ✅ | 格式 `YYYY-MM`,如 `2026-07` |
|
|
|
|
|
+| `statusList` | `{ date: string; status: 'AVAILABLE' \| 'OCCUPIED' }[]` | ✅ | 当月每日状态列表 |
|
|
|
|
|
+
|
|
|
|
|
+**Emits:**
|
|
|
|
|
+
|
|
|
|
|
+| event | 参数 | 说明 |
|
|
|
|
|
+|-------|------|------|
|
|
|
|
|
+| `date-click` | `(date: string, status: 'AVAILABLE' \| 'OCCUPIED')` | 点击某天时触发 |
|
|
|
|
|
+| `month-change` | `(month: string)` | 月份切换时触发(TODO: 当前组件中 prevMonth/nextMonth 为占位实现) |
|
|
|
|
|
+
|
|
|
|
|
+**渲染逻辑:**
|
|
|
|
|
+- 根据 `month` 计算当月天数、第一天是星期几
|
|
|
|
|
+- 构造 6×7 的日历网格(`(DayInfo | null)[][]`)
|
|
|
|
|
+- 空白天数用 `null` 填充
|
|
|
|
|
+- 每一天根据 `statusMap` 查找状态,应用 CSS 类:`available`(绿色 `#e8f5e9`)、`occupied`(红色 `#ffebee`)
|
|
|
|
|
+
|
|
|
|
|
+### 5.2 PaymentDialog.vue — 模拟支付弹窗
|
|
|
|
|
+
|
|
|
|
|
+**用途:** 预约提交成功后弹出,模拟支付定金流程。
|
|
|
|
|
+
|
|
|
|
|
+**Props:**
|
|
|
|
|
+
|
|
|
|
|
+| prop | 类型 | 必填 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| `visible` | `boolean` | ✅ | 弹窗显隐(v-model:visible) |
|
|
|
|
|
+| `amount` | `number` | ✅ | 定金金额 |
|
|
|
|
|
+| `orderId` | `string` | ✅ | 订单 ID,用于调用支付接口 |
|
|
|
|
|
+
|
|
|
|
|
+**Emits:**
|
|
|
|
|
+
|
|
|
|
|
+| event | 参数 | 说明 |
|
|
|
|
|
+|-------|------|------|
|
|
|
|
|
+| `update:visible` | `(value: boolean)` | 弹窗显隐变化 |
|
|
|
|
|
+| `success` | 无 | 支付成功后触发 |
|
|
|
|
|
+| `close` | 无 | 弹窗关闭时触发 |
|
|
|
|
|
+
|
|
|
|
|
+**插槽:** 无
|
|
|
|
|
+
|
|
|
|
|
+**交互流程:**
|
|
|
|
|
+1. 显示定金金额 `¥{{ amount }}`
|
|
|
|
|
+2. 支付方式选择:微信支付 / 支付宝(`el-radio-group`)
|
|
|
|
|
+3. 点击「确认支付」→ `POST /api/appointments/{orderId}/pay-deposit`
|
|
|
|
|
+4. 成功 → `ElMessage.success('支付成功')` → `emit('success')` → 关闭弹窗
|
|
|
|
|
+5. 失败 → `ElMessage.error` 显示错误信息
|
|
|
|
|
+
|
|
|
|
|
+### 5.3 PetSelector.vue — 宠物选择器
|
|
|
|
|
+
|
|
|
|
|
+**用途:** 在预约流程中选择宠物。
|
|
|
|
|
+
|
|
|
|
|
+**Props:**
|
|
|
|
|
+
|
|
|
|
|
+| prop | 类型 | 必填 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| `petList` | `Pet[]` | ✅ | 用户的所有宠物列表 |
|
|
|
|
|
+| `selected` | `Pet \| null` | ✅ | 当前选中的宠物 |
|
|
|
|
|
+
|
|
|
|
|
+**Emits:**
|
|
|
|
|
+
|
|
|
|
|
+| event | 参数 | 说明 |
|
|
|
|
|
+|-------|------|------|
|
|
|
|
|
+| `update:selected` | `(pet: Pet)` | 选择宠物时触发 |
|
|
|
|
|
+
|
|
|
|
|
+**插槽:** 无
|
|
|
|
|
+
|
|
|
|
|
+**渲染逻辑:**
|
|
|
|
|
+- 宠物列表为空 → 显示 `el-empty` +「新增宠物」按钮(跳转 `/pets/new`)
|
|
|
|
|
+- 有宠物 → `el-card` 列表,每张卡片显示宠物首字母头像、名称、品种、年龄
|
|
|
|
|
+- 选中卡片高亮(`border-color: var(--el-color-primary)`)+ 勾选图标
|
|
|
|
|
+
|
|
|
|
|
+## 6. 页面详细设计
|
|
|
|
|
+
|
|
|
|
|
+### 6.1 登录/注册页 (LoginView)
|
|
|
|
|
+
|
|
|
|
|
+**布局:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ ← 返回主页 │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 欢 迎 │
|
|
|
|
|
+│ 请登录或注册您的账号 │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ┌────────────┬────────────┐ │
|
|
|
|
|
+│ │ 登录 │ 注册 │ │ ← el-tabs
|
|
|
|
|
+│ └────────────┴────────────┘ │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ┌────────────────────────┐ │
|
|
|
|
|
+│ │ 👤 请输入账号 │ │
|
|
|
|
|
+│ ├────────────────────────┤ │
|
|
|
|
|
+│ │ 🔒 请输入密码 │ │
|
|
|
|
|
+│ ├────────────────────────┤ │
|
|
|
|
|
+│ │ [ 登录 ] │ │ ← 渐变粉色按钮
|
|
|
|
|
+│ └────────────────────────┘ │
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**数据流:**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor U as User
|
|
|
|
|
+ participant V as LoginView
|
|
|
|
|
+ participant S as useLoginUserStore
|
|
|
|
|
+ participant A as auth-controller
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ U->>V: 填写账号密码
|
|
|
|
|
+ U->>V: 点击登录
|
|
|
|
|
+ V->>V: formRef.validate()
|
|
|
|
|
+ alt 校验失败
|
|
|
|
|
+ V->>U: 显示错误提示
|
|
|
|
|
+ else 校验通过
|
|
|
|
|
+ V->>A: authApi.login({ account, password })
|
|
|
|
|
+ A->>B: POST /api/auth/login
|
|
|
|
|
+ B->>B: 校验账号密码
|
|
|
|
|
+ B->>B: 存入 HttpSession
|
|
|
|
|
+ B-->>A: Response<UserVo>
|
|
|
|
|
+ A-->>V: res.data
|
|
|
|
|
+ V->>S: loginSuccess({ id, name, role })
|
|
|
|
|
+ V->>V: router.push(redirect || '/booking')
|
|
|
|
|
+ V->>U: ElMessage.success('登录成功')
|
|
|
|
|
+ end
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**表单校验规则:**
|
|
|
|
|
+
|
|
|
|
|
+| 表单 | 字段 | 规则 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| 登录 | account | required |
|
|
|
|
|
+| 登录 | password | required, min:4 |
|
|
|
|
|
+| 注册 | account | required, min:4, max:20 |
|
|
|
|
|
+| 注册 | username | required, min:2, max:32 |
|
|
|
|
|
+| 注册 | password | required, min:4, max:20 |
|
|
|
|
|
+| 注册 | confirmPassword | required, 自定义校验器:必须等于 password |
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `getAuthController().login(LoginDto)` → `POST /api/auth/login` → 服务端存入 Session → 返回 `UserVo`
|
|
|
|
|
+- `getAuthController().register(RegisterDto)` → `POST /api/auth/register` → 返回成功 → 切换到登录 tab
|
|
|
|
|
+
|
|
|
|
|
+**状态管理:**
|
|
|
|
|
+- `activeTab`: `ref('login')` — 当前 tab
|
|
|
|
|
+- `loginForm`: `reactive({ account, password })`
|
|
|
|
|
+- `registerForm`: `reactive({ account, username, password, confirmPassword })`
|
|
|
|
|
+- `loginLoading`, `registerLoading`: `ref(false)`
|
|
|
|
|
+
|
|
|
|
|
+### 6.2 首页 (HomeView)
|
|
|
|
|
+
|
|
|
|
|
+**布局:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 宠物服务管理系统 │
|
|
|
|
|
+│ 专业宠物寄养与洗护服务平台 │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ┌──────────────┐ ┌──────────────┐ │
|
|
|
|
|
+│ │ 🏠 寄养服务 │ │ ✂️ 洗护服务 │ │
|
|
|
|
|
+│ │ 专业宠物寄养 │ │ 专业宠物洗护 │ │
|
|
|
|
|
+│ │ 安心托管 │ │ 焕然一新 │ │
|
|
|
|
|
+│ │ [查看详情] │ │ [查看详情] │ │
|
|
|
|
|
+│ └──────────────┘ └──────────────┘ │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 功能介绍 / 平台优势展示 │
|
|
|
|
|
+│ │
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**交互逻辑:**
|
|
|
|
|
+- 点击「寄养服务」卡片 → `router.push('/booking/boarding')`
|
|
|
|
|
+- 点击「洗护服务」卡片 → `router.push('/booking/grooming')`
|
|
|
|
|
+- 未登录用户点击预约需登录(由路由守卫 `requiresAuth` 拦截)
|
|
|
|
|
+
|
|
|
|
|
+**数据依赖:** 无(纯展示页面)
|
|
|
|
|
+
|
|
|
|
|
+### 6.3 预约模块
|
|
|
|
|
+
|
|
|
|
|
+#### 6.3.1 服务预约首页 (BookingView)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ 选择服务类型 │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ┌────────────────┐ ┌────────────────┐│
|
|
|
|
|
+│ │ 寄养服务 │ │ 洗护服务 ││
|
|
|
|
|
+│ │ 专业宠物寄养 │ │ 专业宠物洗护 ││
|
|
|
|
|
+│ │ 安心托管 │ │ 焕然一新 ││
|
|
|
|
|
+│ │ │ │ ││
|
|
|
|
|
+│ │ [查看详情] │ │ [查看详情] ││
|
|
|
|
|
+│ └────────────────┘ └────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**数据流:** 无 API 调用,纯导航跳转
|
|
|
|
|
+
|
|
|
|
|
+#### 6.3.2 寄养预约页 (BoardingBookingView)
|
|
|
|
|
+
|
|
|
|
|
+**多步表单流程(el-steps 4 步):**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor U as User
|
|
|
|
|
+ participant V as BoardingBookingView
|
|
|
|
|
+ participant A as API Layer
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 1 — 选择套餐
|
|
|
|
|
+ V->>A: getPackageController().list4({ type: 'BOARDING' })
|
|
|
|
|
+ A->>B: GET /api/packages?type=BOARDING
|
|
|
|
|
+ B-->>A: PackageVo[]
|
|
|
|
|
+ A-->>V: packages[]
|
|
|
|
|
+ U->>V: 选中一个套餐
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 2 — 选择房型与日期
|
|
|
|
|
+ V->>A: getBoardingRoomController().listRooms()
|
|
|
|
|
+ A->>B: GET /api/boarding-rooms
|
|
|
|
|
+ B-->>A: BoardingRoomVo[]
|
|
|
|
|
+ A-->>V: rooms[]
|
|
|
|
|
+ U->>V: 选中房型
|
|
|
|
|
+ V->>A: getBoardingRoomController().getStatus(roomId, { month })
|
|
|
|
|
+ A->>B: GET /api/boarding-rooms/{id}/status?month=2026-07
|
|
|
|
|
+ B-->>A: RoomStatusVo[]
|
|
|
|
|
+ A-->>V: roomStatusList[]
|
|
|
|
|
+ V->>V: RoomCalendar 渲染日历
|
|
|
|
|
+ U->>V: 选择入住/离店日期
|
|
|
|
|
+ V->>V: calcTotal() — 计算总价(天数 × pricePerNight)
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 3 — 选择宠物
|
|
|
|
|
+ V->>A: getPetController().list()
|
|
|
|
|
+ A->>B: GET /api/pets
|
|
|
|
|
+ B-->>A: PetVo[]
|
|
|
|
|
+ A-->>V: myPets[]
|
|
|
|
|
+ U->>V: PetSelector 选中宠物
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 4 — 确认提交
|
|
|
|
|
+ V->>V: 展示订单摘要
|
|
|
|
|
+ U->>V: 点击提交预约
|
|
|
|
|
+ V->>A: getAppointmentController().create(AppointmentDto)
|
|
|
|
|
+ A->>B: POST /api/appointments { packageId, roomId, petId, startDate, endDate }
|
|
|
|
|
+ B-->>A: AppointmentVo { id, deposit, ... }
|
|
|
|
|
+ A-->>V: createdOrderId, depositAmount
|
|
|
|
|
+ V->>V: PaymentDialog 弹窗
|
|
|
|
|
+ U->>V: 支付定金
|
|
|
|
|
+ V->>A: getAppointmentController().payDeposit(orderId)
|
|
|
|
|
+ A->>B: POST /api/appointments/{id}/pay-deposit
|
|
|
|
|
+ B-->>A: success
|
|
|
|
|
+ V->>V: router.push('/orders')
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**组件树:**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ V["BoardingBookingView"]
|
|
|
|
|
+ V --> S["el-steps (4步)"]
|
|
|
|
|
+ V --> Step1["Step1: 套餐选择"]
|
|
|
|
|
+ Step1 --> PkgList["el-radio-group > el-card<br/>(API: GET packages)"]
|
|
|
|
|
+ V --> Step2["Step2: 房型与日期"]
|
|
|
|
|
+ Step2 --> RoomSel["el-select 房型列表<br/>(API: GET rooms)"]
|
|
|
|
|
+ Step2 --> DatePicker["el-date-picker daterange"]
|
|
|
|
|
+ Step2 --> Calendar["RoomCalendar 宠物房间日历<br/>(API: GET room status)"]
|
|
|
|
|
+ Step2 --> Price["总价显示 ¥xxx"]
|
|
|
|
|
+ V --> Step3["Step3: 宠物选择"]
|
|
|
|
|
+ Step3 --> PetSel["PetSelector<br/>(API: GET pets)"]
|
|
|
|
|
+ V --> Step4["Step4: 确认提交"]
|
|
|
|
|
+ Step4 --> Summary["el-descriptions 订单摘要"]
|
|
|
|
|
+ Step4 --> Btn["提交预约"]
|
|
|
|
|
+ V --> Payment["PaymentDialog<br/>支付弹窗 (API: payDeposit)"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**价格计算逻辑:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+const calcTotal = () => {
|
|
|
|
|
+ if (dateRange.value && selectedRoom.value) {
|
|
|
|
|
+ const days = Math.ceil(
|
|
|
|
|
+ (dateRange.value[1].getTime() - dateRange.value[0].getTime()) / 86400000
|
|
|
|
|
+ )
|
|
|
|
|
+ totalPrice.value = days * selectedRoom.value.pricePerNight
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+// 定金 = totalPrice × 30%
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用列表:**
|
|
|
|
|
+- `GET /api/packages?type=BOARDING` — 获取寄养套餐
|
|
|
|
|
+- `GET /api/boarding-rooms` — 获取所有启用房型
|
|
|
|
|
+- `GET /api/boarding-rooms/{id}/status?month=YYYY-MM` — 获取房态
|
|
|
|
|
+- `GET /api/pets` — 获取用户宠物
|
|
|
|
|
+- `POST /api/appointments` — 提交预约
|
|
|
|
|
+- `POST /api/appointments/{id}/pay-deposit` — 支付定金
|
|
|
|
|
+
|
|
|
|
|
+#### 6.3.3 洗护预约页 (GroomingBookingView)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor U as User
|
|
|
|
|
+ participant V as GroomingBookingView
|
|
|
|
|
+ participant A as API Layer
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 1 — 选择套餐
|
|
|
|
|
+ V->>A: getPackageController().list4({ type: 'GROOMING' })
|
|
|
|
|
+ A-->>V: packages[]
|
|
|
|
|
+ U->>V: 选中套餐
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 2 — 选择时间
|
|
|
|
|
+ U->>V: 选择日期 + 时段(上午/下午)
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 3 — 选择宠物
|
|
|
|
|
+ V->>A: getPetController().list()
|
|
|
|
|
+ A-->>V: myPets[]
|
|
|
|
|
+ U->>V: PetSelector 选中宠物
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,V: Step 4 — 确认提交
|
|
|
|
|
+ V->>A: getAppointmentController().create(AppointmentDto)
|
|
|
|
|
+ A-->>V: createdOrderId
|
|
|
|
|
+ V->>V: PaymentDialog 弹窗
|
|
|
|
|
+ U->>V: 支付定金
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**组件树:**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ V["GroomingBookingView"]
|
|
|
|
|
+ V --> S["el-steps (4步)"]
|
|
|
|
|
+ V --> Step1["Step1: 套餐选择"]
|
|
|
|
|
+ Step1 --> PkgList["el-radio-group > el-card<br/>(API: GET packages?type=GROOMING)"]
|
|
|
|
|
+ V --> Step2["Step2: 时段选择"]
|
|
|
|
|
+ Step2 --> Date["el-date-picker"]
|
|
|
|
|
+ Step2 --> Slot["el-radio-group MORNING/AFTERNOON"]
|
|
|
|
|
+ V --> Step3["Step3: 宠物选择"]
|
|
|
|
|
+ Step3 --> PetSel["PetSelector<br/>(API: GET pets)"]
|
|
|
|
|
+ V --> Step4["Step4: 确认提交"]
|
|
|
|
|
+ Step4 --> Summary["el-descriptions 订单摘要"]
|
|
|
|
|
+ Step4 --> Btn["提交预约"]
|
|
|
|
|
+ V --> Payment["PaymentDialog<br/>(API: payDeposit)"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用列表:**
|
|
|
|
|
+- `GET /api/packages?type=GROOMING` — 获取洗护套餐
|
|
|
|
|
+- `GET /api/pets` — 获取宠物列表
|
|
|
|
|
+- `POST /api/appointments` — 提交预约(type=GROOMING)
|
|
|
|
|
+- `POST /api/appointments/{id}/pay-deposit` — 支付定金
|
|
|
|
|
+
|
|
|
|
|
+### 6.4 订单模块
|
|
|
|
|
+
|
|
|
|
|
+#### 6.4.1 订单列表 (OrderListView)
|
|
|
|
|
+
|
|
|
|
|
+**布局:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ 我的预约 │
|
|
|
|
|
+│ [全部] [待确认] [已确认] [已完成] [已取消]│ ← el-tabs
|
|
|
|
|
+├──────────────────────────────────────┤
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ 订单 ID: 1 [PENDING] 待审核 ││
|
|
|
|
|
+│ │ [查看详情] ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ 订单 ID: 2 [CONFIRMED] 已确认││
|
|
|
|
|
+│ │ [查看详情] ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**组件树:**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ V["OrderListView"]
|
|
|
|
|
+ V --> Tabs["el-tabs 状态筛选"]
|
|
|
|
|
+ V --> Cards["el-card 列表"]
|
|
|
|
|
+ Cards --> Card["el-card × N"]
|
|
|
|
|
+ Card --> Tag["el-tag 状态标签"]
|
|
|
|
|
+ Card --> Btn["查看详情 → router.push /orders/:id"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**状态标签颜色映射:**
|
|
|
|
|
+
|
|
|
|
|
+| 状态 | el-tag type | 中文 |
|
|
|
|
|
+|------|------------|------|
|
|
|
|
|
+| PENDING | warning | 待审核 |
|
|
|
|
|
+| CONFIRMED | success | 已确认 |
|
|
|
|
|
+| CANCELLED | info | 已取消 |
|
|
|
|
|
+| COMPLETED | primary | 已完成 |
|
|
|
|
|
+| CANCEL_REQUESTED | danger | 取消申请 |
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/appointments` → 返回所有用户预约列表 → 前端按 activeStatus 客户端过滤
|
|
|
|
|
+
|
|
|
|
|
+#### 6.4.2 订单详情 (OrderDetailView)
|
|
|
|
|
+
|
|
|
|
|
+**布局:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ ← 返回 │
|
|
|
|
|
+├──────────────────────────────────────┤
|
|
|
|
|
+│ 订单详情 │
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ 订单编号: 1 状态: [PENDING] ││
|
|
|
|
|
+│ │ 服务类型: BOARDING ││
|
|
|
|
|
+│ │ 总价: ¥450 ││
|
|
|
|
|
+│ │ 定金: ¥135 定金支付: 未支付 ││
|
|
|
|
|
+│ │ 创建时间: 2026-07-01 10:00 ││
|
|
|
|
|
+│ │ ││
|
|
|
|
|
+│ │ [支付定金] [申请取消] ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**条件按钮逻辑:**
|
|
|
|
|
+- `showPayDeposit`: `depositPaid === false && status === 'PENDING'`
|
|
|
|
|
+- `showCancel`: `status === 'PENDING' || status === 'CONFIRMED'`
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/appointments/{id}` — 获取订单详情
|
|
|
|
|
+- `POST /api/appointments/{id}/cancel` — 申请取消(`ElMessageBox.confirm` + 输入原因)
|
|
|
|
|
+- `POST /api/appointments/{id}/pay-deposit` — 支付定金
|
|
|
|
|
+
|
|
|
|
|
+### 6.5 宠物档案模块
|
|
|
|
|
+
|
|
|
|
|
+#### 6.5.1 宠物列表 (PetListView)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ 宠物档案 [+ 新增宠物] │
|
|
|
|
|
+├──────────────────────────────────────┤
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ [W] 旺财 - 金毛 ││
|
|
|
|
|
+│ │ 2岁 | 公 ││
|
|
|
|
|
+│ │ [查看详情] [编辑] [删除] ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ ... ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/pets` — 获取所有宠物
|
|
|
|
|
+- `DELETE /api/pets/{id}` — 删除宠物(`ElMessageBox.confirm` 确认)
|
|
|
|
|
+
|
|
|
|
|
+#### 6.5.2 宠物详情 (PetDetailView)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ ← 返回 [编辑] [删除]│
|
|
|
|
|
+├──────────────────────────────────────┤
|
|
|
|
|
+│ 基本信息 │
|
|
|
|
|
+│ 名称: 旺财 | 品种: 金毛 │
|
|
|
|
|
+│ 年龄: 2岁 | 体重: 25kg │
|
|
|
|
|
+│ 性别: 公 | 备注: 活泼好动 │
|
|
|
|
|
+├──────────────────────────────────────┤
|
|
|
|
|
+│ 疫苗记录 [+ 新增] │
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ 狂犬疫苗 | 2026-01-01 | 安心医院 ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+├──────────────────────────────────────┤
|
|
|
|
|
+│ 驱虫记录 [+ 新增] │
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ 体外驱虫 | 2026-03-15 | 福来恩 ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/pets/{id}` — 获取宠物详情(含疫苗/驱虫记录)
|
|
|
|
|
+- `DELETE /api/pets/{id}` — 删除宠物
|
|
|
|
|
+- `POST /api/pets/{id}/vaccinations` — 新增疫苗记录
|
|
|
|
|
+- `POST /api/pets/{id}/deworming` — 新增驱虫记录
|
|
|
|
|
+
|
|
|
|
|
+**新增记录弹窗表单字段:**
|
|
|
|
|
+- 疫苗:疫苗名称、接种日期、医院、医生、备注
|
|
|
|
|
+- 驱虫:驱虫名称、驱虫日期、医院、医生、备注
|
|
|
|
|
+
|
|
|
|
|
+#### 6.5.3 新增/编辑宠物 (PetCreateView / PetEditView)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ ← 返回 │
|
|
|
|
|
+│ 新增宠物 / 编辑宠物 │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 宠物名: [__________] │
|
|
|
|
|
+│ 品种: [__________] │
|
|
|
|
|
+│ 年龄: [0] │
|
|
|
|
|
+│ 体重: [0.0] │
|
|
|
|
|
+│ 性别: ○ 公 ○ 母 │
|
|
|
|
|
+│ 备注: [__________] │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ [保存] [取消] │
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用(PetCreateView):**
|
|
|
|
|
+- `POST /api/pets` — 新增宠物
|
|
|
|
|
+
|
|
|
|
|
+**API 调用(PetEditView):**
|
|
|
|
|
+- `GET /api/pets/{id}` — 加载宠物数据填充表单
|
|
|
|
|
+- `PUT /api/pets/{id}` — 更新宠物信息
|
|
|
|
|
+
|
|
|
|
|
+### 6.6 个人中心 (ProfileView / ProfileEditView / PasswordEditView)
|
|
|
|
|
+
|
|
|
|
|
+**ProfileView 布局:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────┐
|
|
|
|
|
+│ 个人中心 │
|
|
|
|
|
+│ ┌──────────────────────────────────┐│
|
|
|
|
|
+│ │ [大头像] ││
|
|
|
|
|
+│ │ 用户名 ││
|
|
|
|
|
+│ │ [admin/user] ││
|
|
|
|
|
+│ ├──────────────────────────────────┤│
|
|
|
|
|
+│ │ ID: 1 ││
|
|
|
|
|
+│ │ 昵称: 张三 [编辑] ││
|
|
|
|
|
+│ ├──────────────────────────────────┤│
|
|
|
|
|
+│ │ [修改密码] ││
|
|
|
|
|
+│ └──────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**ProfileEditView:**
|
|
|
|
|
+- 表单:昵称输入
|
|
|
|
|
+- `PUT /api/user` — 更新用户信息 → 更新 Pinia store
|
|
|
|
|
+
|
|
|
|
|
+**PasswordEditView:**
|
|
|
|
|
+- 表单:原密码、新密码、确认密码
|
|
|
|
|
+- `PUT /api/user/updatePassword` — 修改密码
|
|
|
|
|
+
|
|
|
|
|
+### 6.7 商家后台
|
|
|
|
|
+
|
|
|
|
|
+#### 6.7.1 订单审核 (AdminOrderListView + AdminOrderDetailView)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor A as Admin
|
|
|
|
|
+ participant V as AdminOrderListView
|
|
|
|
|
+ participant API as API Layer
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ Note over V: 页面加载
|
|
|
|
|
+ V->>API: getAdminAppointmentController().listAll({ pageNum, pageSize, status? })
|
|
|
|
|
+ API->>B: GET /api/admin/appointments?pageNum=1&pageSize=10
|
|
|
|
|
+ B-->>API: PageVo<AppointmentVo>
|
|
|
|
|
+ API-->>V: orders[]
|
|
|
|
|
+
|
|
|
|
|
+ Note over A,V: 审核操作
|
|
|
|
|
+ A->>V: 点击"审核"
|
|
|
|
|
+ V->>V: 打开审核弹窗
|
|
|
|
|
+ A->>V: 选择通过/拒绝 + 填写原因
|
|
|
|
|
+ V->>API: review(appointmentId, { action, rejectReason? })
|
|
|
|
|
+ API->>B: PUT /api/admin/appointments/{id}/review
|
|
|
|
|
+ B-->>API: success
|
|
|
|
|
+ API-->>V: ElMessage.success
|
|
|
|
|
+ V->>V: 刷新列表
|
|
|
|
|
+
|
|
|
|
|
+ Note over A,V: 处理取消申请
|
|
|
|
|
+ A->>V: 点击"处理取消"
|
|
|
|
|
+ V->>V: 打开取消处理弹窗
|
|
|
|
|
+ A->>V: 选择同意/拒绝
|
|
|
|
|
+ V->>API: handleCancel(appointmentId, { action })
|
|
|
|
|
+ API->>B: PUT /api/admin/appointments/{id}/handle-cancel
|
|
|
|
|
+ B-->>API: success
|
|
|
|
|
+ V->>V: 刷新列表
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**AdminOrderListView 组件树:**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ V["AdminOrderListView"]
|
|
|
|
|
+ V --> Filter["el-select 状态筛选"]
|
|
|
|
|
+ V --> Table["el-table 订单列表"]
|
|
|
|
|
+ Table --> Cols["ID | 用户 | 类型 | 总价 | 状态标签 | 操作"]
|
|
|
|
|
+ Table --> Pagination["el-pagination"]
|
|
|
|
|
+ V --> ReviewDlg["审核弹窗"]
|
|
|
|
|
+ ReviewDlg --> Radio["通过/拒绝 radio"]
|
|
|
|
|
+ ReviewDlg --> Reason["原因 textarea"]
|
|
|
|
|
+ V --> CancelDlg["取消处理弹窗"]
|
|
|
|
|
+ CancelDlg --> CancelRadio["同意/拒绝 radio"]
|
|
|
|
|
+ CancelDlg --> Opinion["处理意见 textarea"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**AdminOrderDetailView:** 与 AdminOrderListView 共享审核/取消弹窗逻辑,以详情页形式展示完整订单信息。
|
|
|
|
|
+
|
|
|
|
|
+#### 6.7.2 宠物房间管理 (AdminRoomCalendarView)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ A["商家进入宠物房间管理"]
|
|
|
|
|
+ A --> B["选择房型<br/>el-select 切换"]
|
|
|
|
|
+ A --> C["选择月份<br/>el-date-picker type=month"]
|
|
|
|
|
+ B --> D["RoomCalendar 宠物房间日历"]
|
|
|
|
|
+ C --> D
|
|
|
|
|
+ D --> E["点击某天"]
|
|
|
|
|
+ E --> F{"当前状态?"}
|
|
|
|
|
+ F -->|"AVAILABLE"| G["切换为 OCCUPIED<br/>PUT /api/admin/rooms/{id}/status"]
|
|
|
|
|
+ F -->|"OCCUPIED"| H["切换为 AVAILABLE<br/>PUT /api/admin/rooms/{id}/status"]
|
|
|
|
|
+ G --> I["刷新日历 + ElMessage.success"]
|
|
|
|
|
+ H --> I
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**布局:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────────────┐
|
|
|
|
|
+│ 宠物房间管理 │
|
|
|
|
|
+│ [房型选择 ▼] [2026年07月 ▼] │
|
|
|
|
|
+├──────────────────────────────────────────────┤
|
|
|
|
|
+│ ┌──────────────────────────────────────────┐│
|
|
|
|
|
+│ │ 宠物房间日历 ││
|
|
|
|
|
+│ │ 日 一 二 三 四 五 六 ││
|
|
|
|
|
+│ │ 1 2 3 4 ││
|
|
|
|
|
+│ │ 5 6 7 8 9 10 11 ││
|
|
|
|
|
+│ │ 12 13 14 15 16 17 18 ││
|
|
|
|
|
+│ │ ... ││
|
|
|
|
|
+│ │ 绿色=空闲 红色=占用 ││
|
|
|
|
|
+│ └──────────────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**CRITICAL: 使用"宠物房间管理" / "宠物房间日历" 术语:**
|
|
|
|
|
+- 页面标题为「宠物房间管理」
|
|
|
|
|
+- 日历组件使用「宠物房间日历」称谓
|
|
|
|
|
+- 菜单项 meta.title 为「房态管理」(但页面内统一使用"宠物房间"术语)
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/boarding-rooms` — 获取所有房型
|
|
|
|
|
+- `GET /api/boarding-rooms/{id}/status?month=YYYY-MM` — 获取房态
|
|
|
|
|
+- `PUT /api/admin/boarding-rooms/{roomId}/status` — 修改单日状态(切换 AVAILABLE/OCCUPIED)
|
|
|
|
|
+- `PUT /api/admin/boarding-rooms/{roomId}/status/batch` — 批量修改(预留,当前 UI 暂未实现多选)
|
|
|
|
|
+
|
|
|
|
|
+**状态切换逻辑(handleDateClick):**
|
|
|
|
|
+```typescript
|
|
|
|
|
+const handleDateClick = async (date: string, status: 'AVAILABLE' | 'OCCUPIED') => {
|
|
|
|
|
+ const newStatus = status === 'AVAILABLE' ? 'OCCUPIED' : 'AVAILABLE'
|
|
|
|
|
+ // PUT /api/admin/boarding-rooms/{roomId}/status { date, status: newStatus }
|
|
|
|
|
+ // 刷新日历
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 6.7.3 排班管理 (AdminScheduleListView)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────────────┐
|
|
|
|
|
+│ 排班管理 [+ 新增] │
|
|
|
|
|
+│ [2026年07月 ▼] │
|
|
|
|
|
+├──────────────────────────────────────────────┤
|
|
|
|
|
+│ ┌──────────────────────────────────────────┐│
|
|
|
|
|
+│ │ 设计师 | 日期 | 时段 | 状态 | 操作 ││
|
|
|
|
|
+│ │ 张三 | 07-01 | 上午 | 可用 | [删除] ││
|
|
|
|
|
+│ │ 李四 | 07-01 | 下午 | 可用 | [删除] ││
|
|
|
|
|
+│ │ ... ││
|
|
|
|
|
+│ └──────────────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**新增弹窗:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────┐
|
|
|
|
|
+│ 新增排班 │
|
|
|
|
|
+│ 设计师: [__________] │
|
|
|
|
|
+│ 日期: [date ▼] │
|
|
|
|
|
+│ 时段: [上午 ▼] │
|
|
|
|
|
+│ 是否可用: [🟢] │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ [取消] [保存] │
|
|
|
|
|
+└──────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/admin/schedules?month=YYYY-MM` — 获取排班列表
|
|
|
|
|
+- `POST /api/admin/schedules` — 新增排班
|
|
|
|
|
+- `DELETE /api/admin/schedules/{id}` — 删除排班
|
|
|
|
|
+
|
|
|
|
|
+#### 6.7.4 套餐配置 (AdminPackageListView + Create + Edit)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────────────┐
|
|
|
|
|
+│ 套餐配置 [+ 新增套餐] │
|
|
|
|
|
+├──────────────────────────────────────────────┤
|
|
|
|
|
+│ ┌──────────────────────────────────────────┐│
|
|
|
|
|
+│ │ 名称 | 类型 | 价格 | 天数 | 状态 | 操作 ││
|
|
|
|
|
+│ │ 标准寄养 | 寄养 | ¥100 | 7天 | 启用 | [编辑][删]││
|
|
|
|
|
+│ │ 基础洗护 | 洗护 | ¥80 | - | 启用 | [编辑][删]││
|
|
|
|
|
+│ └──────────────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**新增/编辑表单:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────┐
|
|
|
|
|
+│ 新增套餐 / 编辑套餐 │
|
|
|
|
|
+│ 套餐类型: ○ 寄养 ○ 洗护 │
|
|
|
|
|
+│ 套餐名称: [______________] │
|
|
|
|
|
+│ 价格: [0.00] │
|
|
|
|
|
+│ 描述: [______________] │
|
|
|
|
|
+│ 寄养天数: [1] (仅寄养显示) │
|
|
|
|
|
+│ 是否启用: [🟢] │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ [取消] [保存] │
|
|
|
|
|
+└──────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/packages` — 获取套餐列表
|
|
|
|
|
+- `GET /api/packages/{id}` — 获取单个套餐
|
|
|
|
|
+- `POST /api/admin/packages` — 新增套餐
|
|
|
|
|
+- `PUT /api/admin/packages/{id}` — 更新套餐
|
|
|
|
|
+- `DELETE /api/admin/packages/{id}` — 删除套餐(逻辑删除)
|
|
|
|
|
+
|
|
|
|
|
+#### 6.7.5 用户管理 (AdminUserListView + AdminUserDetailView)
|
|
|
|
|
+
|
|
|
|
|
+**AdminUserListView 复用 UserView.vue:**
|
|
|
|
|
+- 搜索区域:用户名、账号、角色、状态
|
|
|
|
|
+- el-table:ID、用户名、账号、角色标签、启用/禁用 switch、头像、操作(编辑/删除)
|
|
|
|
|
+- 新增/编辑弹窗:用户名、账号、密码、角色
|
|
|
|
|
+- 分页、批量删除、单条删除
|
|
|
|
|
+
|
|
|
|
|
+**AdminUserDetailView:**
|
|
|
|
|
+```
|
|
|
|
|
+┌──────────────────────────────────────────────┐
|
|
|
|
|
+│ ← 用户管理 │
|
|
|
|
|
+├──────────────────────────────────────────────┤
|
|
|
|
|
+│ 用户信息 │
|
|
|
|
|
+│ ┌──────────────────────────────────────────┐│
|
|
|
|
|
+│ │ 用户名: 张三 | 账号: zhangsan ││
|
|
|
|
|
+│ │ 角色: user | 邮箱: z@x.com ││
|
|
|
|
|
+│ │ 手机: 138xxxx | ││
|
|
|
|
|
+│ └──────────────────────────────────────────┘│
|
|
|
|
|
+├──────────────────────────────────────────────┤
|
|
|
|
|
+│ 该用户的宠物档案 │
|
|
|
|
|
+│ ┌──────────────────────────────────────────┐│
|
|
|
|
|
+│ │ 名称 | 品种 | 性别 | 体重 ││
|
|
|
|
|
+│ │ 旺财 | 金毛 | 公 | 25kg ││
|
|
|
|
|
+│ └──────────────────────────────────────────┘│
|
|
|
|
|
+└──────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**API 调用:**
|
|
|
|
|
+- `GET /api/admin/users/{id}` — 获取用户详情(含用户信息 + 宠物列表)
|
|
|
|
|
+- UserView 使用的 `GET /api/user` 分页查询
|
|
|
|
|
+
|
|
|
|
|
+## 7. 状态管理 (Pinia Store)
|
|
|
|
|
+
|
|
|
|
|
+### 7.1 useLoginUserStore (`store/user.ts`)
|
|
|
|
|
+
|
|
|
|
|
+| 分类 | 名称 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| **State** | `loginUser` | `Reactive<LoginUser>` | `{ isLogin: false, user: { id, name, role } }` |
|
|
|
|
|
+| **Getter** | `userId` | `ComputedRef<string>` | `loginUser.user?.id` |
|
|
|
|
|
+| **Action** | `updateUser(user: User)` | `void` | 替换 user 对象 |
|
|
|
|
|
+| **Action** | `loginSuccess(user: User)` | `void` | 设置 isLogin=true + 更新 user |
|
|
|
|
|
+| **Action** | `logoutUser()` | `void` | 重置为未登录状态 |
|
|
|
|
|
+
|
|
|
|
|
+**持久化:** `persist: true` — 通过 pinia-plugin-persistedstate 自动同步到 localStorage
|
|
|
|
|
+
|
|
|
|
|
+### 7.2 useMetaStore (`store/meta.ts`)
|
|
|
|
|
+
|
|
|
|
|
+| 分类 | 名称 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| **State** | `title` | `Ref<string>` | 网站标题(默认"标题") |
|
|
|
|
|
+| **State** | `logo` | `Ref<string>` | Logo 路径(默认 `/logo.svg`) |
|
|
|
|
|
+| **State** | `baseUrl` | `Ref<string>` | 后端地址(默认 `http://localhost:8080/`) |
|
|
|
|
|
+
|
|
|
|
|
+**持久化:** `persist: true`
|
|
|
|
|
+
|
|
|
|
|
+### 7.3 usePetStore (`store/pet.ts`)
|
|
|
|
|
+
|
|
|
|
|
+| 分类 | 名称 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| **State** | `pets` | `Ref<Pet[]>` | 宠物列表缓存 |
|
|
|
|
|
+| **Action** | `setPets(list: Pet[])` | `void` | 覆盖设置宠物列表 |
|
|
|
|
|
+| **Action** | `addPet(pet: Pet)` | `void` | 追加一只宠物 |
|
|
|
|
|
+| **Action** | `updatePet(updated: Pet)` | `void` | 按 id 更新宠物 |
|
|
|
|
|
+| **Action** | `removePet(id: string)` | `void` | 按 id 删除宠物 |
|
|
|
|
|
+
|
|
|
|
|
+### 7.4 useAppointmentStore (`store/appointment.ts`)
|
|
|
|
|
+
|
|
|
|
|
+| 分类 | 名称 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| **State** | `currentPackage` | `Ref<Package \| null>` | 当前选中套餐 |
|
|
|
|
|
+| **State** | `selectedPet` | `Ref<Pet \| null>` | 当前选中宠物 |
|
|
|
|
|
+| **State** | `selectedRoom` | `Ref<BoardingRoom \| null>` | 当前选中房型 |
|
|
|
|
|
+| **State** | `dateRange` | `Ref<[Date, Date] \| null>` | 入住/离店日期范围 |
|
|
|
|
|
+| **Action** | `reset()` | `void` | 重置所有状态为 null |
|
|
|
|
|
+
|
|
|
|
|
+> 注意:目前 BoardingBookingView 使用局部 ref 而非此 store 管理状态。
|
|
|
|
|
+
|
|
|
|
|
+## 8. API 层设计
|
|
|
|
|
+
|
|
|
|
|
+### 8.1 Axios 实例配置
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// util/axios-instance.ts
|
|
|
|
|
+const axiosInstance = axios.create({
|
|
|
|
|
+ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // Vite 代理前缀
|
|
|
|
|
+ timeout: 30000,
|
|
|
|
|
+ withCredentials: true, // 自动携带 JSESSIONID Cookie
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 响应拦截器
|
|
|
|
|
+axiosInstance.interceptors.response.use(
|
|
|
|
|
+ (response) => {
|
|
|
|
|
+ if (response.data.code !== 0 && response.data.code !== 200) {
|
|
|
|
|
+ return Promise.reject(new Error(response.data.message || '请求失败'))
|
|
|
|
|
+ }
|
|
|
|
|
+ return response.data // 解包,直接返回 { code, message, data }
|
|
|
|
|
+ },
|
|
|
|
|
+ (error) => {
|
|
|
|
|
+ if (error.response?.status === 401) {
|
|
|
|
|
+ window.location.href = '/login' // Session 过期 → 跳转登录
|
|
|
|
|
+ }
|
|
|
|
|
+ return Promise.reject(error)
|
|
|
|
|
+ }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// Orval 自定义实例
|
|
|
|
|
+export const customAxiosInstance = <T>(config: CustomInstanceConfig): Promise<T> => {
|
|
|
|
|
+ return axiosInstance(config)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 8.2 Orval 生成的控制器
|
|
|
|
|
+
|
|
|
|
|
+| 文件 | 工厂函数 | 主要方法 | 对应 API 端点 |
|
|
|
|
|
+|------|---------|---------|-------------|
|
|
|
|
|
+| `auth-controller.ts` | `getAuthController()` | `login`, `register`, `logout` | `POST /api/auth/login`, `POST /api/auth/register`, ... |
|
|
|
|
|
+| `user-controller.ts` | `getUserController()` | `queryByPageUsers`, `add`, `edit`, `deleteById`, `updateStatus`, `updatePassword`, `updateAvatar`, `queryById` | `GET/POST/PUT/DELETE /api/user/**` |
|
|
|
|
|
+| `pet-controller.ts` | `getPetController()` | `list`, `add1`, `getById`, `update`, `_delete`, `addVaccination`, `addDeworming` | `GET/POST/PUT/DELETE /api/pets/**` |
|
|
|
|
|
+| `appointment-controller.ts` | `getAppointmentController()` | `create`, `list1`, `getById2`, `cancel`, `payDeposit` | `POST/GET /api/appointments/**` |
|
|
|
|
|
+| `admin-appointment-controller.ts` | `getAdminAppointmentController()` | `listAll`, `review`, `handleCancel` | `GET/PUT /api/admin/appointments/**` |
|
|
|
|
|
+| `boarding-room-controller.ts` | `getBoardingRoomController()` | `listRooms`, `getStatus` | `GET /api/boarding-rooms/**` |
|
|
|
|
|
+| `admin-boarding-room-controller.ts` | `getAdminBoardingRoomController()` | `updateStatus1`, `batchUpdateStatus` | `PUT /api/admin/boarding-rooms/**` |
|
|
|
|
|
+| `package-controller.ts` | `getPackageController()` | `list4`, `getById1` | `GET /api/packages/**` |
|
|
|
|
|
+| `admin-package-controller.ts` | `getAdminPackageController()` | `create2`, `update1`, `delete1` | `POST/PUT/DELETE /api/admin/packages/**` |
|
|
|
|
|
+| `admin-schedule-controller.ts` | `getAdminScheduleController()` | `list2`, `create1`, `delete2` | `GET/POST/DELETE /api/admin/schedules/**` |
|
|
|
|
|
+| `admin-user-controller.ts` | `getAdminUserController()` | `listAllUsers`, `getDetail` | `GET /api/admin/users/**` |
|
|
|
|
|
+| `schedule-controller.ts` | `getScheduleController()` | (预留) | - |
|
|
|
|
|
+
|
|
|
|
|
+## 9. 关键数据流
|
|
|
|
|
+
|
|
|
|
|
+### 9.1 登录数据流
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor U as User
|
|
|
|
|
+ participant V as LoginView
|
|
|
|
|
+ participant S as useLoginUserStore
|
|
|
|
|
+ participant A as Axios Instance
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ U->>V: 输入账号密码
|
|
|
|
|
+ U->>V: 点击登录
|
|
|
|
|
+ V->>V: formRef.validate()
|
|
|
|
|
+ alt 校验失败
|
|
|
|
|
+ V->>U: 显示表单错误
|
|
|
|
|
+ else 校验通过
|
|
|
|
|
+ V->>A: authApi.login({ account, password })
|
|
|
|
|
+ A->>B: POST /api/auth/login<br/>withCredentials: true
|
|
|
|
|
+ B->>B: MD5 校验密码
|
|
|
|
|
+ B->>B: request.getSession().setAttribute("user", userInfo)
|
|
|
|
|
+ B-->>A: Set-Cookie: JSESSIONID=xxx<br/>Response<UserVo>
|
|
|
|
|
+ A-->>V: res.data (UserVo)
|
|
|
|
|
+ V->>S: loginSuccess({ id, name, role })
|
|
|
|
|
+ S->>S: loginUser.isLogin = true
|
|
|
|
|
+ S->>S: persist → localStorage
|
|
|
|
|
+ V->>V: router.push(redirect || '/booking')
|
|
|
|
|
+ V->>U: ElMessage.success('登录成功')
|
|
|
|
|
+ end
|
|
|
|
|
+
|
|
|
|
|
+ Note over U,B: 后续请求
|
|
|
|
|
+ U->>V: 访问需登录页面
|
|
|
|
|
+ V->>A: 任意 API 请求
|
|
|
|
|
+ A->>B: Cookie: JSESSIONID=xxx
|
|
|
|
|
+ B->>B: session.getAttribute("user")
|
|
|
|
|
+ B-->>A: 正常响应
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.2 预约数据流
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor U as User
|
|
|
|
|
+ participant V as BoardingBookingView
|
|
|
|
|
+ participant S as useAppointmentStore
|
|
|
|
|
+ participant A as API Layer
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ Note over V: Step 1 — 选择套餐
|
|
|
|
|
+ V->>A: GET /api/packages?type=BOARDING
|
|
|
|
|
+ A->>B: 查询套餐表
|
|
|
|
|
+ B-->>A: PackageVo[] (is_active=true)
|
|
|
|
|
+ A-->>V: 卡片列表渲染
|
|
|
|
|
+ U->>V: 点击选择套餐
|
|
|
|
|
+ V->>S: currentPackage = selected
|
|
|
|
|
+
|
|
|
|
|
+ Note over V: Step 2 — 选择房型与日期
|
|
|
|
|
+ V->>A: GET /api/boarding-rooms
|
|
|
|
|
+ A-->>V: BoardingRoomVo[]
|
|
|
|
|
+ U->>V: 选择房型
|
|
|
|
|
+ V->>A: GET /api/boarding-rooms/{id}/status?month=2026-07
|
|
|
|
|
+ A-->>V: RoomStatusVo[]
|
|
|
|
|
+ V->>V: 渲染 RoomCalendar 宠物房间日历
|
|
|
|
|
+ U->>V: 选择入住/离店日期
|
|
|
|
|
+ V->>V: 计算天数、总价
|
|
|
|
|
+ V->>S: selectedRoom, dateRange
|
|
|
|
|
+
|
|
|
|
|
+ Note over V: Step 3 — 选择宠物
|
|
|
|
|
+ V->>A: GET /api/pets
|
|
|
|
|
+ A-->>V: PetVo[]
|
|
|
|
|
+ V->>V: 渲染 PetSelector
|
|
|
|
|
+ U->>V: 选择宠物
|
|
|
|
|
+ V->>S: selectedPet
|
|
|
|
|
+
|
|
|
|
|
+ Note over V: Step 4 — 提交 + 支付
|
|
|
|
|
+ U->>V: 点击提交预约
|
|
|
|
|
+ V->>A: POST /api/appointments<br/>{ packageId, roomId, petId, startDate, endDate }
|
|
|
|
|
+ A->>B: 创建预约记录 (status=PENDING)
|
|
|
|
|
+ B-->>A: AppointmentVo { id, deposit, totalPrice }
|
|
|
|
|
+ A-->>V: createdOrderId, depositAmount
|
|
|
|
|
+ V->>V: 打开 PaymentDialog
|
|
|
|
|
+ U->>V: 选择支付方式
|
|
|
|
|
+ U->>V: 点击确认支付
|
|
|
|
|
+ V->>A: POST /api/appointments/{id}/pay-deposit
|
|
|
|
|
+ A->>B: 更新 depositPaid=true
|
|
|
|
|
+ B-->>A: success
|
|
|
|
|
+ V->>U: ElMessage.success('支付成功')
|
|
|
|
|
+ V->>V: router.push('/orders')
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.3 订单取消数据流
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor U as User
|
|
|
|
|
+ participant V as OrderDetailView
|
|
|
|
|
+ participant A as API Layer
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ alt PENDING 状态取消
|
|
|
|
|
+ U->>V: 点击"申请取消"
|
|
|
|
|
+ V->>V: ElMessageBox.confirm
|
|
|
|
|
+ U->>V: 确认 + 填写原因
|
|
|
|
|
+ V->>A: POST /api/appointments/{id}/cancel<br/>{ cancelReason }
|
|
|
|
|
+ A->>B: 校验状态为 PENDING
|
|
|
|
|
+ B->>B: status → CANCELLED
|
|
|
|
|
+ alt 已支付定金
|
|
|
|
|
+ B->>B: depositRefunded = true
|
|
|
|
|
+ end
|
|
|
|
|
+ B->>B: 释放房间状态 (若有)
|
|
|
|
|
+ B-->>A: success
|
|
|
|
|
+ A-->>V: ElMessage.success('取消成功')
|
|
|
|
|
+ V->>V: 刷新订单详情
|
|
|
|
|
+
|
|
|
|
|
+ else CONFIRMED 状态申请取消
|
|
|
|
|
+ U->>V: 点击"申请取消"
|
|
|
|
|
+ V->>A: POST /api/appointments/{id}/cancel
|
|
|
|
|
+ A->>B: 校验状态为 CONFIRMED
|
|
|
|
|
+ B->>B: status → CANCEL_REQUESTED
|
|
|
|
|
+ B-->>A: success
|
|
|
|
|
+ A-->>V: ElMessage.success('取消申请已提交')
|
|
|
|
|
+ end
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.4 后台审核数据流
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor A as Admin
|
|
|
|
|
+ participant V as AdminOrderListView
|
|
|
|
|
+ participant API as API Layer
|
|
|
|
|
+ participant B as Backend
|
|
|
|
|
+
|
|
|
|
|
+ Note over V: 页面加载
|
|
|
|
|
+ V->>API: GET /api/admin/appointments?pageNum=1&pageSize=10
|
|
|
|
|
+ API->>B: 分页查询所有预约
|
|
|
|
|
+ B-->>API: PageVo<AppointmentVo>
|
|
|
|
|
+ API-->>V: 表格渲染
|
|
|
|
|
+
|
|
|
|
|
+ Note over A,V: 审核通过
|
|
|
|
|
+ A->>V: 点击 PENDING 订单的"审核"
|
|
|
|
|
+ V->>V: 打开审核弹窗
|
|
|
|
|
+ A->>V: 选择"通过"
|
|
|
|
|
+ V->>API: PUT /api/admin/appointments/{id}/review<br/>{ action: 'CONFIRMED' }
|
|
|
|
|
+ API->>B: 校验订单
|
|
|
|
|
+ B->>B: status → CONFIRMED
|
|
|
|
|
+ B->>B: 批量插入 boarding_room_status (OCCUPIED)
|
|
|
|
|
+ B->>B: 关联 appointment_id
|
|
|
|
|
+ B-->>API: success
|
|
|
|
|
+ API-->>V: ElMessage.success
|
|
|
|
|
+ V->>V: 刷新列表
|
|
|
|
|
+
|
|
|
|
|
+ Note over A,V: 审核拒绝
|
|
|
|
|
+ A->>V: 选择"拒绝" + 填写原因
|
|
|
|
|
+ V->>API: PUT /api/admin/appointments/{id}/review<br/>{ action: 'CANCELLED', rejectReason }
|
|
|
|
|
+ API->>B: status → CANCELLED
|
|
|
|
|
+ B->>B: 保存 rejectReason
|
|
|
|
|
+ B-->>API: success
|
|
|
|
|
+
|
|
|
|
|
+ Note over A,V: 处理取消申请
|
|
|
|
|
+ A->>V: 点击 CANCEL_REQUESTED 的"处理取消"
|
|
|
|
|
+ V->>V: 打开处理弹窗
|
|
|
|
|
+ A->>V: 同意/拒绝
|
|
|
|
|
+ V->>API: PUT /api/admin/appointments/{id}/handle-cancel<br/>{ action: 'CANCELLED'|'CONFIRMED' }
|
|
|
|
|
+ API->>B: 更新 status + 释放房间
|
|
|
|
|
+ B-->>API: success
|
|
|
|
|
+ V->>V: 刷新列表
|
|
|
|
|
+```
|