Bläddra i källkod

feat:完成基本的页面开发

yangyi 4 månader sedan
förälder
incheckning
275d5cb4ad
100 ändrade filer med 2933 tillägg och 595 borttagningar
  1. 6 0
      orval.config.ts
  2. 65 0
      src/api/admin-appointment-controller.ts
  3. 30 0
      src/api/admin-boarding-room-controller.ts
  4. 52 0
      src/api/admin-package-controller.ts
  5. 57 0
      src/api/admin-schedule-controller.ts
  6. 37 0
      src/api/admin-user-controller.ts
  7. 82 0
      src/api/appointment-controller.ts
  8. 15 7
      src/api/auth-controller.ts
  9. 41 0
      src/api/boarding-room-controller.ts
  10. 13 0
      src/api/models/adminUserDetailVo.ts
  11. 15 0
      src/api/models/appointmentDto.ts
  12. 28 0
      src/api/models/appointmentVo.ts
  13. 0 23
      src/api/models/authTokenVo.ts
  14. 15 0
      src/api/models/boardingRoomVo.ts
  15. 8 0
      src/api/models/cancelBody.ts
  16. 3 4
      src/api/models/deleteByIdParams.ts
  17. 15 0
      src/api/models/dewormingDto.ts
  18. 17 0
      src/api/models/dewormingVo.ts
  19. 0 18
      src/api/models/fieldError.ts
  20. 10 0
      src/api/models/getStatusParams.ts
  21. 11 0
      src/api/models/handleCancelDto.ts
  22. 50 13
      src/api/models/index.ts
  23. 10 0
      src/api/models/list2Params.ts
  24. 10 0
      src/api/models/list3Params.ts
  25. 10 0
      src/api/models/list4Params.ts
  26. 15 0
      src/api/models/list5Params.ts
  27. 13 0
      src/api/models/listAllParams.ts
  28. 4 14
      src/api/models/loginDto.ts
  29. 17 0
      src/api/models/packageDto.ts
  30. 16 0
      src/api/models/packageVo.ts
  31. 14 0
      src/api/models/pageVoAppointmentVo.ts
  32. 0 18
      src/api/models/pageVoListUserVo.ts
  33. 14 0
      src/api/models/pageVoUserVo.ts
  34. 19 0
      src/api/models/petDto.ts
  35. 24 0
      src/api/models/petVo.ts
  36. 0 16
      src/api/models/queryByPageParams.ts
  37. 15 0
      src/api/models/queryByPageUsersParams.ts
  38. 13 0
      src/api/models/rAdminUserDetailVo.ts
  39. 13 0
      src/api/models/rAppointmentVo.ts
  40. 12 0
      src/api/models/rBoolean.ts
  41. 13 0
      src/api/models/rDewormingVo.ts
  42. 13 0
      src/api/models/rListAppointmentVo.ts
  43. 13 0
      src/api/models/rListBoardingRoomVo.ts
  44. 13 0
      src/api/models/rListDewormingVo.ts
  45. 13 0
      src/api/models/rListPackageVo.ts
  46. 13 0
      src/api/models/rListPetVo.ts
  47. 13 0
      src/api/models/rListRoomStatusVo.ts
  48. 13 0
      src/api/models/rListScheduleVo.ts
  49. 13 0
      src/api/models/rListVaccinationVo.ts
  50. 13 0
      src/api/models/rPackageVo.ts
  51. 13 0
      src/api/models/rPageVoAppointmentVo.ts
  52. 13 0
      src/api/models/rPageVoUserVo.ts
  53. 13 0
      src/api/models/rPetVo.ts
  54. 13 0
      src/api/models/rScheduleVo.ts
  55. 13 0
      src/api/models/rUserVo.ts
  56. 13 0
      src/api/models/rVaccinationVo.ts
  57. 12 0
      src/api/models/rVoid.ts
  58. 2 9
      src/api/models/registerDto.ts
  59. 0 19
      src/api/models/response.ts
  60. 0 20
      src/api/models/responseAuthTokenVo.ts
  61. 0 19
      src/api/models/responseBoolean.ts
  62. 0 20
      src/api/models/responseListFieldError.ts
  63. 0 20
      src/api/models/responsePageVoListUserVo.ts
  64. 0 20
      src/api/models/responseUserVo.ts
  65. 12 0
      src/api/models/reviewDto.ts
  66. 12 0
      src/api/models/roomStatusDto.ts
  67. 14 0
      src/api/models/roomStatusVo.ts
  68. 15 0
      src/api/models/scheduleDto.ts
  69. 15 0
      src/api/models/scheduleVo.ts
  70. 4 16
      src/api/models/updateUserAvatarDto.ts
  71. 6 22
      src/api/models/updateUserPasswordDto.ts
  72. 3 17
      src/api/models/updateUserStatusDto.ts
  73. 11 32
      src/api/models/userDto.ts
  74. 5 13
      src/api/models/userVo.ts
  75. 15 0
      src/api/models/vaccinationDto.ts
  76. 17 0
      src/api/models/vaccinationVo.ts
  77. 37 0
      src/api/package-controller.ts
  78. 143 0
      src/api/pet-controller.ts
  79. 25 0
      src/api/schedule-controller.ts
  80. 52 32
      src/api/user-controller.ts
  81. 117 0
      src/components/PaymentDialog.vue
  82. 97 0
      src/components/PetSelector.vue
  83. 169 0
      src/components/RoomCalendar.vue
  84. 79 56
      src/layout/AdminLayout.vue
  85. 63 37
      src/layout/UserLayout.vue
  86. 1 0
      src/main.ts
  87. 184 35
      src/router/index.ts
  88. 25 0
      src/store/appointment.ts
  89. 4 2
      src/store/index.ts
  90. 34 0
      src/store/pet.ts
  91. 0 1
      src/store/user.ts
  92. 31 49
      src/style.css
  93. 68 7
      src/type/index.ts
  94. 0 14
      src/util/axios-instance.ts
  95. 7 8
      src/view/LoginView.vue
  96. 11 14
      src/view/UserView.vue
  97. 197 0
      src/view/admin/AdminOrderDetailView.vue
  98. 253 0
      src/view/admin/AdminOrderListView.vue
  99. 87 0
      src/view/admin/AdminPackageCreateView.vue
  100. 109 0
      src/view/admin/AdminPackageEditView.vue

+ 6 - 0
orval.config.ts

@@ -24,6 +24,12 @@ export default defineConfig({
 
             // 覆盖默认配置
             override: {
+                operations: {
+                    queryByPage: {
+                        operationName: () => 'queryByPageUsers',
+                    },
+                },
+
                 // 自定义 axios 实例
                 mutator: {
                     path: './src/util/axios-instance.ts',

+ 65 - 0
src/api/admin-appointment-controller.ts

@@ -0,0 +1,65 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type {
+  HandleCancelDto,
+  ListAllParams,
+  RBoolean,
+  RPageVoAppointmentVo,
+  ReviewDto,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAdminAppointmentController = () => {
+  /**
+   * @summary 审核预约
+   */
+  const review = (id: number, reviewDto: ReviewDto) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/admin/appointments/${id}/review`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: reviewDto,
+    });
+  };
+  /**
+   * @summary 处理取消申请
+   */
+  const handleCancel = (id: number, handleCancelDto: HandleCancelDto) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/admin/appointments/${id}/handle-cancel`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: handleCancelDto,
+    });
+  };
+  /**
+   * @summary 获取全部预约(管理员)
+   */
+  const listAll = (params?: ListAllParams) => {
+    return customAxiosInstance<RPageVoAppointmentVo>({
+      url: `/admin/appointments`,
+      method: "GET",
+      params,
+    });
+  };
+  return { review, handleCancel, listAll };
+};
+export type ReviewResult = NonNullable<
+  Awaited<
+    ReturnType<ReturnType<typeof getAdminAppointmentController>["review"]>
+  >
+>;
+export type HandleCancelResult = NonNullable<
+  Awaited<
+    ReturnType<ReturnType<typeof getAdminAppointmentController>["handleCancel"]>
+  >
+>;
+export type ListAllResult = NonNullable<
+  Awaited<
+    ReturnType<ReturnType<typeof getAdminAppointmentController>["listAll"]>
+  >
+>;

+ 30 - 0
src/api/admin-boarding-room-controller.ts

@@ -0,0 +1,30 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { RBoolean, RoomStatusDto } from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAdminBoardingRoomController = () => {
+  /**
+   * @summary 手动修改某日房态
+   */
+  const updateStatus1 = (roomId: number, roomStatusDto: RoomStatusDto) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/admin/boarding-rooms/${roomId}/status`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: roomStatusDto,
+    });
+  };
+  return { updateStatus1 };
+};
+export type UpdateStatus1Result = NonNullable<
+  Awaited<
+    ReturnType<
+      ReturnType<typeof getAdminBoardingRoomController>["updateStatus1"]
+    >
+  >
+>;

+ 52 - 0
src/api/admin-package-controller.ts

@@ -0,0 +1,52 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PackageDto, RBoolean, RPackageVo } from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAdminPackageController = () => {
+  /**
+   * @summary 修改套餐
+   */
+  const update1 = (id: number, packageDto: PackageDto) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/admin/packages/${id}`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: packageDto,
+    });
+  };
+  /**
+   * @summary 删除套餐
+   */
+  const delete1 = (id: number) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/admin/packages/${id}`,
+      method: "DELETE",
+    });
+  };
+  /**
+   * @summary 新增套餐
+   */
+  const create2 = (packageDto: PackageDto) => {
+    return customAxiosInstance<RPackageVo>({
+      url: `/admin/packages`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: packageDto,
+    });
+  };
+  return { update1, delete1, create2 };
+};
+export type Update1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminPackageController>["update1"]>>
+>;
+export type Delete1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminPackageController>["delete1"]>>
+>;
+export type Create2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminPackageController>["create2"]>>
+>;

+ 57 - 0
src/api/admin-schedule-controller.ts

@@ -0,0 +1,57 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type {
+  List2Params,
+  RBoolean,
+  RListScheduleVo,
+  RScheduleVo,
+  ScheduleDto,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAdminScheduleController = () => {
+  /**
+   * @summary 获取排班列表
+   */
+  const list2 = (params?: List2Params) => {
+    return customAxiosInstance<RListScheduleVo>({
+      url: `/admin/schedules`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 添加排班
+   */
+  const create1 = (scheduleDto: ScheduleDto) => {
+    return customAxiosInstance<RScheduleVo>({
+      url: `/admin/schedules`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: scheduleDto,
+    });
+  };
+  /**
+   * @summary 删除排班
+   */
+  const delete2 = (id: number) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/admin/schedules/${id}`,
+      method: "DELETE",
+    });
+  };
+  return { list2, create1, delete2 };
+};
+export type List2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminScheduleController>["list2"]>>
+>;
+export type Create1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminScheduleController>["create1"]>>
+>;
+export type Delete2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminScheduleController>["delete2"]>>
+>;

+ 37 - 0
src/api/admin-user-controller.ts

@@ -0,0 +1,37 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { List5Params, RAdminUserDetailVo, RPageVoUserVo } from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAdminUserController = () => {
+  /**
+   * @summary 获取注册用户列表
+   */
+  const list5 = (params?: List5Params) => {
+    return customAxiosInstance<RPageVoUserVo>({
+      url: `/admin/users`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 获取用户详情及宠物档案
+   */
+  const getDetail = (id: number) => {
+    return customAxiosInstance<RAdminUserDetailVo>({
+      url: `/admin/users/${id}`,
+      method: "GET",
+    });
+  };
+  return { list5, getDetail };
+};
+export type List5Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminUserController>["list5"]>>
+>;
+export type GetDetailResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAdminUserController>["getDetail"]>>
+>;

+ 82 - 0
src/api/appointment-controller.ts

@@ -0,0 +1,82 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type {
+  AppointmentDto,
+  CancelBody,
+  RAppointmentVo,
+  RBoolean,
+  RListAppointmentVo,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAppointmentController = () => {
+  /**
+   * @summary 获取我的预约列表
+   */
+  const list1 = () => {
+    return customAxiosInstance<RListAppointmentVo>({
+      url: `/appointments`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 提交预约
+   */
+  const create = (appointmentDto: AppointmentDto) => {
+    return customAxiosInstance<RAppointmentVo>({
+      url: `/appointments`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: appointmentDto,
+    });
+  };
+  /**
+   * @summary 模拟支付定金
+   */
+  const payDeposit = (id: number) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/appointments/${id}/pay-deposit`,
+      method: "POST",
+    });
+  };
+  /**
+   * @summary 申请取消
+   */
+  const cancel = (id: number, cancelBody: CancelBody) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/appointments/${id}/cancel`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: cancelBody,
+    });
+  };
+  /**
+   * @summary 获取预约详情
+   */
+  const getById2 = (id: number) => {
+    return customAxiosInstance<RAppointmentVo>({
+      url: `/appointments/${id}`,
+      method: "GET",
+    });
+  };
+  return { list1, create, payDeposit, cancel, getById2 };
+};
+export type List1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAppointmentController>["list1"]>>
+>;
+export type CreateResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAppointmentController>["create"]>>
+>;
+export type PayDepositResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAppointmentController>["payDeposit"]>>
+>;
+export type CancelResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAppointmentController>["cancel"]>>
+>;
+export type GetById2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAppointmentController>["getById2"]>>
+>;

+ 15 - 7
src/api/auth-controller.ts

@@ -1,11 +1,10 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
-import type { LoginDto, RegisterDto, ResponseAuthTokenVo } from "./models";
+import type { LoginDto, RUserVo, RVoid, RegisterDto } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
 
 export const getAuthController = () => {
@@ -13,29 +12,38 @@ export const getAuthController = () => {
    * @summary 用户注册
    */
   const register = (registerDto: RegisterDto) => {
-    return customAxiosInstance<ResponseAuthTokenVo>({
+    return customAxiosInstance<RUserVo>({
       url: `/auth/register`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
       data: registerDto,
     });
   };
+  /**
+   * @summary 退出登录
+   */
+  const logout = () => {
+    return customAxiosInstance<RVoid>({ url: `/auth/logout`, method: "POST" });
+  };
   /**
    * @summary 用户登录
    */
   const login = (loginDto: LoginDto) => {
-    return customAxiosInstance<ResponseAuthTokenVo>({
+    return customAxiosInstance<RUserVo>({
       url: `/auth/login`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
       data: loginDto,
     });
   };
-  return { register, login };
+  return { register, logout, login };
 };
 export type RegisterResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getAuthController>["register"]>>
 >;
+export type LogoutResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAuthController>["logout"]>>
+>;
 export type LoginResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getAuthController>["login"]>>
 >;

+ 41 - 0
src/api/boarding-room-controller.ts

@@ -0,0 +1,41 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type {
+  GetStatusParams,
+  RListBoardingRoomVo,
+  RListRoomStatusVo,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getBoardingRoomController = () => {
+  /**
+   * @summary 获取所有房型
+   */
+  const listRooms = () => {
+    return customAxiosInstance<RListBoardingRoomVo>({
+      url: `/boarding-rooms`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 获取指定房型某月房态
+   */
+  const getStatus = (id: number, params: GetStatusParams) => {
+    return customAxiosInstance<RListRoomStatusVo>({
+      url: `/boarding-rooms/${id}/status`,
+      method: "GET",
+      params,
+    });
+  };
+  return { listRooms, getStatus };
+};
+export type ListRoomsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getBoardingRoomController>["listRooms"]>>
+>;
+export type GetStatusResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getBoardingRoomController>["getStatus"]>>
+>;

+ 13 - 0
src/api/models/adminUserDetailVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PetVo } from "./petVo";
+import type { UserVo } from "./userVo";
+
+export interface AdminUserDetailVo {
+  pets?: PetVo[];
+  user?: UserVo;
+}

+ 15 - 0
src/api/models/appointmentDto.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface AppointmentDto {
+  endDate?: string;
+  packageId: number;
+  petId: number;
+  remark?: string;
+  roomId?: number;
+  startDate?: string;
+}

+ 28 - 0
src/api/models/appointmentVo.ts

@@ -0,0 +1,28 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface AppointmentVo {
+  cancelReason?: string;
+  createdAt?: string;
+  deposit?: number;
+  depositPaid?: boolean;
+  endDate?: string;
+  id?: number;
+  packageId?: number;
+  packageName?: string;
+  petId?: number;
+  petName?: string;
+  rejectReason?: string;
+  remark?: string;
+  roomId?: number;
+  roomName?: string;
+  startDate?: string;
+  status?: string;
+  totalPrice?: number;
+  type?: string;
+  userId?: number;
+}

+ 0 - 23
src/api/models/authTokenVo.ts

@@ -1,23 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-
-/**
- * 登录响应
- */
-export interface AuthTokenVo {
-  /** 账号 */
-  account?: string;
-  /** 角色 */
-  role?: string;
-  /** JWT token */
-  token?: string;
-  /** 用户ID */
-  userId?: number;
-  /** 用户名 */
-  username?: string;
-}

+ 15 - 0
src/api/models/boardingRoomVo.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface BoardingRoomVo {
+  capacity?: number;
+  description?: string;
+  id?: number;
+  pricePerDay?: number;
+  roomName?: string;
+  roomType?: string;
+}

+ 8 - 0
src/api/models/cancelBody.ts

@@ -0,0 +1,8 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type CancelBody = { [key: string]: string };

+ 3 - 4
src/api/models/deleteByIdParams.ts

@@ -1,11 +1,10 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
 export type DeleteByIdParams = {
-  ids: string[];
+  ids: string;
 };

+ 15 - 0
src/api/models/dewormingDto.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface DewormingDto {
+  dewormingDate: string;
+  /** @minLength 1 */
+  dewormingName: string;
+  doctor?: string;
+  hospital?: string;
+  remark?: string;
+}

+ 17 - 0
src/api/models/dewormingVo.ts

@@ -0,0 +1,17 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface DewormingVo {
+  createdAt?: string;
+  dewormingDate?: string;
+  dewormingName?: string;
+  doctor?: string;
+  hospital?: string;
+  id?: number;
+  petId?: number;
+  remark?: string;
+}

+ 0 - 18
src/api/models/fieldError.ts

@@ -1,18 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-
-export interface FieldError {
-  arguments?: unknown[];
-  bindingFailure?: boolean;
-  code?: string;
-  codes?: string[];
-  defaultMessage?: string;
-  field?: string;
-  objectName?: string;
-  rejectedValue?: unknown;
-}

+ 10 - 0
src/api/models/getStatusParams.ts

@@ -0,0 +1,10 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type GetStatusParams = {
+  month: string;
+};

+ 11 - 0
src/api/models/handleCancelDto.ts

@@ -0,0 +1,11 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface HandleCancelDto {
+  /** @minLength 1 */
+  action: string;
+}

+ 50 - 13
src/api/models/index.ts

@@ -1,26 +1,63 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-export * from "./authTokenVo";
+export * from "./adminUserDetailVo";
+export * from "./appointmentDto";
+export * from "./appointmentVo";
+export * from "./boardingRoomVo";
+export * from "./cancelBody";
 export * from "./deleteByIdParams";
-export * from "./fieldError";
+export * from "./dewormingDto";
+export * from "./dewormingVo";
+export * from "./getStatusParams";
+export * from "./handleCancelDto";
+export * from "./list2Params";
+export * from "./list3Params";
+export * from "./list4Params";
+export * from "./list5Params";
+export * from "./listAllParams";
 export * from "./loginDto";
-export * from "./pageVoListUserVo";
-export * from "./queryByPageParams";
+export * from "./packageDto";
+export * from "./packageVo";
+export * from "./pageVoAppointmentVo";
+export * from "./pageVoUserVo";
+export * from "./petDto";
+export * from "./petVo";
+export * from "./queryByPageUsersParams";
+export * from "./rAdminUserDetailVo";
+export * from "./rAppointmentVo";
+export * from "./rBoolean";
+export * from "./rDewormingVo";
+export * from "./rListAppointmentVo";
+export * from "./rListBoardingRoomVo";
+export * from "./rListDewormingVo";
+export * from "./rListPackageVo";
+export * from "./rListPetVo";
+export * from "./rListRoomStatusVo";
+export * from "./rListScheduleVo";
+export * from "./rListVaccinationVo";
+export * from "./rPackageVo";
+export * from "./rPageVoAppointmentVo";
+export * from "./rPageVoUserVo";
+export * from "./rPetVo";
+export * from "./rScheduleVo";
+export * from "./rUserVo";
+export * from "./rVaccinationVo";
+export * from "./rVoid";
 export * from "./registerDto";
-export * from "./response";
-export * from "./responseAuthTokenVo";
-export * from "./responseBoolean";
-export * from "./responseListFieldError";
-export * from "./responsePageVoListUserVo";
-export * from "./responseUserVo";
+export * from "./reviewDto";
+export * from "./roomStatusDto";
+export * from "./roomStatusVo";
+export * from "./scheduleDto";
+export * from "./scheduleVo";
 export * from "./updateUserAvatarDto";
 export * from "./updateUserPasswordDto";
 export * from "./updateUserStatusDto";
 export * from "./userDto";
 export * from "./userVo";
+export * from "./vaccinationDto";
+export * from "./vaccinationVo";

+ 10 - 0
src/api/models/list2Params.ts

@@ -0,0 +1,10 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type List2Params = {
+  month?: string;
+};

+ 10 - 0
src/api/models/list3Params.ts

@@ -0,0 +1,10 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type List3Params = {
+  month?: string;
+};

+ 10 - 0
src/api/models/list4Params.ts

@@ -0,0 +1,10 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type List4Params = {
+  type?: string;
+};

+ 15 - 0
src/api/models/list5Params.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type List5Params = {
+  pageNum?: number;
+  pageSize?: number;
+  username?: string;
+  account?: string;
+  role?: string;
+  enable?: number;
+};

+ 13 - 0
src/api/models/listAllParams.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type ListAllParams = {
+  pageNum?: number;
+  pageSize?: number;
+  status?: string;
+  type?: string;
+};

+ 4 - 14
src/api/models/loginDto.ts

@@ -1,23 +1,13 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 登录请求参数
- */
 export interface LoginDto {
-  /**
-   * 账号
-   * @minLength 1
-   */
+  /** @minLength 1 */
   account: string;
-  /**
-   * 密码
-   * @minLength 1
-   */
+  /** @minLength 1 */
   password: string;
 }

+ 17 - 0
src/api/models/packageDto.ts

@@ -0,0 +1,17 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface PackageDto {
+  description?: string;
+  duration?: number;
+  isActive?: number;
+  /** @minLength 1 */
+  name: string;
+  price: number;
+  /** @minLength 1 */
+  type: string;
+}

+ 16 - 0
src/api/models/packageVo.ts

@@ -0,0 +1,16 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface PackageVo {
+  createdAt?: string;
+  description?: string;
+  duration?: number;
+  id?: number;
+  name?: string;
+  price?: number;
+  type?: string;
+}

+ 14 - 0
src/api/models/pageVoAppointmentVo.ts

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { AppointmentVo } from "./appointmentVo";
+
+export interface PageVoAppointmentVo {
+  data?: AppointmentVo[];
+  pageNum?: number;
+  pageSize?: number;
+  total?: number;
+}

+ 0 - 18
src/api/models/pageVoListUserVo.ts

@@ -1,18 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-import type { UserVo } from "./userVo";
-
-/**
- * 分页数据对象
- */
-export interface PageVoListUserVo {
-  /** 当前页的数据 */
-  data?: UserVo[];
-  /** 总数据条数 */
-  total?: number;
-}

+ 14 - 0
src/api/models/pageVoUserVo.ts

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { UserVo } from "./userVo";
+
+export interface PageVoUserVo {
+  data?: UserVo[];
+  pageNum?: number;
+  pageSize?: number;
+  total?: number;
+}

+ 19 - 0
src/api/models/petDto.ts

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface PetDto {
+  avatar?: string;
+  birthDate?: string;
+  breed?: string;
+  gender?: string;
+  /** @minLength 1 */
+  name: string;
+  remark?: string;
+  /** @minLength 1 */
+  species: string;
+  weight?: number;
+}

+ 24 - 0
src/api/models/petVo.ts

@@ -0,0 +1,24 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { DewormingVo } from "./dewormingVo";
+import type { VaccinationVo } from "./vaccinationVo";
+
+export interface PetVo {
+  avatar?: string;
+  birthDate?: string;
+  breed?: string;
+  createdAt?: string;
+  dewormings?: DewormingVo[];
+  gender?: string;
+  id?: number;
+  name?: string;
+  remark?: string;
+  species?: string;
+  userId?: number;
+  vaccinations?: VaccinationVo[];
+  weight?: number;
+}

+ 0 - 16
src/api/models/queryByPageParams.ts

@@ -1,16 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-
-export type QueryByPageParams = {
-  account: string;
-  username: string;
-  role: string;
-  enable?: number;
-  pageNum?: number;
-  pageSize?: number;
-};

+ 15 - 0
src/api/models/queryByPageUsersParams.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export type QueryByPageUsersParams = {
+  pageNum?: number;
+  pageSize?: number;
+  username?: string;
+  account?: string;
+  role?: string;
+  enable?: number;
+};

+ 13 - 0
src/api/models/rAdminUserDetailVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { AdminUserDetailVo } from "./adminUserDetailVo";
+
+export interface RAdminUserDetailVo {
+  code?: number;
+  data?: AdminUserDetailVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rAppointmentVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { AppointmentVo } from "./appointmentVo";
+
+export interface RAppointmentVo {
+  code?: number;
+  data?: AppointmentVo;
+  message?: string;
+}

+ 12 - 0
src/api/models/rBoolean.ts

@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface RBoolean {
+  code?: number;
+  data?: boolean;
+  message?: string;
+}

+ 13 - 0
src/api/models/rDewormingVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { DewormingVo } from "./dewormingVo";
+
+export interface RDewormingVo {
+  code?: number;
+  data?: DewormingVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rListAppointmentVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { AppointmentVo } from "./appointmentVo";
+
+export interface RListAppointmentVo {
+  code?: number;
+  data?: AppointmentVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListBoardingRoomVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { BoardingRoomVo } from "./boardingRoomVo";
+
+export interface RListBoardingRoomVo {
+  code?: number;
+  data?: BoardingRoomVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListDewormingVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { DewormingVo } from "./dewormingVo";
+
+export interface RListDewormingVo {
+  code?: number;
+  data?: DewormingVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListPackageVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PackageVo } from "./packageVo";
+
+export interface RListPackageVo {
+  code?: number;
+  data?: PackageVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListPetVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PetVo } from "./petVo";
+
+export interface RListPetVo {
+  code?: number;
+  data?: PetVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListRoomStatusVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { RoomStatusVo } from "./roomStatusVo";
+
+export interface RListRoomStatusVo {
+  code?: number;
+  data?: RoomStatusVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListScheduleVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { ScheduleVo } from "./scheduleVo";
+
+export interface RListScheduleVo {
+  code?: number;
+  data?: ScheduleVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rListVaccinationVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { VaccinationVo } from "./vaccinationVo";
+
+export interface RListVaccinationVo {
+  code?: number;
+  data?: VaccinationVo[];
+  message?: string;
+}

+ 13 - 0
src/api/models/rPackageVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PackageVo } from "./packageVo";
+
+export interface RPackageVo {
+  code?: number;
+  data?: PackageVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rPageVoAppointmentVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PageVoAppointmentVo } from "./pageVoAppointmentVo";
+
+export interface RPageVoAppointmentVo {
+  code?: number;
+  data?: PageVoAppointmentVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rPageVoUserVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PageVoUserVo } from "./pageVoUserVo";
+
+export interface RPageVoUserVo {
+  code?: number;
+  data?: PageVoUserVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rPetVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { PetVo } from "./petVo";
+
+export interface RPetVo {
+  code?: number;
+  data?: PetVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rScheduleVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { ScheduleVo } from "./scheduleVo";
+
+export interface RScheduleVo {
+  code?: number;
+  data?: ScheduleVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rUserVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { UserVo } from "./userVo";
+
+export interface RUserVo {
+  code?: number;
+  data?: UserVo;
+  message?: string;
+}

+ 13 - 0
src/api/models/rVaccinationVo.ts

@@ -0,0 +1,13 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { VaccinationVo } from "./vaccinationVo";
+
+export interface RVaccinationVo {
+  code?: number;
+  data?: VaccinationVo;
+  message?: string;
+}

+ 12 - 0
src/api/models/rVoid.ts

@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface RVoid {
+  code?: number;
+  data?: unknown;
+  message?: string;
+}

+ 2 - 9
src/api/models/registerDto.ts

@@ -1,29 +1,22 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 注册请求参数
- */
 export interface RegisterDto {
   /**
-   * 账号
    * @minLength 4
    * @maxLength 20
    */
   account: string;
   /**
-   * 密码
    * @minLength 4
    * @maxLength 20
    */
   password: string;
   /**
-   * 用户名
    * @minLength 2
    * @maxLength 32
    */

+ 0 - 19
src/api/models/response.ts

@@ -1,19 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-
-/**
- * 后端统一的响应实体
- */
-export interface Response {
-  /** 状态码;200:成功 */
-  code?: number;
-  /** 响应的具体数据 */
-  data?: unknown;
-  /** 响应附加信息 */
-  message?: string;
-}

+ 0 - 20
src/api/models/responseAuthTokenVo.ts

@@ -1,20 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-import type { AuthTokenVo } from "./authTokenVo";
-
-/**
- * 后端统一的响应实体
- */
-export interface ResponseAuthTokenVo {
-  /** 状态码;200:成功 */
-  code?: number;
-  /** 响应的具体数据 */
-  data?: AuthTokenVo;
-  /** 响应附加信息 */
-  message?: string;
-}

+ 0 - 19
src/api/models/responseBoolean.ts

@@ -1,19 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-
-/**
- * 后端统一的响应实体
- */
-export interface ResponseBoolean {
-  /** 状态码;200:成功 */
-  code?: number;
-  /** 响应的具体数据 */
-  data?: boolean;
-  /** 响应附加信息 */
-  message?: string;
-}

+ 0 - 20
src/api/models/responseListFieldError.ts

@@ -1,20 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-import type { FieldError } from "./fieldError";
-
-/**
- * 后端统一的响应实体
- */
-export interface ResponseListFieldError {
-  /** 状态码;200:成功 */
-  code?: number;
-  /** 响应的具体数据 */
-  data?: FieldError[];
-  /** 响应附加信息 */
-  message?: string;
-}

+ 0 - 20
src/api/models/responsePageVoListUserVo.ts

@@ -1,20 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-import type { PageVoListUserVo } from "./pageVoListUserVo";
-
-/**
- * 后端统一的响应实体
- */
-export interface ResponsePageVoListUserVo {
-  /** 状态码;200:成功 */
-  code?: number;
-  /** 响应的具体数据 */
-  data?: PageVoListUserVo;
-  /** 响应附加信息 */
-  message?: string;
-}

+ 0 - 20
src/api/models/responseUserVo.ts

@@ -1,20 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-import type { UserVo } from "./userVo";
-
-/**
- * 后端统一的响应实体
- */
-export interface ResponseUserVo {
-  /** 状态码;200:成功 */
-  code?: number;
-  /** 响应的具体数据 */
-  data?: UserVo;
-  /** 响应附加信息 */
-  message?: string;
-}

+ 12 - 0
src/api/models/reviewDto.ts

@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface ReviewDto {
+  /** @minLength 1 */
+  action: string;
+  rejectReason?: string;
+}

+ 12 - 0
src/api/models/roomStatusDto.ts

@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface RoomStatusDto {
+  date: string;
+  /** @minLength 1 */
+  status: string;
+}

+ 14 - 0
src/api/models/roomStatusVo.ts

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface RoomStatusVo {
+  appointmentId?: number;
+  date?: string;
+  id?: number;
+  roomId?: number;
+  status?: string;
+}

+ 15 - 0
src/api/models/scheduleDto.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface ScheduleDto {
+  /** @minLength 1 */
+  employeeName: string;
+  remark?: string;
+  /** @minLength 1 */
+  timeSlot: string;
+  workDate: string;
+}

+ 15 - 0
src/api/models/scheduleVo.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface ScheduleVo {
+  createdAt?: string;
+  employeeName?: string;
+  id?: number;
+  remark?: string;
+  timeSlot?: string;
+  workDate?: string;
+}

+ 4 - 16
src/api/models/updateUserAvatarDto.ts

@@ -1,24 +1,12 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 更新用户头像的实体DTO
- */
 export interface UpdateUserAvatarDto {
-  /**
-   * 头像地址的链接
-   * @minLength 1
-   * @pattern ^(https?)://[^\s/$.?#].[^\s]*$
-   */
+  /** @minLength 1 */
   avatar: string;
-  /**
-   * 用户的ID
-   * @minLength 1
-   */
-  id: string;
+  id?: number;
 }

+ 6 - 22
src/api/models/updateUserPasswordDto.ts

@@ -1,30 +1,14 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 用户密码修改使用的DTO
- */
 export interface UpdateUserPasswordDto {
-  /**
-   * 用户ID
-   * @minLength 1
-   */
-  id: string;
-  /**
-   * 用户的新密码
-   * @minLength 4
-   * @maxLength 20
-   */
+  id?: number;
+  /** @minLength 1 */
+  newPassword: string;
+  /** @minLength 1 */
   oldPassword: string;
-  /**
-   * 用户的当前密码
-   * @minLength 4
-   * @maxLength 20
-   */
-  password: string;
 }

+ 3 - 17
src/api/models/updateUserStatusDto.ts

@@ -1,25 +1,11 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 用户状态更新使用的DTO
- */
 export interface UpdateUserStatusDto {
-  /**
-   * 更新后的状态;1:启用,0:禁用
-   * @minLength 1
-   * @minimum 0
-   * @maximum 1
-   */
   enable: number;
-  /**
-   * 用户ID
-   * @minLength 1
-   */
-  id: string;
+  id?: number;
 }

+ 11 - 32
src/api/models/userDto.ts

@@ -1,39 +1,18 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 用户新增和修改使用的DTO
- */
 export interface UserDto {
-  /**
-   * 用户账号
-   * @minLength 4
-   * @maxLength 20
-   */
-  account: string;
-  /** 用户ID */
-  id?: string;
-  /**
-   * 用户密码
-   * @minLength 4
-   * @maxLength 20
-   */
-  password: string;
-  /**
-   * 用户角色
-   * @minLength 4
-   * @maxLength 32
-   */
-  role: string;
-  /**
-   * 用户名称
-   * @minLength 2
-   * @maxLength 32
-   */
-  username: string;
+  account?: string;
+  avatar?: string;
+  email?: string;
+  enable?: number;
+  id?: number;
+  password?: string;
+  phone?: string;
+  role?: string;
+  username?: string;
 }

+ 5 - 13
src/api/models/userVo.ts

@@ -1,25 +1,17 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 
-/**
- * 用户响应展示对象
- */
 export interface UserVo {
-  /** 用户账号 */
   account?: string;
-  /** 用户头像地址 */
   avatar?: string;
-  /** 用户状态;0:禁用,1:启用 */
+  email?: string;
   enable?: number;
-  /** 用户ID */
-  id?: string;
-  /** 用户角色 */
+  id?: number;
+  phone?: string;
   role?: string;
-  /** 用户名称 */
   username?: string;
 }

+ 15 - 0
src/api/models/vaccinationDto.ts

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface VaccinationDto {
+  doctor?: string;
+  hospital?: string;
+  remark?: string;
+  vaccineDate: string;
+  /** @minLength 1 */
+  vaccineName: string;
+}

+ 17 - 0
src/api/models/vaccinationVo.ts

@@ -0,0 +1,17 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+
+export interface VaccinationVo {
+  createdAt?: string;
+  doctor?: string;
+  hospital?: string;
+  id?: number;
+  petId?: number;
+  remark?: string;
+  vaccineDate?: string;
+  vaccineName?: string;
+}

+ 37 - 0
src/api/package-controller.ts

@@ -0,0 +1,37 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { List4Params, RListPackageVo, RPackageVo } from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getPackageController = () => {
+  /**
+   * @summary 获取套餐列表
+   */
+  const list4 = (params?: List4Params) => {
+    return customAxiosInstance<RListPackageVo>({
+      url: `/packages`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 获取套餐详情
+   */
+  const getById1 = (id: number) => {
+    return customAxiosInstance<RPackageVo>({
+      url: `/packages/${id}`,
+      method: "GET",
+    });
+  };
+  return { list4, getById1 };
+};
+export type List4Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPackageController>["list4"]>>
+>;
+export type GetById1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPackageController>["getById1"]>>
+>;

+ 143 - 0
src/api/pet-controller.ts

@@ -0,0 +1,143 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type {
+  DewormingDto,
+  PetDto,
+  RBoolean,
+  RDewormingVo,
+  RListDewormingVo,
+  RListPetVo,
+  RListVaccinationVo,
+  RPetVo,
+  RVaccinationVo,
+  VaccinationDto,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getPetController = () => {
+  /**
+   * @summary 获取宠物详情
+   */
+  const getById = (id: number) => {
+    return customAxiosInstance<RPetVo>({ url: `/pets/${id}`, method: "GET" });
+  };
+  /**
+   * @summary 修改宠物信息
+   */
+  const update = (id: number, petDto: PetDto) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/pets/${id}`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: petDto,
+    });
+  };
+  /**
+   * @summary 删除宠物
+   */
+  const _delete = (id: number) => {
+    return customAxiosInstance<RBoolean>({
+      url: `/pets/${id}`,
+      method: "DELETE",
+    });
+  };
+  /**
+   * @summary 获取当前用户宠物列表
+   */
+  const list = () => {
+    return customAxiosInstance<RListPetVo>({ url: `/pets`, method: "GET" });
+  };
+  /**
+   * @summary 新增宠物
+   */
+  const add1 = (petDto: PetDto) => {
+    return customAxiosInstance<RPetVo>({
+      url: `/pets`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: petDto,
+    });
+  };
+  /**
+   * @summary 获取疫苗记录
+   */
+  const getVaccinations = (id: number) => {
+    return customAxiosInstance<RListVaccinationVo>({
+      url: `/pets/${id}/vaccinations`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 新增疫苗记录
+   */
+  const addVaccination = (id: number, vaccinationDto: VaccinationDto) => {
+    return customAxiosInstance<RVaccinationVo>({
+      url: `/pets/${id}/vaccinations`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: vaccinationDto,
+    });
+  };
+  /**
+   * @summary 获取驱虫记录
+   */
+  const getDeworming = (id: number) => {
+    return customAxiosInstance<RListDewormingVo>({
+      url: `/pets/${id}/deworming`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 新增驱虫记录
+   */
+  const addDeworming = (id: number, dewormingDto: DewormingDto) => {
+    return customAxiosInstance<RDewormingVo>({
+      url: `/pets/${id}/deworming`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: dewormingDto,
+    });
+  };
+  return {
+    getById,
+    update,
+    _delete,
+    list,
+    add1,
+    getVaccinations,
+    addVaccination,
+    getDeworming,
+    addDeworming,
+  };
+};
+export type GetByIdResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["getById"]>>
+>;
+export type UpdateResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["update"]>>
+>;
+export type _DeleteResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["_delete"]>>
+>;
+export type ListResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["list"]>>
+>;
+export type Add1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["add1"]>>
+>;
+export type GetVaccinationsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["getVaccinations"]>>
+>;
+export type AddVaccinationResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["addVaccination"]>>
+>;
+export type GetDewormingResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["getDeworming"]>>
+>;
+export type AddDewormingResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPetController>["addDeworming"]>>
+>;

+ 25 - 0
src/api/schedule-controller.ts

@@ -0,0 +1,25 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * OpenAPI definition
+ * OpenAPI spec version: v0
+ */
+import type { List3Params, RListScheduleVo } from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getScheduleController = () => {
+  /**
+   * @summary 获取排班列表
+   */
+  const list3 = (params?: List3Params) => {
+    return customAxiosInstance<RListScheduleVo>({
+      url: `/schedules`,
+      method: "GET",
+      params,
+    });
+  };
+  return { list3 };
+};
+export type List3Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getScheduleController>["list3"]>>
+>;

+ 52 - 32
src/api/user-controller.ts

@@ -1,17 +1,16 @@
 /**
  * Generated by orval v7.0.1 🍺
  * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
+ * OpenAPI definition
+ * OpenAPI spec version: v0
  */
 import type {
   DeleteByIdParams,
-  QueryByPageParams,
-  Response,
-  ResponseBoolean,
-  ResponsePageVoListUserVo,
-  ResponseUserVo,
+  QueryByPageUsersParams,
+  RBoolean,
+  RPageVoUserVo,
+  RUserVo,
+  RVoid,
   UpdateUserAvatarDto,
   UpdateUserPasswordDto,
   UpdateUserStatusDto,
@@ -20,79 +19,100 @@ import type {
 import { customAxiosInstance } from "../util/axios-instance";
 
 export const getUserController = () => {
-  const queryByPage = (params: QueryByPageParams) => {
-    return customAxiosInstance<ResponsePageVoListUserVo>({
+  /**
+   * @summary 分页查询用户列表
+   */
+  const queryByPageUsers = (params?: QueryByPageUsersParams) => {
+    return customAxiosInstance<RPageVoUserVo>({
       url: `/user`,
       method: "GET",
       params,
     });
   };
+  /**
+   * @summary 修改用户
+   */
   const edit = (userDto: UserDto) => {
-    return customAxiosInstance<ResponseBoolean>({
+    return customAxiosInstance<RBoolean>({
       url: `/user`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
       data: userDto,
     });
   };
+  /**
+   * @summary 新增用户
+   */
   const add = (userDto: UserDto) => {
-    return customAxiosInstance<ResponseBoolean>({
+    return customAxiosInstance<RBoolean>({
       url: `/user`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
       data: userDto,
     });
   };
+  /**
+   * @summary 删除用户
+   */
   const deleteById = (params: DeleteByIdParams) => {
-    return customAxiosInstance<ResponseBoolean>({
+    return customAxiosInstance<RBoolean>({
       url: `/user`,
       method: "DELETE",
       params,
     });
   };
-  const updateUserStatus = (updateUserStatusDto: UpdateUserStatusDto) => {
-    return customAxiosInstance<Response>({
+  /**
+   * @summary 启用/禁用用户
+   */
+  const updateStatus = (updateUserStatusDto: UpdateUserStatusDto) => {
+    return customAxiosInstance<RVoid>({
       url: `/user/updateStatus`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
       data: updateUserStatusDto,
     });
   };
+  /**
+   * @summary 修改密码
+   */
   const updatePassword = (updateUserPasswordDto: UpdateUserPasswordDto) => {
-    return customAxiosInstance<Response>({
+    return customAxiosInstance<RVoid>({
       url: `/user/updatePassword`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
       data: updateUserPasswordDto,
     });
   };
-  const updateUserAvatar = (updateUserAvatarDto: UpdateUserAvatarDto) => {
-    return customAxiosInstance<Response>({
+  /**
+   * @summary 修改头像
+   */
+  const updateAvatar = (updateUserAvatarDto: UpdateUserAvatarDto) => {
+    return customAxiosInstance<RVoid>({
       url: `/user/updateAvatar`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
       data: updateUserAvatarDto,
     });
   };
-  const queryById = (id: string) => {
-    return customAxiosInstance<ResponseUserVo>({
-      url: `/user/${id}`,
-      method: "GET",
-    });
+  /**
+   * @summary 按ID查询用户
+   */
+  const queryById = (id: number) => {
+    return customAxiosInstance<RUserVo>({ url: `/user/${id}`, method: "GET" });
   };
   return {
-    queryByPage,
+    queryByPageUsers,
     edit,
     add,
     deleteById,
-    updateUserStatus,
+    updateStatus,
     updatePassword,
-    updateUserAvatar,
+    updateAvatar,
     queryById,
   };
 };
-export type QueryByPageResult = NonNullable<
-  Awaited<ReturnType<ReturnType<typeof getUserController>["queryByPage"]>>
+export type QueryByPageUsersResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getUserController>["queryByPageUsers"]>>
 >;
 export type EditResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["edit"]>>
@@ -103,14 +123,14 @@ export type AddResult = NonNullable<
 export type DeleteByIdResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["deleteById"]>>
 >;
-export type UpdateUserStatusResult = NonNullable<
-  Awaited<ReturnType<ReturnType<typeof getUserController>["updateUserStatus"]>>
+export type UpdateStatusResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getUserController>["updateStatus"]>>
 >;
 export type UpdatePasswordResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["updatePassword"]>>
 >;
-export type UpdateUserAvatarResult = NonNullable<
-  Awaited<ReturnType<ReturnType<typeof getUserController>["updateUserAvatar"]>>
+export type UpdateAvatarResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getUserController>["updateAvatar"]>>
 >;
 export type QueryByIdResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["queryById"]>>

+ 117 - 0
src/components/PaymentDialog.vue

@@ -0,0 +1,117 @@
+<template>
+  <el-dialog v-model="visible" title="支付定金" width="420px" :close-on-click-modal="false">
+    <div class="payment-content">
+      <div class="amount-section">
+        <p class="label">需支付定金</p>
+        <p class="amount">¥{{ amount }}</p>
+      </div>
+
+      <div class="payment-methods">
+        <p class="label">选择支付方式</p>
+        <el-radio-group v-model="paymentMethod" class="method-group">
+          <el-radio value="wechat" class="method-item">
+            <span>微信支付</span>
+          </el-radio>
+          <el-radio value="alipay" class="method-item">
+            <span>支付宝</span>
+          </el-radio>
+        </el-radio-group>
+      </div>
+    </div>
+
+    <template #footer>
+      <el-button @click="handleClose">取消</el-button>
+      <el-button type="primary" :loading="paying" @click="handlePay">
+        {{ paying ? '支付中...' : '确认支付' }}
+      </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps<{
+  visible: boolean
+  amount: number
+  orderId: string
+}>()
+
+const emit = defineEmits<{
+  'update:visible': [value: boolean]
+  success: []
+  close: []
+}>()
+
+const visible = ref(props.visible)
+const paying = ref(false)
+const paymentMethod = ref('wechat')
+
+const handlePay = async () => {
+  paying.value = true
+  try {
+    const { getAppointmentController } = await import('@/api/appointment-controller')
+    const api = getAppointmentController()
+    const res = await api.payDeposit(Number(props.orderId))
+    if (res.code === 200) {
+      ElMessage.success('支付成功')
+      emit('success')
+      visible.value = false
+      emit('update:visible', false)
+    } else {
+      ElMessage.error(res.message || '支付失败')
+    }
+  } catch {
+    ElMessage.error('支付失败')
+  } finally {
+    paying.value = false
+  }
+}
+
+const handleClose = () => {
+  visible.value = false
+  emit('update:visible', false)
+  emit('close')
+}
+</script>
+
+<style scoped>
+.payment-content {
+  text-align: center;
+}
+
+.amount-section {
+  margin-bottom: 24px;
+}
+
+.label {
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 8px;
+}
+
+.amount {
+  font-size: 36px;
+  font-weight: bold;
+  color: var(--el-color-primary);
+  margin: 0;
+}
+
+.payment-methods {
+  text-align: left;
+}
+
+.method-group {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: 12px;
+}
+
+.method-item {
+  padding: 12px 16px;
+  border: 1px solid #e0e0e0;
+  border-radius: 8px;
+}
+</style>

+ 97 - 0
src/components/PetSelector.vue

@@ -0,0 +1,97 @@
+<template>
+  <div class="pet-selector">
+    <div v-if="petList.length === 0" class="empty">
+      <el-empty description="暂无宠物,请先添加" />
+      <el-button type="primary" @click="goAddPet">新增宠物</el-button>
+    </div>
+
+    <div v-else class="pet-cards">
+      <el-card
+        v-for="pet in petList"
+        :key="pet.id"
+        :class="['pet-card', { selected: selected?.id === pet.id }]"
+        @click="handleSelect(pet)"
+      >
+        <div class="pet-info">
+          <el-avatar>{{ pet.name[0] }}</el-avatar>
+          <div>
+            <strong>{{ pet.name }}</strong>
+            <p>{{ pet.breed }} · {{ pet.age }}岁</p>
+          </div>
+          <el-icon v-if="selected?.id === pet.id" class="check-icon">
+            <Check />
+          </el-icon>
+        </div>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { Check } from '@element-plus/icons-vue'
+import { useRouter } from 'vue-router'
+import type { Pet } from '@/type'
+
+const router = useRouter()
+
+defineProps<{
+  petList: Pet[]
+  selected: Pet | null
+}>()
+
+const emit = defineEmits<{
+  'update:selected': [pet: Pet]
+}>()
+
+const handleSelect = (pet: Pet) => {
+  emit('update:selected', pet)
+}
+
+const goAddPet = () => {
+  router.push('/pets/new')
+}
+</script>
+
+<style scoped>
+.pet-selector {
+  max-width: 500px;
+}
+
+.empty {
+  text-align: center;
+  padding: 20px;
+}
+
+.pet-card {
+  margin-bottom: 12px;
+  cursor: pointer;
+  transition: all 0.2s;
+}
+
+.pet-card:hover {
+  border-color: var(--el-color-primary);
+}
+
+.pet-card.selected {
+  border-color: var(--el-color-primary);
+  background-color: rgba(227, 72, 137, 0.05);
+}
+
+.pet-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.pet-info p {
+  margin: 4px 0 0;
+  font-size: 12px;
+  color: #666;
+}
+
+.check-icon {
+  margin-left: auto;
+  color: var(--el-color-primary);
+  font-size: 20px;
+}
+</style>

+ 169 - 0
src/components/RoomCalendar.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="room-calendar">
+    <div class="calendar-header">
+      <el-button text @click="prevMonth">&lt;</el-button>
+      <span class="month-label">{{ currentYear }}年{{ currentMonth }}月</span>
+      <el-button text @click="nextMonth">&gt;</el-button>
+    </div>
+
+    <table class="calendar-table">
+      <thead>
+        <tr>
+          <th v-for="day in weekDays" :key="day">{{ day }}</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="(week, wi) in calendarDays" :key="wi">
+          <td
+            v-for="(day, di) in week"
+            :key="di"
+            :class="dayClass(day)"
+            @click="handleDayClick(day)"
+          >
+            <span v-if="day" class="day-num">{{ day.day }}</span>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps<{
+  roomId: string
+  month: string
+  statusList: { date: string; status: 'AVAILABLE' | 'OCCUPIED' }[]
+}>()
+
+const emit = defineEmits<{
+  'date-click': [date: string, status: 'AVAILABLE' | 'OCCUPIED']
+}>()
+
+const weekDays = ['日', '一', '二', '三', '四', '五', '六']
+
+const [currentYear, currentMonth] = props.month.split('-').map(Number)
+
+interface DayInfo {
+  day: number
+  date: string
+  status?: 'AVAILABLE' | 'OCCUPIED'
+}
+
+const statusMap = computed(() => {
+  const map = new Map<string, 'AVAILABLE' | 'OCCUPIED'>()
+  for (const item of props.statusList) {
+    map.set(item.date, item.status)
+  }
+  return map
+})
+
+const calendarDays = computed(() => {
+  const daysInMonth = new Date(currentYear, currentMonth, 0).getDate()
+  const firstDayOfWeek = new Date(currentYear, currentMonth - 1, 1).getDay()
+  const weeks: (DayInfo | null)[][] = []
+  let week: (DayInfo | null)[] = []
+
+  for (let i = 0; i < firstDayOfWeek; i++) {
+    week.push(null)
+  }
+
+  for (let day = 1; day <= daysInMonth; day++) {
+    const dateStr = `${currentYear}-${String(currentMonth).padStart(2, '0')}-${String(day).padStart(2, '0')}`
+    week.push({
+      day,
+      date: dateStr,
+      status: statusMap.value.get(dateStr)
+    })
+    if (week.length === 7) {
+      weeks.push(week)
+      week = []
+    }
+  }
+
+  if (week.length > 0) {
+    while (week.length < 7) {
+      week.push(null)
+    }
+    weeks.push(week)
+  }
+
+  return weeks
+})
+
+const dayClass = (day: DayInfo | null) => {
+  if (!day) return 'empty'
+  if (day.status === 'OCCUPIED') return 'occupied'
+  if (day.status === 'AVAILABLE') return 'available'
+  return ''
+}
+
+const handleDayClick = (day: DayInfo | null) => {
+  if (!day) return
+  emit('date-click', day.date, day.status || 'AVAILABLE')
+}
+
+const prevMonth = () => {
+  // TODO: emit month change
+}
+
+const nextMonth = () => {
+  // TODO: emit month change
+}
+</script>
+
+<style scoped>
+.room-calendar {
+  max-width: 400px;
+}
+
+.calendar-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+}
+
+.month-label {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.calendar-table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.calendar-table th {
+  padding: 8px;
+  text-align: center;
+  font-size: 12px;
+  color: #666;
+}
+
+.calendar-table td {
+  padding: 8px;
+  text-align: center;
+  cursor: pointer;
+  border-radius: 4px;
+}
+
+.calendar-table td.empty {
+  cursor: default;
+}
+
+.calendar-table td.available {
+  background-color: #e8f5e9;
+  color: #2e7d32;
+}
+
+.calendar-table td.occupied {
+  background-color: #ffebee;
+  color: #c62828;
+}
+
+.calendar-table td:hover:not(.empty) {
+  opacity: 0.8;
+}
+</style>

+ 79 - 56
src/layout/AdminLayout.vue

@@ -1,123 +1,146 @@
-<script setup lang="ts">
-import {useLoginUserStore} from "../store";
-import {computed, type ComputedRef, ref} from "vue";
-import type {RouteRecordRaw} from "vue-router";
-import router, {adminRoutes} from "../router";
-
-const title = ref<string>("标题");
-const loginUserStore = useLoginUserStore()
-const login = computed(()=>loginUserStore.loginUser.isLogin)
-const loginHandler = () => {
-
-}
-const logoutHandler = () => {
-
-}
-const menus:ComputedRef<RouteRecordRaw[] | undefined> = computed(()=>{
-  return adminRoutes[0].children;
-})
-const isCollapse = ref(false)
-const handleOpen = (key: string, keyPath: string[]) => {
-  console.log(key, keyPath)
-}
-const handleClose = (key: string, keyPath: string[]) => {
-  console.log(key, keyPath)
-}
-</script>
-
 <template>
   <el-container>
     <el-header class="header">
       <div class="header-left" @click="router.push('/')">
         <img src="/logo.svg" alt="logo" class="logo">
-        <h2 class="title">{{title}}</h2>
+        <h2 class="title">{{ metaStore.title }}</h2>
       </div>
-      <h1>后台管理</h1>
+      <h1 class="admin-title">后台管理</h1>
       <div class="header-right">
         <template v-if="login">
           <el-dropdown placement="bottom">
-            <el-button> {{ loginUserStore.loginUser?.user?.name }} </el-button>
+            <el-button>
+              <el-icon><UserFilled /></el-icon>
+              {{ loginUserStore.loginUser?.user?.name }}
+            </el-button>
             <template #dropdown>
               <el-dropdown-menu>
-                <el-dropdown-item>个人信息</el-dropdown-item>
-                <el-dropdown-item @click="logoutHandler">退出</el-dropdown-item>
+                <el-dropdown-item @click="router.push('/profile')">个人信息</el-dropdown-item>
+                <el-dropdown-item @click="router.push('/booking')">返回前台</el-dropdown-item>
+                <el-dropdown-item divided @click="logoutHandler">退出</el-dropdown-item>
               </el-dropdown-menu>
             </template>
           </el-dropdown>
         </template>
         <template v-else>
-          <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
-          <el-button size="small">注册</el-button>
+          <el-button type="primary" size="small" @click="router.push('/login')">登录</el-button>
         </template>
       </div>
     </el-header>
 
     <el-container class="main-container">
-      <el-aside class="menu">
-        <el-radio-group v-model="isCollapse" style="margin-bottom: 20px;display: flex;justify-content: center">
-          <el-radio-button :value="false">expand</el-radio-button>
-          <el-radio-button :value="true">collapse</el-radio-button>
+      <el-aside :class="['menu', { collapsed: isCollapse }]">
+        <el-radio-group v-model="isCollapse" class="collapse-toggle">
+          <el-radio-button :value="false">展开</el-radio-button>
+          <el-radio-button :value="true">收起</el-radio-button>
         </el-radio-group>
         <el-menu
-            router
-            class="el-menu-vertical"
-            :collapse="isCollapse"
-            @open="handleOpen"
-            @close="handleClose"
+          router
+          :collapse="isCollapse"
+          :default-active="activeIndex"
         >
-          <el-menu-item :index="menu.path" v-for="menu in menus" :key="menu.path">
-            <el-icon><Component :is="menu.meta?.icon"/></el-icon>
-            <template #title>{{menu.meta?.title}}</template>
+          <el-menu-item :index="menu.path" v-for="menu in navMenus" :key="menu.path">
+            <el-icon><component :is="menu.meta?.icon" /></el-icon>
+            <template #title>{{ menu.meta?.title }}</template>
           </el-menu-item>
         </el-menu>
       </el-aside>
       <el-main>
-        <router-view/>
+        <router-view />
       </el-main>
     </el-container>
   </el-container>
-
 </template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { useLoginUserStore, useMetaStore } from '../store'
+import router, { adminRoutes } from '../router'
+import { UserFilled } from '@element-plus/icons-vue'
+import { getAuthController } from '../api/auth-controller'
+
+const route = useRoute()
+const metaStore = useMetaStore()
+const loginUserStore = useLoginUserStore()
+
+const login = computed(() => loginUserStore.loginUser.isLogin)
+const isCollapse = ref(false)
+const activeIndex = computed(() => route.path)
+
+const navMenus = computed(() => {
+  return (adminRoutes[0].children || []).filter(m => m.meta?.icon)
+})
+
+const logoutHandler = async () => {
+  try {
+    await getAuthController().logout()
+  } catch {
+    // 忽略服务端登出错误
+  }
+  loginUserStore.logoutUser()
+  router.push('/login')
+}
+</script>
+
 <style scoped>
 .el-container {
   height: 100vh;
 }
+
 .header {
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 0 20px;
-  margin-bottom: 10px;
-  border-bottom: 1px solid
+  border-bottom: 1px solid #eee;
 }
+
 .header-left {
   display: flex;
   align-items: center;
   gap: 10px;
+  cursor: pointer;
 }
+
 .logo {
   width: 40px;
   height: 40px;
 }
+
 .title {
   margin: 0;
   font-size: 20px;
 }
 
+.admin-title {
+  font-size: 18px;
+  color: var(--el-color-primary);
+}
+
 .header-right {
   display: flex;
   align-items: center;
   gap: 10px;
 }
 
-.menu{
-  width: 200px;
-}
 .main-container {
   flex: 1;
 }
-.el-menu-vertical-demo:not(.el-menu--collapse) {
+
+.menu {
   width: 200px;
-  min-height: 400px;
+  transition: width 0.3s;
+  border-right: 1px solid #eee;
+}
+
+.menu.collapsed {
+  width: 64px;
+}
+
+.collapse-toggle {
+  display: flex;
+  justify-content: center;
+  margin: 16px 8px;
 }
-</style>
+</style>

+ 63 - 37
src/layout/UserLayout.vue

@@ -2,23 +2,22 @@
   <div class="user-layout">
     <el-container>
       <el-header class="header">
-        <div class="header-left" @click="router.push('/')">
+        <div class="header-left" @click="router.push('/booking')">
           <img :src="metaStore.logo" alt="logo" class="logo">
-          <h2 class="title">{{metaStore.title}}</h2>
+          <h2 class="title">{{ metaStore.title }}</h2>
         </div>
         <el-menu
-            :default-active="activeIndex"
-            mode="horizontal"
-            router
-            @select="handleSelect"
-            class="nav-menu"
+          :default-active="activeIndex"
+          mode="horizontal"
+          router
+          class="nav-menu"
         >
-          <el-menu-item :index="menu.path" v-for="menu in menus" :key="menu.path">
-            <el-icon><component :is="menu.meta?.icon"/></el-icon>
+          <el-menu-item :index="menu.path" v-for="menu in navMenus" :key="menu.path">
+            <el-icon><component :is="menu.meta?.icon" /></el-icon>
             <template #title>{{ menu.meta?.title }}</template>
           </el-menu-item>
           <el-menu-item v-if="isAdmin" index="/admin">
-            <el-icon><Tools/></el-icon>
+            <el-icon><Tools /></el-icon>
             <template #title>后台管理</template>
           </el-menu-item>
         </el-menu>
@@ -31,94 +30,109 @@
               </el-button>
               <template #dropdown>
                 <el-dropdown-menu>
-                  <el-dropdown-item>个人信息</el-dropdown-item>
+                  <el-dropdown-item @click="router.push('/profile')">个人信息</el-dropdown-item>
+                  <el-dropdown-item @click="router.push('/orders')">我的订单</el-dropdown-item>
+                  <el-dropdown-item @click="router.push('/pets')">宠物档案</el-dropdown-item>
                   <el-dropdown-item divided @click="logoutHandler">退出登录</el-dropdown-item>
                 </el-dropdown-menu>
               </template>
             </el-dropdown>
           </template>
           <template v-else>
-            <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
+            <el-button type="primary" size="small" @click="router.push('/login')">登录</el-button>
             <el-button size="small" @click="router.push('/login')">注册</el-button>
           </template>
         </div>
       </el-header>
 
       <el-main>
-        <router-view/>
+        <router-view />
       </el-main>
 
-      <el-footer>Footer</el-footer>
+      <el-footer class="footer">
+        <p>{{ metaStore.title }} &copy; {{ new Date().getFullYear() }}</p>
+      </el-footer>
     </el-container>
   </div>
 </template>
 
 <script setup lang="ts">
-import {computed, ref} from 'vue'
-import {useLoginUserStore, useMetaStore} from "../store";
-import router, {userRoutes} from "../router";
-import {
-  Tools,
-  UserFilled
-} from '@element-plus/icons-vue'
+import { computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { useLoginUserStore, useMetaStore } from '../store'
+import router, { userRoutes } from '../router'
+import { Tools, UserFilled } from '@element-plus/icons-vue'
+import { getAuthController } from '../api/auth-controller'
+
+const route = useRoute()
 const metaStore = useMetaStore()
 const loginUserStore = useLoginUserStore()
+
 const login = computed(() => loginUserStore.loginUser.isLogin)
 const isAdmin = computed(() => loginUserStore.loginUser.user?.role === 'admin')
-const loginHandler = () => {
-  router.push('/login')
-}
-let activeIndex = ref('/')
-const menus = computed(()=>{
-  return userRoutes[0].children;
+
+const activeIndex = computed(() => route.path)
+
+const navMenus = computed(() => {
+  return (userRoutes[0].children || []).filter(m => m.meta?.icon)
 })
-const handleSelect = () => {
-  //todo: 处理菜单选择
-}
-const logoutHandler = () => {
+
+const logoutHandler = async () => {
+  try {
+    await getAuthController().logout()
+  } catch {
+    // 忽略服务端登出错误
+  }
   loginUserStore.logoutUser()
-  router.push('/')
+  router.push('/login')
 }
 </script>
+
 <style scoped>
 .user-layout {
   display: flex;
   flex-direction: column;
   min-height: 100vh;
 }
-el-main{
-  flex: 1;
-}
+
 .header {
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 0 20px;
+  border-bottom: 1px solid #eee;
 }
+
 .header-left {
   display: flex;
   align-items: center;
   gap: 10px;
+  cursor: pointer;
 }
+
 .logo {
   width: 40px;
   height: 40px;
 }
+
 .title {
   margin: 0;
   font-size: 20px;
 }
+
 .nav-menu {
   flex: 1;
   display: flex;
   justify-content: center;
   border-bottom: none;
 }
+
 .header-right {
   display: flex;
   align-items: center;
   gap: 10px;
 }
+
 .username {
   font-size: 14px;
   color: #606266;
@@ -133,11 +147,23 @@ el-main{
 }
 
 .user-btn:hover {
-  color: #db2777;
+  color: var(--el-color-primary);
 }
 
 .user-avatar {
   font-size: 20px;
-  color: #be185d;
+  color: var(--el-color-primary);
+}
+
+.footer {
+  text-align: center;
+  padding: 16px;
+  color: #999;
+  font-size: 14px;
+  border-top: 1px solid #eee;
+}
+
+.footer p {
+  margin: 0;
 }
 </style>

+ 1 - 0
src/main.ts

@@ -1,6 +1,7 @@
 import { createApp } from 'vue'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
+import './style.css'
 import App from './App.vue'
 import router from "./router";
 import { createPinia } from 'pinia'

+ 184 - 35
src/router/index.ts

@@ -1,72 +1,219 @@
 import {createRouter, createWebHistory, type Router, type RouteRecordRaw} from 'vue-router'
 
-import HomeView from '../view/HomeView.vue'
-import AboutView from '../view/AboutView.vue'
 import LoginView from '../view/LoginView.vue'
 import UserLayout from '../layout/UserLayout.vue'
 import AdminLayout from "../layout/AdminLayout.vue";
 import UserView from "../view/UserView.vue";
+
+import BookingView from '../view/booking/BookingView.vue'
+import BoardingBookingView from '../view/booking/BoardingBookingView.vue'
+import GroomingBookingView from '../view/booking/GroomingBookingView.vue'
+import OrderListView from '../view/order/OrderListView.vue'
+import OrderDetailView from '../view/order/OrderDetailView.vue'
+import PetListView from '../view/pet/PetListView.vue'
+import PetCreateView from '../view/pet/PetCreateView.vue'
+import PetDetailView from '../view/pet/PetDetailView.vue'
+import PetEditView from '../view/pet/PetEditView.vue'
+import ProfileView from '../view/profile/ProfileView.vue'
+import ProfileEditView from '../view/profile/ProfileEditView.vue'
+import PasswordEditView from '../view/profile/PasswordEditView.vue'
+
+import AdminOrderListView from '../view/admin/AdminOrderListView.vue'
+import AdminOrderDetailView from '../view/admin/AdminOrderDetailView.vue'
+import AdminRoomCalendarView from '../view/admin/AdminRoomCalendarView.vue'
+import AdminScheduleListView from '../view/admin/AdminScheduleListView.vue'
+import AdminPackageListView from '../view/admin/AdminPackageListView.vue'
+import AdminPackageCreateView from '../view/admin/AdminPackageCreateView.vue'
+import AdminPackageEditView from '../view/admin/AdminPackageEditView.vue'
+import AdminUserDetailView from '../view/admin/AdminUserDetailView.vue'
+
 import {
-    HomeFilled,
-    Menu,
     User,
+    Calendar,
+    List,
+    Avatar,
+    EditPen,
+    Tickets,
+    Coin,
 } from '@element-plus/icons-vue'
 import {useLoginUserStore} from "../store";
 import {ElMessage} from "element-plus";
 
-export const userRoutes:RouteRecordRaw[] = [
+export const userRoutes: RouteRecordRaw[] = [
     {
         path: '/',
         name: 'commonLayout',
         component: UserLayout,
         children: [
+            // 预约
+            {
+                path: '/booking',
+                name: 'booking',
+                component: BookingView,
+                meta: {
+                    title: '服务预约',
+                    icon: Calendar
+                }
+            },
+            {
+                path: '/booking/boarding',
+                name: 'boardingBooking',
+                component: BoardingBookingView,
+                meta: { title: '寄养预约', requiresAuth: true }
+            },
+            {
+                path: '/booking/grooming',
+                name: 'groomingBooking',
+                component: GroomingBookingView,
+                meta: { title: '洗护预约', requiresAuth: true }
+            },
+            // 订单
+            {
+                path: '/orders',
+                name: 'orderList',
+                component: OrderListView,
+                meta: {
+                    title: '我的订单',
+                    icon: Tickets,
+                    requiresAuth: true
+                }
+            },
+            {
+                path: '/orders/:id',
+                name: 'orderDetail',
+                component: OrderDetailView,
+                meta: { title: '订单详情', requiresAuth: true }
+            },
+            // 宠物
             {
-                path: '/',
-                name: 'home',
-                component: HomeView,
+                path: '/pets',
+                name: 'petList',
+                component: PetListView,
                 meta: {
-                    title: '首页',
-                    icon: HomeFilled
+                    title: '宠物档案',
+                    icon: Avatar,
+                    requiresAuth: true
                 }
             },
             {
-                path: '/about',
-                name: 'about',
-                component: AboutView,
+                path: '/pets/new',
+                name: 'petCreate',
+                component: PetCreateView,
+                meta: { title: '新增宠物', requiresAuth: true }
+            },
+            {
+                path: '/pets/:id',
+                name: 'petDetail',
+                component: PetDetailView,
+                meta: { title: '宠物详情', requiresAuth: true }
+            },
+            {
+                path: '/pets/:id/edit',
+                name: 'petEdit',
+                component: PetEditView,
+                meta: { title: '编辑宠物', requiresAuth: true }
+            },
+            // 个人中心
+            {
+                path: '/profile',
+                name: 'profile',
+                component: ProfileView,
                 meta: {
-                    title: '关于',
-                    icon: Menu
+                    title: '个人中心',
+                    icon: User,
+                    requiresAuth: true
                 }
             },
+            {
+                path: '/profile/edit',
+                name: 'profileEdit',
+                component: ProfileEditView,
+                meta: { title: '修改资料', requiresAuth: true }
+            },
+            {
+                path: '/profile/password',
+                name: 'passwordEdit',
+                component: PasswordEditView,
+                meta: { title: '修改密码', requiresAuth: true }
+            },
         ],
     },
 ]
-export const adminRoutes:RouteRecordRaw[] = [
+export const adminRoutes: RouteRecordRaw[] = [
     {
         path: '/admin',
         name: 'adminLayout',
         component: AdminLayout,
-        children:[
+        children: [
+            {
+                path: '/admin/orders',
+                name: 'adminOrderList',
+                component: AdminOrderListView,
+                meta: {
+                    title: '订单审核',
+                    icon: List
+                }
+            },
+            {
+                path: '/admin/orders/:id',
+                name: 'adminOrderDetail',
+                component: AdminOrderDetailView,
+                meta: { title: '订单审核详情' }
+            },
+            {
+                path: '/admin/rooms',
+                name: 'adminRooms',
+                component: AdminRoomCalendarView,
+                meta: {
+                    title: '房态管理',
+                    icon: Calendar
+                }
+            },
+            {
+                path: '/admin/schedules',
+                name: 'adminSchedules',
+                component: AdminScheduleListView,
+                meta: {
+                    title: '排班管理',
+                    icon: EditPen
+                }
+            },
+            {
+                path: '/admin/packages',
+                name: 'adminPackageList',
+                component: AdminPackageListView,
+                meta: {
+                    title: '套餐配置',
+                    icon: Coin
+                }
+            },
+            {
+                path: '/admin/packages/new',
+                name: 'adminPackageCreate',
+                component: AdminPackageCreateView,
+                meta: { title: '新增套餐' }
+            },
+            {
+                path: '/admin/packages/:id/edit',
+                name: 'adminPackageEdit',
+                component: AdminPackageEditView,
+                meta: { title: '编辑套餐' }
+            },
             {
                 path: '/admin/users',
-                name: 'users',
+                name: 'adminUsers',
                 component: UserView,
-                meta:{
+                meta: {
                     title: '用户管理',
-                    icon: User,
-                    requiresAdmin: true
+                    icon: User
                 }
             },
             {
-                path:'/admin/other',
-                name:'other',
-                component: AdminLayout,
-                meta:{
-                    title: '其他',
-                    icon: Menu,
-                    requiresAdmin: true
-                }
-            }
+                path: '/admin/users/:id',
+                name: 'adminUserDetail',
+                component: AdminUserDetailView,
+                meta: { title: '用户详情' }
+            },
         ],
         meta: {
             requiresAdmin: true
@@ -74,7 +221,7 @@ export const adminRoutes:RouteRecordRaw[] = [
     }
 ]
 
-const router : Router = createRouter({
+const router: Router = createRouter({
     history: createWebHistory(),
     routes: [
         {
@@ -87,14 +234,16 @@ const router : Router = createRouter({
     ],
 });
 router.beforeEach((to, _from, next) => {
-    if (to.matched.some(record => record.meta.requiresAdmin)) {
-        const loginUserStore = useLoginUserStore()
+    const loginUserStore = useLoginUserStore()
+    const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
+    const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin)
+    if (requiresAuth || requiresAdmin) {
         if (!loginUserStore.loginUser.isLogin) {
             ElMessage.warning('请先登录')
             next({path: '/login', query: {redirect: to.fullPath}})
-        } else if (loginUserStore.loginUser.user?.role !== 'admin') {
+        } else if (requiresAdmin && loginUserStore.loginUser.user?.role !== 'admin') {
             ElMessage.error('无权访问')
-            next('/')
+            next('/booking')
         } else {
             next()
         }

+ 25 - 0
src/store/appointment.ts

@@ -0,0 +1,25 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import type { Pet, Package, BoardingRoom } from '@/type'
+
+export const useAppointmentStore = defineStore('appointment', () => {
+  const currentPackage = ref<Package | null>(null)
+  const selectedPet = ref<Pet | null>(null)
+  const selectedRoom = ref<BoardingRoom | null>(null)
+  const dateRange = ref<[Date, Date] | null>(null)
+
+  function reset() {
+    currentPackage.value = null
+    selectedPet.value = null
+    selectedRoom.value = null
+    dateRange.value = null
+  }
+
+  return {
+    currentPackage,
+    selectedPet,
+    selectedRoom,
+    dateRange,
+    reset
+  }
+})

+ 4 - 2
src/store/index.ts

@@ -1,2 +1,4 @@
-export  {useMetaStore} from "./meta.ts";
-export  {useLoginUserStore} from "./user.ts";
+export { useMetaStore } from './meta.ts'
+export { useLoginUserStore } from './user.ts'
+export { useAppointmentStore } from './appointment.ts'
+export { usePetStore } from './pet.ts'

+ 34 - 0
src/store/pet.ts

@@ -0,0 +1,34 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import type { Pet } from '@/type'
+
+export const usePetStore = defineStore('pet', () => {
+  const pets = ref<Pet[]>([])
+
+  function setPets(list: Pet[]) {
+    pets.value = list
+  }
+
+  function addPet(pet: Pet) {
+    pets.value.push(pet)
+  }
+
+  function updatePet(updated: Pet) {
+    const index = pets.value.findIndex(p => p.id === updated.id)
+    if (index !== -1) {
+      pets.value[index] = updated
+    }
+  }
+
+  function removePet(id: string) {
+    pets.value = pets.value.filter(p => p.id !== id)
+  }
+
+  return {
+    pets,
+    setPets,
+    addPet,
+    updatePet,
+    removePet
+  }
+})

+ 0 - 1
src/store/user.ts

@@ -31,7 +31,6 @@ export const useLoginUserStore = defineStore('loginUser', ()=>{
             name: '未登录',
             role: 'guest'
         }
-        localStorage.removeItem('token')
     }
    return{
         //States

+ 31 - 49
src/style.css

@@ -3,77 +3,59 @@
   line-height: 1.5;
   font-weight: 400;
 
-  color-scheme: light dark;
-  color: rgba(255, 255, 255, 0.87);
-  background-color: #242424;
+  color: #213547;
+  background-color: #ffffff;
 
   font-synthesis: none;
   text-rendering: optimizeLegibility;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-}
 
-a {
-  font-weight: 500;
-  color: #646cff;
-  text-decoration: inherit;
+  --el-color-primary: #e34889;
+  --el-color-primary-light-3: #f06292;
+  --el-color-primary-light-5: #f48fb1;
+  --el-color-primary-light-7: #f8bbd0;
+  --el-color-primary-dark-2: #c2185b;
+  --el-color-primary-dark: #c2185b;
 }
-a:hover {
-  color: #535bf2;
+
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
 }
 
 body {
   margin: 0;
-  display: flex;
-  place-items: center;
   min-width: 320px;
   min-height: 100vh;
 }
 
-h1 {
-  font-size: 3.2em;
-  line-height: 1.1;
+#app {
+  width: 100%;
+  min-height: 100vh;
 }
 
-button {
-  border-radius: 8px;
-  border: 1px solid transparent;
-  padding: 0.6em 1.2em;
-  font-size: 1em;
-  font-weight: 500;
-  font-family: inherit;
-  background-color: #1a1a1a;
-  cursor: pointer;
-  transition: border-color 0.25s;
-}
-button:hover {
-  border-color: #646cff;
+a {
+  color: var(--el-color-primary);
+  text-decoration: none;
 }
-button:focus,
-button:focus-visible {
-  outline: 4px auto -webkit-focus-ring-color;
+
+a:hover {
+  opacity: 0.8;
 }
 
-.card {
-  padding: 2em;
+.el-button--primary {
+  background: linear-gradient(135deg, #db2777, #be185d);
+  border: none;
 }
 
-#app {
-  max-width: 1280px;
-  margin: 0 auto;
-  padding: 2rem;
-  text-align: center;
+.el-button--primary:hover {
+  background: linear-gradient(135deg, #e34889, #c2185b);
+  opacity: 0.9;
 }
 
-@media (prefers-color-scheme: light) {
-  :root {
-    color: #213547;
-    background-color: #ffffff;
-  }
-  a:hover {
-    color: #747bff;
-  }
-  button {
-    background-color: #f9f9f9;
-  }
+.el-menu-item.is-active {
+  color: var(--el-color-primary) !important;
 }
+</style>

+ 68 - 7
src/type/index.ts

@@ -1,11 +1,72 @@
-export interface User{
-    id:string,
-    name:string,
+export interface User {
+    id: string
+    name: string
     avatar?: string
     role: 'admin' | 'user' | 'guest'
 }
-export interface LoginUser{
-    isLogin:boolean,
-    token?:string
-    user?:User
+
+export interface LoginUser {
+    isLogin: boolean
+    token?: string
+    user?: User
+}
+
+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
 }

+ 0 - 14
src/util/axios-instance.ts

@@ -15,20 +15,6 @@ const axiosInstance: AxiosInstance = axios.create({
     },
 });
 
-// 请求拦截器
-axiosInstance.interceptors.request.use(
-    (config) => {
-        // 添加 token
-        const token = localStorage.getItem('token');
-        if (token) {
-            config.headers.Authorization = `Bearer ${token}`;
-        }
-        return config;
-    },
-    (error) => {
-        return Promise.reject(error);
-    }
-);
 
 // 响应拦截器
 axiosInstance.interceptors.response.use(

+ 7 - 8
src/view/LoginView.vue

@@ -129,7 +129,7 @@ import { ElMessage } from 'element-plus'
 import { User, Lock, ArrowLeft } from '@element-plus/icons-vue'
 import type { FormInstance, FormRules } from 'element-plus'
 import { getAuthController } from '../api/auth-controller'
-import type { LoginDto, RegisterDto } from '../api/models'
+import type { LoginDto, RegisterDto, UserVo } from '../api/models'
 import { useLoginUserStore } from '../store'
 import type { User as UserType } from '../type'
 
@@ -203,16 +203,15 @@ const handleLogin = async () => {
       password: loginForm.password
     } as LoginDto)
 
-    const tokenData = res.data
-    if (res.code === 200 && tokenData?.token) {
-      localStorage.setItem('token', tokenData.token)
+    const userData = res.data as UserVo & { userId?: number }
+    if (res.code === 200) {
       loginUserStore.loginSuccess({
-        id: String(tokenData.userId || ''),
-        name: tokenData.username || tokenData.account || '用户',
-        role: (tokenData.role as UserType['role']) || 'user'
+        id: String(userData.userId ?? userData.id ?? ''),
+        name: userData.username || userData.account || '用户',
+        role: (userData.role as UserType['role']) || 'user'
       })
       ElMessage.success('登录成功')
-      router.push((route.query.redirect as string) || '/')
+      router.push((route.query.redirect as string) || '/booking')
     } else {
       ElMessage.error(res.message || '登录失败')
     }

+ 11 - 14
src/view/UserView.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { getUserController } from '@/api/user-controller'
-import type { QueryByPageParams, UserDto, UserVo } from '@/api/models'
+import type { UserDto, UserVo } from '@/api/models'
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules, ElIcon } from 'element-plus'
 import { Plus, RefreshRight, Search, Delete, Edit } from '@element-plus/icons-vue'
 import { ref, reactive, computed } from 'vue'
@@ -84,15 +84,14 @@ const handleReset = () => {
 const fetchData = async () => {
   loading.value = true
   try {
-    const condition: QueryByPageParams = {
-      username: searchForm.username || '',
-      account: searchForm.account || '',
-      role: searchForm.role || '',
+    const res = await api.queryByPageUsers({
+      username: searchForm.username || undefined,
+      account: searchForm.account || undefined,
+      role: searchForm.role || undefined,
       enable: searchForm.enable,
       pageNum: pagination.pageNum,
       pageSize: pagination.pageSize
-    }
-    const res = await api.queryByPage(condition)
+    })
     const pageData = res?.data
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
@@ -128,8 +127,7 @@ const handleAdd = () => {
 const handleEdit = async (row: UserVo) => {
   dialogTitle.value = '编辑用户'
   try {
-    const id = row.id || ''
-    const res = await api.queryById(id)
+    const res = await api.queryById(row.id ?? 0)
     if (res?.code === 0 || res?.code === 200) {
       Object.assign(userForm, {
         id: res.data?.id,
@@ -205,8 +203,7 @@ const handleDelete = (row: UserVo) => {
     type: 'warning'
   }).then(async () => {
     try {
-      const ids = row.id ? [row.id] : []
-      const res = await api.deleteById({ ids })
+      const res = await api.deleteById({ ids: String(row.id) })
       if (res?.code === 0 || res?.code === 200) {
         ElMessage.success('删除成功')
         fetchData()
@@ -229,8 +226,8 @@ const handleBatchDelete = () => {
     type: 'warning'
   }).then(async () => {
     try {
-      const ids = selectedRows.value.map(row => row.id).filter((id): id is string => id !== undefined)
-      const res = await api.deleteById({ ids })
+      const ids = selectedRows.value.map(row => row.id).filter((id): id is number => id !== undefined)
+      const res = await api.deleteById({ ids: ids.join(',') })
       if (res?.code === 0 || res?.code === 200) {
         ElMessage.success('删除成功')
         selectedRows.value = []
@@ -250,7 +247,7 @@ const handleToggleEnable = async (row: UserVo, newEnable: number) => {
   const oldEnable = row.enable
   row.enable = newEnable
   try {
-    const res = await api.updateUserStatus({ id: row.id!, enable: newEnable })
+    const res = await api.updateStatus({ id: row.id!, enable: newEnable })
     if (res?.code === 0 || res?.code === 200) {
       ElMessage.success('状态更新成功')
     } else {

+ 197 - 0
src/view/admin/AdminOrderDetailView.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="admin-order-detail">
+    <el-button text @click="router.back()">← 返回</el-button>
+    <el-card v-if="order" class="detail-card">
+      <h2>订单详情</h2>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="订单编号">{{ order.id }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="statusTag(order.status)">{{ statusLabel(order.status) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="用户">{{ order.userId }}</el-descriptions-item>
+        <el-descriptions-item label="服务类型">{{ order.type }}</el-descriptions-item>
+        <el-descriptions-item label="总价">¥{{ order.totalPrice }}</el-descriptions-item>
+        <el-descriptions-item label="定金">¥{{ order.deposit }} {{ order.depositPaid ? '(已支付)' : '(未支付)' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ order.createdAt }}</el-descriptions-item>
+      </el-descriptions>
+
+      <div class="actions">
+        <el-button v-if="order.status === 'PENDING'" type="primary" @click="openReview">审核</el-button>
+        <el-button v-if="order.status === 'CANCEL_REQUESTED'" type="warning" @click="openCancelHandle">处理取消</el-button>
+      </div>
+    </el-card>
+
+    <el-dialog v-model="reviewVisible" title="订单审核" width="400px">
+      <el-form>
+        <el-form-item label="审核结果">
+          <el-radio-group v-model="reviewAction">
+            <el-radio value="CONFIRMED">通过</el-radio>
+            <el-radio value="CANCELLED">拒绝</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="原因">
+          <el-input v-model="reviewReason" type="textarea" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="reviewVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitReview">确认</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="cancelVisible" title="处理取消申请" width="400px">
+      <p>取消原因:{{ order?.cancelReason }}</p>
+      <el-form>
+        <el-form-item label="处理结果">
+          <el-radio-group v-model="cancelAction">
+            <el-radio value="CANCELLED">同意取消</el-radio>
+            <el-radio value="CONFIRMED">拒绝取消</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="cancelVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitCancel">确认</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import type { Appointment } from '@/type'
+import { getAppointmentController } from '@/api/appointment-controller'
+import { getAdminAppointmentController } from '@/api/admin-appointment-controller'
+import type { ReviewDto, HandleCancelDto } from '@/api/models'
+
+const route = useRoute()
+const router = useRouter()
+const order = ref<Appointment | null>(null)
+const loading = ref(false)
+
+const reviewVisible = ref(false)
+const reviewAction = ref('CONFIRMED')
+const reviewReason = ref('')
+
+const cancelVisible = ref(false)
+const cancelAction = ref('CANCELLED')
+
+const loadOrder = async () => {
+  loading.value = true
+  try {
+    const api = getAppointmentController()
+    const res = await api.getById2(Number(route.params.id))
+    if (res.code === 200 && res.data) {
+      const a = res.data
+      order.value = {
+        id: String(a.id ?? ''),
+        userId: String(a.userId ?? ''),
+        petId: String(a.petId ?? ''),
+        packageId: String(a.packageId ?? ''),
+        roomId: a.roomId ? String(a.roomId) : undefined,
+        type: (a.type as Appointment['type']) ?? 'GROOMING',
+        startDate: a.startDate,
+        endDate: a.endDate,
+        status: (a.status as Appointment['status']) ?? 'PENDING',
+        deposit: a.deposit ?? 0,
+        depositPaid: a.depositPaid ?? false,
+        totalPrice: a.totalPrice ?? 0,
+        cancelReason: a.cancelReason,
+        rejectReason: a.rejectReason,
+        createdAt: a.createdAt ?? ''
+      }
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '加载失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+const openReview = () => {
+  reviewAction.value = 'CONFIRMED'
+  reviewReason.value = ''
+  reviewVisible.value = true
+}
+
+const submitReview = async () => {
+  if (!order.value) return
+  try {
+    const api = getAdminAppointmentController()
+    const dto: ReviewDto = {
+      action: reviewAction.value,
+      rejectReason: reviewAction.value === 'CANCELLED' ? reviewReason.value : undefined
+    }
+    const res = await api.review(Number(order.value.id), dto)
+    if (res.code === 200) {
+      ElMessage.success('审核完成')
+      reviewVisible.value = false
+      await loadOrder()
+    } else {
+      ElMessage.error(res.message || '操作失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '操作失败')
+  }
+}
+
+const openCancelHandle = () => {
+  cancelAction.value = 'CANCELLED'
+  cancelVisible.value = true
+}
+
+const submitCancel = async () => {
+  if (!order.value) return
+  try {
+    const api = getAdminAppointmentController()
+    const dto: HandleCancelDto = { action: cancelAction.value }
+    const res = await api.handleCancel(Number(order.value.id), dto)
+    if (res.code === 200) {
+      ElMessage.success('处理完成')
+      cancelVisible.value = false
+      await loadOrder()
+    } else {
+      ElMessage.error(res.message || '操作失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '操作失败')
+  }
+}
+
+const statusLabel = (status: string) => {
+  const map: Record<string, string> = {
+    PENDING: '待审核', CONFIRMED: '已确认', CANCELLED: '已取消',
+    COMPLETED: '已完成', CANCEL_REQUESTED: '取消申请'
+  }
+  return map[status] || status
+}
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = {
+    PENDING: 'warning', CONFIRMED: 'success', CANCELLED: 'info',
+    COMPLETED: 'primary', CANCEL_REQUESTED: 'danger'
+  }
+  return map[status] || 'info'
+}
+
+onMounted(loadOrder)
+</script>
+
+<style scoped>
+.admin-order-detail {
+  max-width: 800px;
+  padding: 20px;
+}
+
+.detail-card {
+  margin-top: 16px;
+}
+
+.actions {
+  margin-top: 24px;
+  display: flex;
+  gap: 12px;
+}
+</style>

+ 253 - 0
src/view/admin/AdminOrderListView.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="admin-order-list">
+    <h1>订单审核</h1>
+
+    <div class="toolbar">
+      <el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="fetchOrders">
+        <el-option label="全部" value="" />
+        <el-option label="待审核" value="PENDING" />
+        <el-option label="已确认" value="CONFIRMED" />
+        <el-option label="已取消" value="CANCELLED" />
+        <el-option label="取消申请" value="CANCEL_REQUESTED" />
+        <el-option label="已完成" value="COMPLETED" />
+      </el-select>
+    </div>
+
+    <el-table :data="orders" border stripe v-loading="loading">
+      <el-table-column prop="id" label="ID" width="80" />
+      <el-table-column prop="userId" label="用户" width="120" />
+      <el-table-column prop="type" label="类型" width="100" />
+      <el-table-column prop="totalPrice" label="总价" width="100" />
+      <el-table-column prop="status" label="状态" width="140">
+        <template #default="{ row }">
+          <el-tag :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="200" fixed="right">
+        <template #default="{ row }">
+          <el-button v-if="row.status === 'PENDING'" size="small" type="primary" @click="openReview(row)">审核</el-button>
+          <el-button v-if="row.status === 'CANCEL_REQUESTED'" size="small" type="warning" @click="openCancelHandle(row)">处理取消</el-button>
+          <el-button size="small" @click="viewDetail(row.id)">详情</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pagination">
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :total="total"
+        layout="total, prev, pager, next"
+        @current-change="fetchOrders"
+      />
+    </div>
+
+    <!-- 审核对话框 -->
+    <el-dialog v-model="reviewVisible" title="订单审核" width="400px">
+      <el-form>
+        <el-form-item label="审核结果">
+          <el-radio-group v-model="reviewAction">
+            <el-radio value="CONFIRMED">通过</el-radio>
+            <el-radio value="CANCELLED">拒绝</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="原因">
+          <el-input v-model="reviewReason" type="textarea" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="reviewVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitReview">确认</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 处理取消对话框 -->
+    <el-dialog v-model="cancelVisible" title="处理取消申请" width="400px">
+      <p>取消原因:{{ cancelReason }}</p>
+      <el-form>
+        <el-form-item label="处理结果">
+          <el-radio-group v-model="cancelAction">
+            <el-radio value="CANCELLED">同意取消</el-radio>
+            <el-radio value="CONFIRMED">拒绝取消</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="处理意见">
+          <el-input v-model="cancelOpinion" type="textarea" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="cancelVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitCancel">确认</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import type { Appointment } from '@/type'
+import { getAdminAppointmentController } from '@/api/admin-appointment-controller'
+import type { ListAllParams, ReviewDto, HandleCancelDto } from '@/api/models'
+
+const router = useRouter()
+const orders = ref<Appointment[]>([])
+const loading = ref(false)
+const statusFilter = ref('')
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+
+// Review dialog
+const reviewVisible = ref(false)
+const reviewAction = ref('CONFIRMED')
+const reviewReason = ref('')
+const reviewingOrder = ref<Appointment | null>(null)
+
+// Cancel dialog
+const cancelVisible = ref(false)
+const cancelAction = ref('CANCELLED')
+const cancelOpinion = ref('')
+const cancelReason = ref('')
+const cancelOrder = ref<Appointment | null>(null)
+
+const fetchOrders = async () => {
+  loading.value = true
+  try {
+    const api = getAdminAppointmentController()
+    const params: ListAllParams = {
+      pageNum: pageNum.value,
+      pageSize: pageSize.value
+    }
+    if (statusFilter.value) params.status = statusFilter.value
+    const res = await api.listAll(params)
+    if (res.code === 200 && res.data) {
+      orders.value = (res.data.data ?? []).map(a => ({
+        id: String(a.id ?? ''),
+        userId: String(a.userId ?? ''),
+        petId: String(a.petId ?? ''),
+        packageId: String(a.packageId ?? ''),
+        roomId: a.roomId ? String(a.roomId) : undefined,
+        type: (a.type as Appointment['type']) ?? 'GROOMING',
+        startDate: a.startDate,
+        endDate: a.endDate,
+        status: (a.status as Appointment['status']) ?? 'PENDING',
+        deposit: a.deposit ?? 0,
+        depositPaid: a.depositPaid ?? false,
+        totalPrice: a.totalPrice ?? 0,
+        cancelReason: a.cancelReason,
+        rejectReason: a.rejectReason,
+        createdAt: a.createdAt ?? ''
+      }))
+      total.value = res.data.total ?? 0
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '加载失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+const openReview = (order: Appointment) => {
+  reviewingOrder.value = order
+  reviewAction.value = 'CONFIRMED'
+  reviewReason.value = ''
+  reviewVisible.value = true
+}
+
+const submitReview = async () => {
+  if (!reviewingOrder.value) return
+  try {
+    const api = getAdminAppointmentController()
+    const dto: ReviewDto = {
+      action: reviewAction.value,
+      rejectReason: reviewAction.value === 'CANCELLED' ? reviewReason.value : undefined
+    }
+    const res = await api.review(Number(reviewingOrder.value.id), dto)
+    if (res.code === 200) {
+      ElMessage.success('审核完成')
+      reviewVisible.value = false
+      await fetchOrders()
+    } else {
+      ElMessage.error(res.message || '操作失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '操作失败')
+  }
+}
+
+const openCancelHandle = (order: Appointment) => {
+  cancelOrder.value = order
+  cancelReason.value = order.cancelReason || ''
+  cancelAction.value = 'CANCELLED'
+  cancelOpinion.value = ''
+  cancelVisible.value = true
+}
+
+const submitCancel = async () => {
+  if (!cancelOrder.value) return
+  try {
+    const api = getAdminAppointmentController()
+    const dto: HandleCancelDto = { action: cancelAction.value }
+    const res = await api.handleCancel(Number(cancelOrder.value.id), dto)
+    if (res.code === 200) {
+      ElMessage.success('处理完成')
+      cancelVisible.value = false
+      await fetchOrders()
+    } else {
+      ElMessage.error(res.message || '操作失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '操作失败')
+  }
+}
+
+const viewDetail = (id: string) => {
+  router.push(`/admin/orders/${id}`)
+}
+
+const statusLabel = (status: string) => {
+  const map: Record<string, string> = {
+    PENDING: '待审核',
+    CONFIRMED: '已确认',
+    CANCELLED: '已取消',
+    COMPLETED: '已完成',
+    CANCEL_REQUESTED: '取消申请'
+  }
+  return map[status] || status
+}
+
+const statusTag = (status: string) => {
+  const map: Record<string, string> = {
+    PENDING: 'warning',
+    CONFIRMED: 'success',
+    CANCELLED: 'info',
+    COMPLETED: 'primary',
+    CANCEL_REQUESTED: 'danger'
+  }
+  return map[status] || 'info'
+}
+
+fetchOrders()
+</script>
+
+<style scoped>
+.admin-order-list {
+  padding: 20px;
+}
+
+h1 {
+  margin-bottom: 20px;
+}
+
+.toolbar {
+  margin-bottom: 16px;
+}
+
+.pagination {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 87 - 0
src/view/admin/AdminPackageCreateView.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="admin-package-create">
+    <el-button text @click="router.back()">← 返回</el-button>
+    <h1>新增套餐</h1>
+
+    <el-form :model="form" label-width="120px" class="form">
+      <el-form-item label="套餐类型">
+        <el-radio-group v-model="form.type">
+          <el-radio value="BOARDING">寄养</el-radio>
+          <el-radio value="GROOMING">洗护</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="套餐名称">
+        <el-input v-model="form.name" />
+      </el-form-item>
+      <el-form-item label="价格">
+        <el-input-number v-model="form.price" :min="0" :precision="2" />
+      </el-form-item>
+      <el-form-item label="描述">
+        <el-input v-model="form.description" type="textarea" />
+      </el-form-item>
+      <el-form-item v-if="form.type === 'BOARDING'" label="寄养天数">
+        <el-input-number v-model="form.durationDays" :min="1" />
+      </el-form-item>
+      <el-form-item label="是否启用">
+        <el-switch v-model="form.isActive" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleSubmit">保存</el-button>
+        <el-button @click="router.back()">取消</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue'
+import { useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { getAdminPackageController } from '@/api/admin-package-controller'
+import type { PackageDto } from '@/api/models'
+
+const router = useRouter()
+
+const form = reactive({
+  type: 'BOARDING' as 'BOARDING' | 'GROOMING',
+  name: '',
+  price: 0,
+  description: '',
+  durationDays: 1,
+  isActive: true
+})
+
+const handleSubmit = async () => {
+  try {
+    const api = getAdminPackageController()
+    const dto: PackageDto = {
+      name: form.name,
+      type: form.type,
+      price: form.price,
+      description: form.description,
+      duration: form.type === 'BOARDING' ? form.durationDays : undefined,
+      isActive: form.isActive ? 1 : 0
+    }
+    const res = await api.create2(dto)
+    if (res.code === 200) {
+      ElMessage.success('新增成功')
+      router.push('/admin/packages')
+    } else {
+      ElMessage.error(res.message || '新增失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '新增失败')
+  }
+}
+</script>
+
+<style scoped>
+.admin-package-create {
+  max-width: 600px;
+  padding: 20px;
+}
+
+.form {
+  margin-top: 20px;
+}
+</style>

+ 109 - 0
src/view/admin/AdminPackageEditView.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="admin-package-edit">
+    <el-button text @click="router.back()">← 返回</el-button>
+    <h1>编辑套餐</h1>
+
+    <el-form :model="form" label-width="120px" class="form">
+      <el-form-item label="套餐类型">
+        <el-radio-group v-model="form.type">
+          <el-radio value="BOARDING">寄养</el-radio>
+          <el-radio value="GROOMING">洗护</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="套餐名称">
+        <el-input v-model="form.name" />
+      </el-form-item>
+      <el-form-item label="价格">
+        <el-input-number v-model="form.price" :min="0" :precision="2" />
+      </el-form-item>
+      <el-form-item label="描述">
+        <el-input v-model="form.description" type="textarea" />
+      </el-form-item>
+      <el-form-item v-if="form.type === 'BOARDING'" label="寄养天数">
+        <el-input-number v-model="form.durationDays" :min="1" />
+      </el-form-item>
+      <el-form-item label="是否启用">
+        <el-switch v-model="form.isActive" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleSubmit">保存</el-button>
+        <el-button @click="router.back()">取消</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { getPackageController } from '@/api/package-controller'
+import { getAdminPackageController } from '@/api/admin-package-controller'
+import type { PackageDto } from '@/api/models'
+
+const router = useRouter()
+const route = useRoute()
+
+const form = reactive({
+  type: 'BOARDING' as 'BOARDING' | 'GROOMING',
+  name: '',
+  price: 0,
+  description: '',
+  durationDays: 1,
+  isActive: true
+})
+
+const fetchPackage = async () => {
+  try {
+    const api = getPackageController()
+    const res = await api.getById1(Number(route.params.id))
+    if (res.code === 200 && res.data) {
+      const p = res.data
+      form.type = (p.type as 'BOARDING' | 'GROOMING') ?? 'BOARDING'
+      form.name = p.name ?? ''
+      form.price = p.price ?? 0
+      form.description = p.description ?? ''
+      form.durationDays = p.duration ?? 1
+      form.isActive = true
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '加载失败')
+  }
+}
+
+const handleSubmit = async () => {
+  try {
+    const api = getAdminPackageController()
+    const dto: PackageDto = {
+      name: form.name,
+      type: form.type,
+      price: form.price,
+      description: form.description,
+      duration: form.type === 'BOARDING' ? form.durationDays : undefined,
+      isActive: form.isActive ? 1 : 0
+    }
+    const res = await api.update1(Number(route.params.id), dto)
+    if (res.code === 200) {
+      ElMessage.success('保存成功')
+      router.push('/admin/packages')
+    } else {
+      ElMessage.error(res.message || '保存失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '保存失败')
+  }
+}
+
+onMounted(fetchPackage)
+</script>
+
+<style scoped>
+.admin-package-edit {
+  max-width: 600px;
+  padding: 20px;
+}
+
+.form {
+  margin-top: 20px;
+}
+</style>

Vissa filer visades inte eftersom för många filer har ändrats