| 技术 | 版本 | 用途 |
|---|---|---|
| Vue 3 | ^3.5.13 | 前端框架(Composition API + <script setup>) |
| Vite | ^6.3.5 | 构建工具 |
| TypeScript | ~5.8.3 | 类型安全 |
| Element Plus | ^2.13.6 | UI 组件库 |
| Pinia | ^3.0.4 | 状态管理 |
| Pinia Persistedstate | ^4.7.1 | Store 持久化 |
| Vue Router | ^4.6.4 | 路由管理 |
| Axios | ^1.15.2 | HTTP 请求 |
| Orval | 7.0 | OpenAPI → API 代码生成 |
| Prettier | ^3.8.3 | 代码格式化 |
ui/
├── index.html
├── package.json
├── vite.config.ts # Vite 配置(代理 /api → localhost:8080)
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── orval.config.ts # Orval 代码生成配置
├── .gitignore
├── public/
│ ├── logo.svg
│ └── vite.svg
├── src/
│ ├── main.ts # 入口文件
│ ├── App.vue # 根组件(<router-view />)
│ ├── style.css # 全局样式
│ ├── vite-env.d.ts
│ │
│ ├── type/
│ │ └── index.ts # 自定义类型定义
│ │ - User { id, name, avatar, role }
│ │ - LoginUser { isLogin, user? }
│ │
│ ├── util/
│ │ ├── index.ts # 工具函数(当前为空)
│ │ └── axios-instance.ts # Axios 实例 + 拦截器配置
│ │ - withCredentials: true(自动携带 Cookie)
│ │ - 响应拦截器:统一错误处理 + 401 → /login
│ │ - customAxiosInstance(供 Orval 使用)
│ │
│ ├── api/ # Orval 生成的 API 代码
│ │ ├── auth-controller.ts # 认证 API
│ │ ├── user-controller.ts # 用户管理 API
│ │ ├── pet-controller.ts # 宠物 API(待生成)
│ │ ├── appointment-controller.ts # 预约 API(待生成)
│ │ ├── boarding-room-controller.ts # 房态 API(待生成)
│ │ ├── package-controller.ts # 套餐 API(待生成)
│ │ ├── schedule-controller.ts # 排班 API(待生成)
│ │ └── models/ # 数据模型类型定义
│ │ ├── index.ts # 统一导出
│ │ ├── response.ts # 统一响应
│ │ ├── responseUserVo.ts
│ │ ├── responseBoolean.ts
│ │ ├── responsePageVoListUserVo.ts
│ │ ├── responseListFieldError.ts
│ │ ├── userVo.ts
│ │ ├── userDto.ts
│ │ ├── loginDto.ts
│ │ ├── registerDto.ts
│ │ ├── updateUserPasswordDto.ts
│ │ ├── updateUserAvatarDto.ts
│ │ ├── updateUserStatusDto.ts
│ │ ├── deleteByIdParams.ts
│ │ ├── queryByPageParams.ts
│ │ ├── pageVoListUserVo.ts
│ │ └── fieldError.ts
│ │
│ ├── store/ # Pinia 状态管理
│ │ ├── index.ts # 统一导出
│ │ ├── user.ts # useLoginUserStore
│ │ └── meta.ts # useMetaStore
│ │
│ ├── router/
│ │ └── index.ts # 路由配置 + 导航守卫
│ │
│ ├── layout/
│ │ ├── UserLayout.vue # 用户侧布局(水平 TopNav)
│ │ └── AdminLayout.vue # 商家后台侧布局(侧边栏 + 顶栏)
│ │
│ ├── view/ # 页面组件
│ │ ├── HomeView.vue # 首页
│ │ ├── LoginView.vue # 登录/注册(tab 切换)
│ │ ├── AboutView.vue # 关于页面
│ │ ├── UserView.vue # 用户管理(后台)
│ │ │
│ │ ├── booking/ # 服务预约模块
│ │ │ ├── BookingView.vue # 预约首页(选择服务类型)
│ │ │ ├── BoardingBookingView.vue # 寄养预约表单
│ │ │ └── GroomingBookingView.vue # 洗护预约表单
│ │ │
│ │ ├── order/ # 订单管理模块
│ │ │ ├── OrderListView.vue # 我的订单列表
│ │ │ └── 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 # 编辑套餐
│ │ ├── AdminUserListView.vue # 用户管理列表(复用的 UserView)
│ │ └── AdminUserDetailView.vue # 用户详情(含宠物档案)
│ │
│ └── components/ # 公共组件
│ ├── PaymentDialog.vue # 模拟支付弹窗
│ ├── RoomCalendar.vue # 房态日历组件
│ ├── PetSelector.vue # 宠物选择器
│ └── ...
// router/index.ts
const routes = [
// 公共路由
{
path: '/login',
name: 'login',
component: LoginView
},
// 用户侧路由(嵌套在 UserLayout 下)
{
path: '/',
component: UserLayout,
children: [
{ path: '', name: 'home', component: HomeView },
{ path: 'about', name: 'about', component: AboutView },
// 预约
{ path: 'booking', name: 'booking', component: BookingView },
{ path: 'booking/boarding', name: 'boardingBooking', component: BoardingBookingView },
{ path: 'booking/grooming', name: 'groomingBooking', component: GroomingBookingView },
// 订单
{ path: 'orders', name: 'orderList', component: OrderListView },
{ path: 'orders/:id', name: 'orderDetail', component: OrderDetailView },
// 宠物
{ path: 'pets', name: 'petList', component: PetListView },
{ path: 'pets/new', name: 'petCreate', component: PetCreateView },
{ path: 'pets/:id', name: 'petDetail', component: PetDetailView },
{ path: 'pets/:id/edit', name: 'petEdit', component: PetEditView },
// 个人中心
{ path: 'profile', name: 'profile', component: ProfileView },
{ path: 'profile/edit', name: 'profileEdit', component: ProfileEditView },
{ path: 'profile/password', name: 'passwordEdit', component: PasswordEditView },
]
},
// 商家后台路由(嵌套在 AdminLayout 下)
{
path: '/admin',
component: AdminLayout,
meta: { requiresAdmin: true },
children: [
{ path: '', name: 'adminDashboard', component: AdminDashboardView },
{ path: 'orders', name: 'adminOrderList', component: AdminOrderListView },
{ path: 'orders/:id', name: 'adminOrderDetail', component: AdminOrderDetailView },
{ path: 'rooms', name: 'adminRooms', component: AdminRoomCalendarView },
{ path: 'schedules', name: 'adminSchedules', component: AdminScheduleListView },
{ path: 'packages', name: 'adminPackageList', component: AdminPackageListView },
{ path: 'packages/new', name: 'adminPackageCreate', component: AdminPackageCreateView },
{ path: 'packages/:id/edit', name: 'adminPackageEdit', component: AdminPackageEditView },
{ path: 'users', name: 'adminUsers', component: AdminUserListView },
{ path: 'users/:id', name: 'adminUserDetail', component: AdminUserDetailView },
]
}
]
router.beforeEach((to, _from, next) => {
// 检查是否需要管理员权限
if (to.matched.some(record => record.meta.requiresAdmin)) {
const loginUserStore = useLoginUserStore()
if (!loginUserStore.loginUser.isLogin) {
// 未登录 → 跳转登录页,携带 redirect
ElMessage.warning('请先登录')
next({ path: '/login', query: { redirect: to.fullPath } })
} else if (loginUserStore.loginUser.user?.role !== 'admin') {
// 非管理员 → 跳转首页
ElMessage.error('无权访问')
next('/')
} else {
next() // 放行
}
} else {
next() // 无需权限 → 放行
}
})
| route | meta.title | meta.icon | meta.requiresAdmin |
|---|---|---|---|
| / | 首页 | HomeFilled | - |
| /about | 关于 | Menu | - |
| /admin/users | 用户管理 | User | true |
| /admin/other | 其他 | Menu | true |
其余路由的 meta 待补充完整。
┌─────────────────────────────────────────────────────────┐
│ [Logo] 标题 [首页] [关于] [后台管理] [登录] │ ← el-header(水平)
├─────────────────────────────────────────────────────────┤
│ │
│ <router-view /> │ ← el-main(内容区)
│ │
├─────────────────────────────────────────────────────────┤
│ Footer │ ← el-footer
└─────────────────────────────────────────────────────────┘
功能说明:
状态判断:
const login = computed(() => loginUserStore.loginUser.isLogin)
const isAdmin = computed(() => loginUserStore.loginUser.user?.role === 'admin')
┌──────────────────────────────────────────────────────────┐
│ [Logo] 标题 后台管理 [用户名 ▼] │ ← el-header
├──────────┬───────────────────────────────────────────────┤
│ 可折叠 │ │
│ 侧边栏 │ <router-view /> │ ← el-main
│ 导航菜单 │ │
│ │ │
└──────────┴───────────────────────────────────────────────┘
功能说明:
可折叠菜单:
const isCollapse = ref(false)
// el-menu 的 :collapse="isCollapse" 控制展开/收起
// store/user.ts
export const useLoginUserStore = defineStore('loginUser', () => {
// === State ===
const loginUser = reactive<LoginUser>({
isLogin: false,
user: {
id: '',
name: '未登录',
role: 'guest'
}
})
// === Getters ===
const userId = computed(() => loginUser.user?.id)
// === Actions ===
function updateUser(user: User) {
loginUser.user = { ...user }
}
function loginSuccess(user: User) {
updateUser(user)
loginUser.isLogin = true
}
function logoutUser() {
loginUser.isLogin = false
loginUser.user = { id: '', name: '未登录', role: 'guest' }
// 清除登录状态(服务端 session 通过 logout API 销毁)
// 前端仅清除本地 store 状态
}
return { loginUser, userId, updateUser, loginSuccess, logoutUser }
}, { persist: true }) // pinia-plugin-persistedstate
持久化存储:
pinia-plugin-persistedstate 自动持久化到 localStorage// store/meta.ts
export const useMetaStore = defineStore('meta', () => {
const title = ref('标题') // 网站标题
const logo = ref('/logo.svg') // 网站 Logo
const baseUrl = ref('http://localhost:8080/') // 后端地址
return { title, logo, baseUrl }
}, { persist: true })
| Store 名称 | 用途 | 状态字段 |
|---|---|---|
| useAppointmentStore | 预约流程状态 | currentPackage, selectedPet, selectedRoom, dateRange |
| usePetStore | 宠物列表缓存 | pets: PetVo[] |
// util/axios-instance.ts
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 30000,
withCredentials: true, // 跨域请求自动携带 Cookie(JSESSIONID)
headers: { 'Content-Type': 'application/json' }
})
// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
// 后端返回格式: { code, message, data }
if (response.data.code !== 0 && response.data.code !== 200) {
return Promise.reject(new Error(response.data.message || '请求失败'))
}
return response.data // 直接返回 data,不需要 .data.data
},
(error) => {
if (error.response?.status === 401) {
// Session 过期或未登录,跳转登录页
window.location.href = '/login'
}
return Promise.reject(error)
}
)
// Orval 使用的自定义实例
export const customAxiosInstance = <T>(config: CustomInstanceConfig): Promise<T> => {
return axiosInstance(config)
}
功能:
状态管理:
校验规则:
交互流程:
用户输入 → 点击登录/注册 → validate() → API 调用
→ 成功 → 更新 store(服务端已存入 Session) → 跳转
→ 失败 → ElMessage.error 显示错误信息
功能:
布局建议:
┌──────────────────────────────────────┐
│ 品牌 Banner + 宣传语 │
├────────────┬──────────┬──────────────┤
│ 寄养服务 │ 洗护服务 │ 更多服务 │ ← 功能卡片入口
├────────────┴──────────┴──────────────┤
│ 功能介绍 / 优势展示 │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 选择服务类型 │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 寄养服务 │ │ 洗护服务 │ │ ← 两大服务卡片
│ │ 🏠 │ │ ✂️ │ │
│ │ 查看详情 │ │ 查看详情 │ │
│ └──────────┘ └──────────┘ │
└──────────────────────────────────────┘
步骤流程(Stepper 组件):
Step 1: 选择寄养套餐
→ GET /api/packages?type=BOARDING
→ 卡片列表展示套餐(名称、价格、天数、描述)
→ 用户选择一个套餐
Step 2: 选择房型与日期
→ GET /api/boarding-rooms
→ 选择房型 → GET /api/boarding-rooms/{id}/status?month=YYYY-MM
→ 日历展示房态(绿色=空闲,红色=占用)
→ 用户选择入住/离店日期
→ 实时计算总价(每晚价格 × 天数)
Step 3: 选择宠物
→ GET /api/pets
→ 下拉选择或卡片选择已有的宠物
Step 4: 确认提交
→ 展示订单摘要:套餐、房型、日期、宠物、总价
→ 点击"提交预约"
→ POST /api/appointments
→ 成功 → 弹出 PaymentDialog(支付定金)
步骤流程:
Step 1: 选择洗护套餐
→ GET /api/packages?type=GROOMING
→ 卡片列表展示
Step 2: 选择预约时间
→ GET /api/admin/schedules?month=YYYY-MM
→ 日历展示设计师排班(可选的日期+时段)
→ 选择时间段
Step 3: 选择宠物
→ GET /api/pets
→ 选择宠物
Step 4: 确认提交
→ 提交预约 → 支付定金
┌──────────────────────────────────────┐
│ 我的预约 [状态筛选 Tab] │
├──────────────────────────────────────┤
│ ┌──────────────────────────────────┐│
│ │ 订单卡片: ││
│ │ 宠物名 | 套餐名 | 日期 ││
│ │ 状态标签 | 总价 ││
│ │ [申请取消] [查看详情] ││
│ └──────────────────────────────────┘│
│ ┌──────────────────────────────────┐│
│ │ 订单卡片 2... ││
│ └──────────────────────────────────┘│
│ ... 分页 ... │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ ← 返回 │
├──────────────────────────────────────┤
│ 订单编号:xxx │
│ 状态:[PENDING/CONFIRMED/CANCELLED] │
│ │
│ 服务类型:寄养/洗护 │
│ 套餐名称:xxx │
│ 宠物:xxx(品种/年龄) │
│ 房型:xxx(仅寄养) │
│ 日期:2026-06-01 ~ 2026-06-03 │
│ 总价:¥600 │
│ 定金:¥180(已支付/未支付) │
│ 创建时间:2026-06-01 10:00 │
│ │
│ [申请取消] [支付定金] [联系客服] │
└──────────────────────────────────────┘
条件按钮逻辑:
┌──────────────────────────────────────┐
│ 宠物档案 [+ 新增宠物] │
├──────────────────────────────────────┤
│ ┌──────────────────────────────────┐│
│ │ [头像] 宠物名 - 品种 ││
│ │ 年龄: 2岁 | 性别: 公 ││
│ │ [编辑] [删除] [查看详情] ││
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ ← 返回 [编辑] [删除] │
├──────────────────────────────────────┤
│ 基本信息 │
│ 名称: 旺财 | 品种: 金毛 │
│ 年龄: 2岁 | 体重: 25kg │
│ 性别: 公 | 备注: 活泼好动 │
├──────────────────────────────────────┤
│ 疫苗记录 [+ 新增] │
│ ┌──────────────────────────────────┐│
│ │ 狂犬疫苗 2026-01-01 2027-01-01 ││
│ │ 接种机构: 安心宠物医院 ││
│ └──────────────────────────────────┘│
├──────────────────────────────────────┤
│ 驱虫记录 [+ 新增] │
│ ┌──────────────────────────────────┐│
│ │ 体外驱虫 2026-03-15 2026-06-15 ││
│ │ 药物: 福来恩 ││
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 个人中心 │
│ │
│ [大头像] 用户名 │
│ 角色:普通用户 │
├──────────────────────────────────────┤
│ 账号:xxx [不可改] │
│ 昵称:xxx [编辑] │
│ 手机:138xxxxxxx [编辑] │
│ 邮箱:xxx@xxx.com [编辑] │
├──────────────────────────────────────┤
│ [修改密码] │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ ← 个人中心 │
├──────────────────────────────────────┤
│ 原密码:[input] │
│ 新密码:[input] │
│ 确认密码:[input] │
│ │
│ [确认修改] │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 订单审核 [状态筛选] [搜索] │
├──────────────────────────────────────┤
│ ┌──────────────────────────────────┐│
│ │ 表格: ││
│ │ ID | 用户 | 宠物 | 套餐 | 类型 ││
│ │ 日期 | 总价 | 定金 | 状态 | 操作 ││
│ │ 操作:[审核] [详情] ││
│ └──────────────────────────────────┘│
│ ... 分页 ... │
└──────────────────────────────────────┘
操作:
展示订单全部信息 + 审核操作按钮 + 订单状态变更历史。
┌──────────────────────────────────────┐
│ 房态管理 [房型选择] [月份选择]│
├──────────────────────────────────────┤
│ ┌──────────────────────────────────┐│
│ │ 日历视图: ││
│ │ 日 一 二 三 四 五 六 ││
│ │ 1 2 3 4 ││
│ │ 5 6 7 8 9 10 ││
│ │ ... ││
│ │ 点击日期切换: [空闲] [占用] ││
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
交互:
┌──────────────────────────────────────┐
│ 排班管理 [月份选择] [+ 新增] │
├──────────────────────────────────────┤
│ ┌──────────────────────────────────┐│
│ │ 表格: ││
│ │ 设计师 | 日期 | 时段 | 状态 | 操作 ││
│ │ 张三 | 06-01 | 上午 | 可用 | [删]│
│ │ 李四 | 06-01 | 全天 | 可用 | [删]│
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
新增弹窗:
┌──────────────────────────────────────┐
│ 套餐配置 [+ 新增套餐] │
├──────────────────────────────────────┤
│ ┌──────────────────────────────────┐│
│ │ 表格: ││
│ │ 名称 | 类型 | 价格 | 天数 | 状态 | 操 │
│ │ 标准寄养 | 寄养 | ¥100/天 | 1-7 | ✅ | [编辑][删]│
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
新增/编辑表单:
复用现有的 UserView.vue 组件,功能包括:
┌──────────────────────────────────────┐
│ ← 用户管理 │
├──────────────────────────────────────┤
│ 用户信息 │
│ [头像] 用户名 | 账号 | 角色 | 状态 │
│ 注册时间: 2026-01-01 │
├──────────────────────────────────────┤
│ 该用户的宠物档案 │
│ ┌──────────────────────────────────┐│
│ │ 宠物名 | 品种 | 年龄 | 操作 ││
│ │ 旺财 | 金毛 | 2岁 | [查看详情] ││
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
<!-- 用法 -->
<PaymentDialog v-model:visible="showPayment" :amount="deposit" :order-id="orderId"
@success="onPaymentSuccess" @close="onPaymentClose" />
功能:
<!-- 用法 -->
<RoomCalendar :room-id="roomId" :month="currentMonth" :status-list="statusList"
@date-click="handleDateClick" />
功能:
<!-- 用法 -->
<PetSelector :pet-list="pets" v-model:selected="selectedPet" />
功能:
// 调用认证 API
import { getAuthController } from '@/api/auth-controller'
const authApi = getAuthController()
const res = await authApi.login({ account, password })
// res 已经是解包后的数据(响应拦截器已处理)
// 登录成功后服务端已将用户信息存入 HttpSession
// 前端仅需更新 Pinia store
if (res.code === 200) {
loginUserStore.loginSuccess({
id: String(res.data.id),
name: res.data.username,
role: res.data.role
})
// 后续请求通过 Cookie(JSESSIONID)自动携带 Session
}
按模块拆分,每个模块一个文件:
| 文件 | 包含接口 |
|---|---|
| auth-controller.ts | register, login |
| user-controller.ts | queryByPage, add, edit, delete, updateStatus, updatePassword, updateAvatar, queryById |
| pet-controller.ts | list, add, getById, update, delete, getVaccinations, addVaccination, getDeworming, addDeworming |
| appointment-controller.ts | create, list, getById, cancel, payDeposit |
| admin-appointment-controller.ts | listAll, review, handleCancel |
| boarding-room-controller.ts | listRooms, getStatus |
| admin-boarding-room-controller.ts | updateStatus |
| package-controller.ts | listPackages, getPackageById |
| admin-package-controller.ts | createPackage, updatePackage, deletePackage |
| admin-schedule-controller.ts | listSchedules, createSchedule, deleteSchedule |
| admin-user-controller.ts | listUsers, getUserDetail |
| 变量 | 值 | 用途 |
|---|---|---|
| --el-color-primary | #e34889 | Element Plus 主色调 |
| --el-color-primary-light | #f06292 | 浅色变体 |
| --el-color-primary-dark | #c2185b | 深色变体 |
| 渐变 | linear-gradient(135deg, #db2777, #be185d) | 按钮/重点元素 |
linear-gradient(135deg, #fdf2f8, #fce7f3, #fbcfe8)rgba(219, 39, 119, 0.15)// vite.config.ts
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
注意:后端 context-path 也设为
/api,所以 Vite 代理会将/api/xxx转发到http://localhost:8080/xxx。当后端 context-path 与前端代理前缀都为/api时,rewrite 会剥离一个/api,导致请求变为http://localhost:8080/auth/login而非http://localhost:8080/api/auth/login。需确认后端实际路径。
| 命令 | 说明 |
|---|---|
| npm run dev | 启动开发服务器(HMR) |
| npm run build | 类型检查 + 生产构建 |
| npm run preview | 预览生产构建 |
| npm run api:gen | 从 OpenAPI 规范生成 API 代码 |
// orval.config.ts
export default defineConfig({
'your-api': {
input: {
target: 'http://localhost:8080/v3/api-docs', // OpenAPI JSON 地址
},
output: {
mode: 'tags', // 按标签拆分
client: 'axios',
target: './src/api/client.ts',
schemas: './src/api/models',
clean: true,
override: {
mutator: {
path: './src/util/axios-instance.ts',
name: 'customAxiosInstance',
},
},
},
hooks: {
afterAllFilesWrite: 'prettier --write',
},
},
})
| 模块 | 已实现 | 待实现 |
|---|---|---|
| 路由 | userRoutes + adminRoutes 基础结构 | 完整路由表(预约/订单/宠物/个人中心) |
| 布局 | UserLayout(顶部导航) + AdminLayout(侧边栏+顶栏) | 完善菜单项 |
| 登录 | LoginView(登录+注册 tab) | - |
| 用户管理 | UserView(CRUD + 搜索 + 分页 + 状态切换) | - |
| 状态管理 | useLoginUserStore + useMetaStore | - |
| API | auth-controller + user-controller | pet/appointment/boarding-room/package/schedule controllers |
| 导航守卫 | requiresAdmin 校验 | 普通页面登录校验 |
| 优先级 | 模块 | 说明 |
|---|---|---|
| P0 | 服务预约(寄养+洗护) | 核心业务 |
| P0 | 订单管理(用户侧+后台) | 核心业务 |
| P0 | 宠物档案 CRUD | 核心业务 |
| P1 | 房态管理(后台日历) | 商家必备 |
| P1 | 套餐配置(后台) | 商家必备 |
| P1 | 排班管理(后台) | 商家必备 |
| P2 | 个人中心 | 用户信息管理 |
| P2 | 商家用户详情 | 查看用户宠物 |
BookingView
└─→ 用户点击"寄养服务"
└─→ router.push('/booking/boarding')
└─→ BoardingBookingView (Step 1: 选择套餐)
├─→ API: GET /api/packages?type=BOARDING
├─→ 用户选择套餐 → 保存到局部状态
└─→ Step 2: 选择房型与日期
├─→ API: GET /api/boarding-rooms
├─→ RoomCalendar 组件展示房态
│ └─→ 用户选择日期
├─→ 实时计算总价
└─→ Step 3: 选择宠物
├─→ API: GET /api/pets
├─→ PetSelector 组件
└─→ Step 4: 确认提交
├─→ API: POST /api/appointments
└─→ PaymentDialog 弹窗
└─→ API: POST /api/appointments/{id}/pay-deposit
└─→ 成功 → router.push('/orders')
OrderListView
└─→ 用户点击"申请取消"
└─→ ElMessageBox.prompt("请输入取消原因")
└─→ 确认 → API: POST /api/appointments/{id}/cancel
└─→ 成功 → ElMessage.success → 刷新列表
AdminOrderListView
└─→ 管理员点击"审核"
└─→ 对话框:通过(默认)/ 拒绝 + 填写原因
└─→ 确认 → API: PUT /api/admin/appointments/{id}/review
└─→ 成功 → ElMessage.success → 刷新列表
// 当前已定义
export interface User {
id: string
name: string
avatar?: string
role: 'admin' | 'user' | 'guest'
}
export interface LoginUser {
isLogin: boolean
user?: User
}
注意: 后端 Java 实体类
Package已重命名为ServicePackage(与java.lang.Package冲突),dto/package/和vo/package/已重命名为servicePackage/(package为 Java 关键字)。但这些变更不影响 API 端点路径(仍为/api/packages/**)和 Orval 生成的 TypeScript 类型,前端无需调整。
// 宠物相关
export interface Pet {
id: string
name: string
breed: string
age: number
weight: number
gender: 'MALE' | 'FEMALE'
notes?: string
}
// 套餐相关
export interface Package {
id: string
type: 'BOARDING' | 'GROOMING'
name: string
price: number
description?: string
durationDays?: number
isActive: boolean
}
// 房型相关
export interface BoardingRoom {
id: string
name: string
capacity: number
pricePerNight: number
description?: string
}
// 房态相关
export interface RoomStatus {
date: string
status: 'AVAILABLE' | 'OCCUPIED'
}
// 预约订单相关
export interface Appointment {
id: string
userId: string
petId: string
packageId: string
roomId?: string
type: 'BOARDING' | 'GROOMING'
startDate?: string
endDate?: string
appointmentTime?: string
status: 'PENDING' | 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'CANCEL_REQUESTED'
deposit: number
depositPaid: boolean
totalPrice: number
cancelReason?: string
rejectReason?: string
createdAt: string
}
// 排班相关
export interface Schedule {
id: string
designerName: string
workDate: string
timeSlot: 'MORNING' | 'AFTERNOON' | 'FULL_DAY'
isAvailable: boolean
}