Selaa lähdekoodia

# api: 题目与比赛模块api(通过orval生成)

yangyi 2 kuukautta sitten
vanhempi
sitoutus
6b37bf545c
72 muutettua tiedostoa jossa 2055 lisäystä ja 19 poistoa
  1. 6 4
      src/api/auth-controller.ts
  2. 71 0
      src/api/category-controller.ts
  3. 186 0
      src/api/contest-controller.ts
  4. 14 0
      src/api/models/_deleteParams.ts
  5. 3 2
      src/api/models/authTokenVo.ts
  6. 20 0
      src/api/models/authTokenVoRole.ts
  7. 22 0
      src/api/models/categoryDto.ts
  8. 19 0
      src/api/models/categoryVo.ts
  9. 38 0
      src/api/models/contestDetailVo.ts
  10. 20 0
      src/api/models/contestDetailVoStatus.ts
  11. 20 0
      src/api/models/contestDetailVoType.ts
  12. 34 0
      src/api/models/contestDto.ts
  13. 21 0
      src/api/models/contestDtoType.ts
  14. 19 0
      src/api/models/contestProblemDto.ts
  15. 24 0
      src/api/models/contestProblemVo.ts
  16. 20 0
      src/api/models/contestProblemVoDifficulty.ts
  17. 15 0
      src/api/models/contestRegisterDto.ts
  18. 21 0
      src/api/models/contestRegistrationVo.ts
  19. 29 0
      src/api/models/contestVo.ts
  20. 20 0
      src/api/models/contestVoStatus.ts
  21. 19 0
      src/api/models/contestVoType.ts
  22. 3 0
      src/api/models/deleteByIdParams.ts
  23. 11 0
      src/api/models/deleteByIds1Params.ts
  24. 14 0
      src/api/models/deleteByIdsParams.ts
  25. 14 0
      src/api/models/deleteCaseParams.ts
  26. 14 0
      src/api/models/deleteTestParams.ts
  27. 14 0
      src/api/models/getDetail1Params.ts
  28. 56 0
      src/api/models/index.ts
  29. 18 0
      src/api/models/pageVoListContestVo.ts
  30. 18 0
      src/api/models/pageVoListProblemVo.ts
  31. 25 0
      src/api/models/problemCaseDto.ts
  32. 25 0
      src/api/models/problemCaseVo.ts
  33. 43 0
      src/api/models/problemDetailVo.ts
  34. 20 0
      src/api/models/problemDetailVoDifficulty.ts
  35. 19 0
      src/api/models/problemDetailVoIsPublic.ts
  36. 47 0
      src/api/models/problemDto.ts
  37. 20 0
      src/api/models/problemDtoDifficulty.ts
  38. 21 0
      src/api/models/problemDtoIsPublic.ts
  39. 26 0
      src/api/models/problemTestDto.ts
  40. 19 0
      src/api/models/problemTestDtoIsHidden.ts
  41. 26 0
      src/api/models/problemTestVo.ts
  42. 19 0
      src/api/models/problemTestVoIsHidden.ts
  43. 33 0
      src/api/models/problemVo.ts
  44. 20 0
      src/api/models/problemVoDifficulty.ts
  45. 19 0
      src/api/models/problemVoIsPublic.ts
  46. 34 0
      src/api/models/queryByPage1Params.ts
  47. 26 0
      src/api/models/queryByPage2Params.ts
  48. 18 0
      src/api/models/queryByPageParams.ts
  49. 23 0
      src/api/models/rankVo.ts
  50. 14 0
      src/api/models/removeProblemParams.ts
  51. 20 0
      src/api/models/responseContestDetailVo.ts
  52. 20 0
      src/api/models/responseContestVo.ts
  53. 20 0
      src/api/models/responseListCategoryVo.ts
  54. 20 0
      src/api/models/responseListContestRegistrationVo.ts
  55. 20 0
      src/api/models/responseListProblemTestVo.ts
  56. 20 0
      src/api/models/responseListRankVo.ts
  57. 20 0
      src/api/models/responsePageVoListContestVo.ts
  58. 20 0
      src/api/models/responsePageVoListProblemVo.ts
  59. 20 0
      src/api/models/responseProblemDetailVo.ts
  60. 2 2
      src/api/models/updateUserStatusDto.ts
  61. 21 0
      src/api/models/updateUserStatusDtoEnable.ts
  62. 3 2
      src/api/models/userDto.ts
  63. 21 0
      src/api/models/userDtoRole.ts
  64. 5 3
      src/api/models/userVo.ts
  65. 18 0
      src/api/models/userVoEnable.ts
  66. 19 0
      src/api/models/userVoRole.ts
  67. 198 0
      src/api/problem-controller.ts
  68. 25 0
      src/api/user-controller.ts
  69. 1 1
      src/view/LoginView.vue
  70. 5 5
      src/view/UserView.vue
  71. 38 0
      todo.md
  72. 219 0
      todoDetail.md

+ 6 - 4
src/api/auth-controller.ts

@@ -10,9 +10,10 @@ import { customAxiosInstance } from "../util/axios-instance";
 
 export const getAuthController = () => {
   /**
+   * 注册新账号,成功后直接返回 JWT token
    * @summary 用户注册
    */
-  const register = (registerDto: RegisterDto) => {
+  const register1 = (registerDto: RegisterDto) => {
     return customAxiosInstance<ResponseAuthTokenVo>({
       url: `/auth/register`,
       method: "POST",
@@ -21,6 +22,7 @@ export const getAuthController = () => {
     });
   };
   /**
+   * 使用账号密码登录,成功后返回 JWT token
    * @summary 用户登录
    */
   const login = (loginDto: LoginDto) => {
@@ -31,10 +33,10 @@ export const getAuthController = () => {
       data: loginDto,
     });
   };
-  return { register, login };
+  return { register1, login };
 };
-export type RegisterResult = NonNullable<
-  Awaited<ReturnType<ReturnType<typeof getAuthController>["register"]>>
+export type Register1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAuthController>["register1"]>>
 >;
 export type LoginResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getAuthController>["login"]>>

+ 71 - 0
src/api/category-controller.ts

@@ -0,0 +1,71 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  CategoryDto,
+  ResponseBoolean,
+  ResponseListCategoryVo,
+  _DeleteParams,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getCategoryController = () => {
+  /**
+   * @summary 查询分类列表
+   */
+  const list = () => {
+    return customAxiosInstance<ResponseListCategoryVo>({
+      url: `/category`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 修改分类
+   */
+  const edit3 = (categoryDto: CategoryDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/category`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: categoryDto,
+    });
+  };
+  /**
+   * @summary 新增分类
+   */
+  const add3 = (categoryDto: CategoryDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/category`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: categoryDto,
+    });
+  };
+  /**
+   * @summary 删除分类
+   */
+  const _delete = (params: _DeleteParams) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/category`,
+      method: "DELETE",
+      params,
+    });
+  };
+  return { list, edit3, add3, _delete };
+};
+export type ListResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getCategoryController>["list"]>>
+>;
+export type Edit3Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getCategoryController>["edit3"]>>
+>;
+export type Add3Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getCategoryController>["add3"]>>
+>;
+export type _DeleteResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getCategoryController>["_delete"]>>
+>;

+ 186 - 0
src/api/contest-controller.ts

@@ -0,0 +1,186 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  ContestDto,
+  ContestProblemDto,
+  ContestRegisterDto,
+  DeleteByIds1Params,
+  GetDetail1Params,
+  QueryByPage2Params,
+  RemoveProblemParams,
+  ResponseBoolean,
+  ResponseContestVo,
+  ResponseListContestRegistrationVo,
+  ResponseListRankVo,
+  ResponsePageVoListContestVo,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getContestController = () => {
+  /**
+   * @summary 分页查询比赛列表
+   */
+  const queryByPage2 = (params: QueryByPage2Params) => {
+    return customAxiosInstance<ResponsePageVoListContestVo>({
+      url: `/contest`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 修改比赛
+   */
+  const edit2 = (contestDto: ContestDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: contestDto,
+    });
+  };
+  /**
+   * @summary 新增比赛
+   */
+  const add2 = (contestDto: ContestDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: contestDto,
+    });
+  };
+  /**
+   * @summary 批量删除比赛
+   */
+  const deleteByIds1 = (params: DeleteByIds1Params) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest`,
+      method: "DELETE",
+      params,
+    });
+  };
+  /**
+   * @summary 修改比赛题目
+   */
+  const updateProblem = (id: number, contestProblemDto: ContestProblemDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest/${id}/problem`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: contestProblemDto,
+    });
+  };
+  /**
+   * @summary 比赛添加题目
+   */
+  const addProblem = (id: number, contestProblemDto: ContestProblemDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest/${id}/problem`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: contestProblemDto,
+    });
+  };
+  /**
+   * @summary 比赛移除题目
+   */
+  const removeProblem = (id: number, params: RemoveProblemParams) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest/${id}/problem`,
+      method: "DELETE",
+      params,
+    });
+  };
+  /**
+   * @summary 报名比赛
+   */
+  const register = (id: number, contestRegisterDto: ContestRegisterDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/contest/${id}/register`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: contestRegisterDto,
+    });
+  };
+  /**
+   * @summary 查询比赛详情
+   */
+  const getDetail1 = (id: number, params?: GetDetail1Params) => {
+    return customAxiosInstance<ResponseContestVo>({
+      url: `/contest/${id}`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 查询比赛报名名单
+   */
+  const queryRegistrations = (id: number) => {
+    return customAxiosInstance<ResponseListContestRegistrationVo>({
+      url: `/contest/${id}/registrations`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 查询比赛排行榜
+   */
+  const getRanking = (id: number) => {
+    return customAxiosInstance<ResponseListRankVo>({
+      url: `/contest/${id}/ranking`,
+      method: "GET",
+    });
+  };
+  return {
+    queryByPage2,
+    edit2,
+    add2,
+    deleteByIds1,
+    updateProblem,
+    addProblem,
+    removeProblem,
+    register,
+    getDetail1,
+    queryRegistrations,
+    getRanking,
+  };
+};
+export type QueryByPage2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["queryByPage2"]>>
+>;
+export type Edit2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["edit2"]>>
+>;
+export type Add2Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["add2"]>>
+>;
+export type DeleteByIds1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["deleteByIds1"]>>
+>;
+export type UpdateProblemResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["updateProblem"]>>
+>;
+export type AddProblemResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["addProblem"]>>
+>;
+export type RemoveProblemResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["removeProblem"]>>
+>;
+export type RegisterResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["register"]>>
+>;
+export type GetDetail1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["getDetail1"]>>
+>;
+export type QueryRegistrationsResult = NonNullable<
+  Awaited<
+    ReturnType<ReturnType<typeof getContestController>["queryRegistrations"]>
+  >
+>;
+export type GetRankingResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getContestController>["getRanking"]>>
+>;

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

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type _DeleteParams = {
+  /**
+   * 分类ID
+   */
+  id: number;
+};

+ 3 - 2
src/api/models/authTokenVo.ts

@@ -5,6 +5,7 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
+import type { AuthTokenVoRole } from "./authTokenVoRole";
 
 /**
  * 登录响应
@@ -12,8 +13,8 @@
 export interface AuthTokenVo {
   /** 账号 */
   account?: string;
-  /** 角色 */
-  role?: string;
+  /** 角色:user-用户,coach-教练,admin-管理员 */
+  role?: AuthTokenVoRole;
   /** JWT token */
   token?: string;
   /** 用户ID */

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 角色:user-用户,coach-教练,admin-管理员
+ */
+export type AuthTokenVoRole =
+  (typeof AuthTokenVoRole)[keyof typeof AuthTokenVoRole];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const AuthTokenVoRole = {
+  user: "user",
+  coach: "coach",
+  admin: "admin",
+} as const;

+ 22 - 0
src/api/models/categoryDto.ts

@@ -0,0 +1,22 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 题目分类新增和修改使用的DTO
+ */
+export interface CategoryDto {
+  /**
+   * 分类名称
+   * @minLength 1
+   */
+  categoryName: string;
+  /** 分类描述 */
+  description?: string;
+  /** 分类ID */
+  id?: number;
+}

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 题目分类响应展示对象
+ */
+export interface CategoryVo {
+  /** 分类名称 */
+  categoryName?: string;
+  /** 分类描述 */
+  description?: string;
+  /** 分类ID */
+  id?: number;
+}

+ 38 - 0
src/api/models/contestDetailVo.ts

@@ -0,0 +1,38 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ContestProblemVo } from "./contestProblemVo";
+import type { ContestDetailVoStatus } from "./contestDetailVoStatus";
+import type { ContestDetailVoType } from "./contestDetailVoType";
+
+/**
+ * 比赛详情响应展示对象
+ */
+export interface ContestDetailVo {
+  /** 创建时间 */
+  createdAt?: string;
+  /** 创建者用户名 */
+  creatorUsername?: string;
+  /** 比赛描述 */
+  description?: string;
+  /** 结束时间 */
+  endDatetime?: string;
+  /** 比赛ID */
+  id?: number;
+  /** 比赛题目列表 */
+  problems?: ContestProblemVo[];
+  /** 当前用户是否已报名 */
+  registered?: boolean;
+  /** 开始时间 */
+  startDatetime?: string;
+  /** 状态:pending-未开始,running-进行中,ended-已结束 */
+  status?: ContestDetailVoStatus;
+  /** 比赛标题 */
+  title?: string;
+  /** 比赛类型:public-公开,private-私有,password-密码访问 */
+  type?: ContestDetailVoType;
+}

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 状态:pending-未开始,running-进行中,ended-已结束
+ */
+export type ContestDetailVoStatus =
+  (typeof ContestDetailVoStatus)[keyof typeof ContestDetailVoStatus];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ContestDetailVoStatus = {
+  pending: "pending",
+  running: "running",
+  ended: "ended",
+} as const;

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛类型:public-公开,private-私有,password-密码访问
+ */
+export type ContestDetailVoType =
+  (typeof ContestDetailVoType)[keyof typeof ContestDetailVoType];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ContestDetailVoType = {
+  public: "public",
+  private: "private",
+  password: "password",
+} as const;

+ 34 - 0
src/api/models/contestDto.ts

@@ -0,0 +1,34 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ContestDtoType } from "./contestDtoType";
+
+/**
+ * 比赛新增和修改使用的DTO
+ */
+export interface ContestDto {
+  /** 比赛描述 */
+  description?: string;
+  /** 结束时间 */
+  endDatetime: string;
+  /** 比赛ID */
+  id?: number;
+  /** 访问密码(type为password时使用) */
+  password?: string;
+  /** 开始时间 */
+  startDatetime: string;
+  /**
+   * 比赛标题
+   * @minLength 1
+   */
+  title: string;
+  /**
+   * 比赛类型:public-公开,private-私有,password-密码访问
+   * @minLength 1
+   */
+  type: ContestDtoType;
+}

+ 21 - 0
src/api/models/contestDtoType.ts

@@ -0,0 +1,21 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛类型:public-公开,private-私有,password-密码访问
+ * @minLength 1
+ */
+export type ContestDtoType =
+  (typeof ContestDtoType)[keyof typeof ContestDtoType];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ContestDtoType = {
+  public: "public",
+  private: "private",
+  password: "password",
+} as const;

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛题目新增和修改使用的DTO
+ */
+export interface ContestProblemDto {
+  /** 题目在比赛中的显示序号 */
+  displayOrder: number;
+  /** 题目ID */
+  problemId: number;
+  /** 本题分值 */
+  score: number;
+}

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

@@ -0,0 +1,24 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ContestProblemVoDifficulty } from "./contestProblemVoDifficulty";
+
+/**
+ * 比赛题目响应展示对象
+ */
+export interface ContestProblemVo {
+  /** 难度:easy-简单,medium-中等,hard-困难 */
+  difficulty?: ContestProblemVoDifficulty;
+  /** 题目在比赛中的显示序号 */
+  displayOrder?: number;
+  /** 题目ID */
+  problemId?: number;
+  /** 本题分值 */
+  score?: number;
+  /** 题目标题 */
+  title?: string;
+}

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 难度:easy-简单,medium-中等,hard-困难
+ */
+export type ContestProblemVoDifficulty =
+  (typeof ContestProblemVoDifficulty)[keyof typeof ContestProblemVoDifficulty];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ContestProblemVoDifficulty = {
+  easy: "easy",
+  medium: "medium",
+  hard: "hard",
+} as const;

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

@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛报名使用的DTO
+ */
+export interface ContestRegisterDto {
+  /** 访问密码(密码型比赛需传入) */
+  password?: string;
+}

+ 21 - 0
src/api/models/contestRegistrationVo.ts

@@ -0,0 +1,21 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛报名记录响应展示对象
+ */
+export interface ContestRegistrationVo {
+  /** 报名时间 */
+  createdAt?: string;
+  /** 报名记录ID */
+  id?: number;
+  /** 用户ID */
+  userId?: number;
+  /** 用户名 */
+  username?: string;
+}

+ 29 - 0
src/api/models/contestVo.ts

@@ -0,0 +1,29 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ContestVoStatus } from "./contestVoStatus";
+import type { ContestVoType } from "./contestVoType";
+
+/**
+ * 比赛列表响应展示对象
+ */
+export interface ContestVo {
+  /** 创建时间 */
+  createdAt?: string;
+  /** 结束时间 */
+  endDatetime?: string;
+  /** 比赛ID */
+  id?: number;
+  /** 开始时间 */
+  startDatetime?: string;
+  /** 状态:pending-未开始,running-进行中,ended-已结束 */
+  status?: ContestVoStatus;
+  /** 比赛标题 */
+  title?: string;
+  /** 比赛类型:public-公开,private-私有,password-密码访问 */
+  type?: ContestVoType;
+}

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 状态:pending-未开始,running-进行中,ended-已结束
+ */
+export type ContestVoStatus =
+  (typeof ContestVoStatus)[keyof typeof ContestVoStatus];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ContestVoStatus = {
+  pending: "pending",
+  running: "running",
+  ended: "ended",
+} as const;

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛类型:public-公开,private-私有,password-密码访问
+ */
+export type ContestVoType = (typeof ContestVoType)[keyof typeof ContestVoType];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ContestVoType = {
+  public: "public",
+  private: "private",
+  password: "password",
+} as const;

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

@@ -7,5 +7,8 @@
  */
 
 export type DeleteByIdParams = {
+  /**
+   * 用户ID列表
+   */
   ids: string[];
 };

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

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

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

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type DeleteByIdsParams = {
+  /**
+   * 题目ID列表
+   */
+  ids: number[];
+};

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

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type DeleteCaseParams = {
+  /**
+   * 样例ID
+   */
+  id: number;
+};

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

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type DeleteTestParams = {
+  /**
+   * 评测用例ID
+   */
+  id: number;
+};

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

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type GetDetail1Params = {
+  /**
+   * 访问密码(密码型比赛需传入)
+   */
+  password?: string;
+};

+ 56 - 0
src/api/models/index.ts

@@ -6,21 +6,77 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 
+export * from "./_deleteParams";
 export * from "./authTokenVo";
+export * from "./authTokenVoRole";
+export * from "./categoryDto";
+export * from "./categoryVo";
+export * from "./contestDetailVo";
+export * from "./contestDetailVoStatus";
+export * from "./contestDetailVoType";
+export * from "./contestDto";
+export * from "./contestDtoType";
+export * from "./contestProblemDto";
+export * from "./contestProblemVo";
+export * from "./contestProblemVoDifficulty";
+export * from "./contestRegisterDto";
+export * from "./contestRegistrationVo";
+export * from "./contestVo";
+export * from "./contestVoStatus";
+export * from "./contestVoType";
 export * from "./deleteByIdParams";
+export * from "./deleteByIds1Params";
+export * from "./deleteByIdsParams";
+export * from "./deleteCaseParams";
+export * from "./deleteTestParams";
 export * from "./fieldError";
+export * from "./getDetail1Params";
 export * from "./loginDto";
+export * from "./pageVoListContestVo";
+export * from "./pageVoListProblemVo";
 export * from "./pageVoListUserVo";
+export * from "./problemCaseDto";
+export * from "./problemCaseVo";
+export * from "./problemDetailVo";
+export * from "./problemDetailVoDifficulty";
+export * from "./problemDetailVoIsPublic";
+export * from "./problemDto";
+export * from "./problemDtoDifficulty";
+export * from "./problemDtoIsPublic";
+export * from "./problemTestDto";
+export * from "./problemTestDtoIsHidden";
+export * from "./problemTestVo";
+export * from "./problemTestVoIsHidden";
+export * from "./problemVo";
+export * from "./problemVoDifficulty";
+export * from "./problemVoIsPublic";
+export * from "./queryByPage1Params";
+export * from "./queryByPage2Params";
 export * from "./queryByPageParams";
+export * from "./rankVo";
 export * from "./registerDto";
+export * from "./removeProblemParams";
 export * from "./response";
 export * from "./responseAuthTokenVo";
 export * from "./responseBoolean";
+export * from "./responseContestDetailVo";
+export * from "./responseContestVo";
+export * from "./responseListCategoryVo";
+export * from "./responseListContestRegistrationVo";
 export * from "./responseListFieldError";
+export * from "./responseListProblemTestVo";
+export * from "./responseListRankVo";
+export * from "./responsePageVoListContestVo";
+export * from "./responsePageVoListProblemVo";
 export * from "./responsePageVoListUserVo";
+export * from "./responseProblemDetailVo";
 export * from "./responseUserVo";
 export * from "./updateUserAvatarDto";
 export * from "./updateUserPasswordDto";
 export * from "./updateUserStatusDto";
+export * from "./updateUserStatusDtoEnable";
 export * from "./userDto";
+export * from "./userDtoRole";
 export * from "./userVo";
+export * from "./userVoEnable";
+export * from "./userVoRole";

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

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

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

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

+ 25 - 0
src/api/models/problemCaseDto.ts

@@ -0,0 +1,25 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 题目样例新增和修改使用的DTO
+ */
+export interface ProblemCaseDto {
+  /** 样例说明 */
+  description?: string;
+  /** 样例ID */
+  id?: number;
+  /** 样例输入 */
+  input?: string;
+  /** 样例输出 */
+  output?: string;
+  /** 所属题目ID */
+  problemId: number;
+  /** 显示顺序 */
+  sortOrder?: number;
+}

+ 25 - 0
src/api/models/problemCaseVo.ts

@@ -0,0 +1,25 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 题目样例响应展示对象
+ */
+export interface ProblemCaseVo {
+  /** 样例说明 */
+  description?: string;
+  /** 样例ID */
+  id?: number;
+  /** 样例输入 */
+  input?: string;
+  /** 样例输出 */
+  output?: string;
+  /** 所属题目ID */
+  problemId?: number;
+  /** 显示顺序 */
+  sortOrder?: number;
+}

+ 43 - 0
src/api/models/problemDetailVo.ts

@@ -0,0 +1,43 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ProblemCaseVo } from "./problemCaseVo";
+import type { CategoryVo } from "./categoryVo";
+import type { ProblemDetailVoDifficulty } from "./problemDetailVoDifficulty";
+import type { ProblemDetailVoIsPublic } from "./problemDetailVoIsPublic";
+
+/**
+ * 题目详情响应展示对象
+ */
+export interface ProblemDetailVo {
+  /** 通过次数 */
+  acceptedCount?: number;
+  /** 题目样例列表 */
+  cases?: ProblemCaseVo[];
+  /** 题目分类列表 */
+  categories?: CategoryVo[];
+  /** 创建时间 */
+  createdAt?: string;
+  /** 创建者用户名 */
+  createdByUsername?: string;
+  /** 题目描述 */
+  description?: string;
+  /** 难度:easy-简单,medium-中等,hard-困难 */
+  difficulty?: ProblemDetailVoDifficulty;
+  /** 题目ID */
+  id?: number;
+  /** 是否公开题目,0-未公开,1-公开 */
+  isPublic?: ProblemDetailVoIsPublic;
+  /** 内存限制(MB) */
+  memoryLimitMb?: number;
+  /** 时间限制(毫秒) */
+  timeLimitMs?: number;
+  /** 题目标题 */
+  title?: string;
+  /** 总提交次数 */
+  tryCount?: number;
+}

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 难度:easy-简单,medium-中等,hard-困难
+ */
+export type ProblemDetailVoDifficulty =
+  (typeof ProblemDetailVoDifficulty)[keyof typeof ProblemDetailVoDifficulty];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemDetailVoDifficulty = {
+  easy: "easy",
+  medium: "medium",
+  hard: "hard",
+} as const;

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 是否公开题目,0-未公开,1-公开
+ */
+export type ProblemDetailVoIsPublic =
+  (typeof ProblemDetailVoIsPublic)[keyof typeof ProblemDetailVoIsPublic];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemDetailVoIsPublic = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

+ 47 - 0
src/api/models/problemDto.ts

@@ -0,0 +1,47 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ProblemDtoDifficulty } from "./problemDtoDifficulty";
+import type { ProblemDtoIsPublic } from "./problemDtoIsPublic";
+
+/**
+ * 题目新增和修改使用的DTO
+ */
+export interface ProblemDto {
+  /** 题目分类ID列表 */
+  categoryIds?: number[];
+  /**
+   * 题目描述
+   * @minLength 1
+   */
+  description: string;
+  /** 难度:easy-简单,medium-中等,hard-困难 */
+  difficulty?: ProblemDtoDifficulty;
+  /** 题目ID */
+  id?: number;
+  /**
+   * 是否公开题目,0-未公开,1-公开
+   * @minimum 0
+   * @maximum 1
+   */
+  isPublic: ProblemDtoIsPublic;
+  /**
+   * 内存限制(MB)
+   * @minimum 1
+   */
+  memoryLimitMb: number;
+  /**
+   * 时间限制(毫秒)
+   * @minimum 1
+   */
+  timeLimitMs: number;
+  /**
+   * 题目标题
+   * @minLength 1
+   */
+  title: string;
+}

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 难度:easy-简单,medium-中等,hard-困难
+ */
+export type ProblemDtoDifficulty =
+  (typeof ProblemDtoDifficulty)[keyof typeof ProblemDtoDifficulty];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemDtoDifficulty = {
+  easy: "easy",
+  medium: "medium",
+  hard: "hard",
+} as const;

+ 21 - 0
src/api/models/problemDtoIsPublic.ts

@@ -0,0 +1,21 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 是否公开题目,0-未公开,1-公开
+ * @minimum 0
+ * @maximum 1
+ */
+export type ProblemDtoIsPublic =
+  (typeof ProblemDtoIsPublic)[keyof typeof ProblemDtoIsPublic];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemDtoIsPublic = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

+ 26 - 0
src/api/models/problemTestDto.ts

@@ -0,0 +1,26 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ProblemTestDtoIsHidden } from "./problemTestDtoIsHidden";
+
+/**
+ * 题目评测用例新增和修改使用的DTO
+ */
+export interface ProblemTestDto {
+  /** 评测用例ID */
+  id?: number;
+  /** 测试输入 */
+  input?: string;
+  /** 是否隐藏用例,0-不隐藏,1-隐藏 */
+  isHidden?: ProblemTestDtoIsHidden;
+  /** 测试输出 */
+  output?: string;
+  /** 所属题目ID */
+  problemId: number;
+  /** 判题顺序 */
+  sortOrder?: number;
+}

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 是否隐藏用例,0-不隐藏,1-隐藏
+ */
+export type ProblemTestDtoIsHidden =
+  (typeof ProblemTestDtoIsHidden)[keyof typeof ProblemTestDtoIsHidden];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemTestDtoIsHidden = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

+ 26 - 0
src/api/models/problemTestVo.ts

@@ -0,0 +1,26 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ProblemTestVoIsHidden } from "./problemTestVoIsHidden";
+
+/**
+ * 题目评测用例响应展示对象
+ */
+export interface ProblemTestVo {
+  /** 评测用例ID */
+  id?: number;
+  /** 测试输入 */
+  input?: string;
+  /** 是否隐藏用例,0-不隐藏,1-隐藏 */
+  isHidden?: ProblemTestVoIsHidden;
+  /** 测试输出 */
+  output?: string;
+  /** 所属题目ID */
+  problemId?: number;
+  /** 判题顺序 */
+  sortOrder?: number;
+}

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 是否隐藏用例,0-不隐藏,1-隐藏
+ */
+export type ProblemTestVoIsHidden =
+  (typeof ProblemTestVoIsHidden)[keyof typeof ProblemTestVoIsHidden];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemTestVoIsHidden = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

+ 33 - 0
src/api/models/problemVo.ts

@@ -0,0 +1,33 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type { ProblemVoDifficulty } from "./problemVoDifficulty";
+import type { ProblemVoIsPublic } from "./problemVoIsPublic";
+
+/**
+ * 题目列表响应展示对象
+ */
+export interface ProblemVo {
+  /** 通过次数 */
+  acceptedCount?: number;
+  /** 创建时间 */
+  createdAt?: string;
+  /** 难度:easy-简单,medium-中等,hard-困难 */
+  difficulty?: ProblemVoDifficulty;
+  /** 题目ID */
+  id?: number;
+  /** 是否公开题目,0-未公开,1-公开 */
+  isPublic?: ProblemVoIsPublic;
+  /** 内存限制(MB) */
+  memoryLimitMb?: number;
+  /** 时间限制(毫秒) */
+  timeLimitMs?: number;
+  /** 题目标题 */
+  title?: string;
+  /** 总提交次数 */
+  tryCount?: number;
+}

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

@@ -0,0 +1,20 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 难度:easy-简单,medium-中等,hard-困难
+ */
+export type ProblemVoDifficulty =
+  (typeof ProblemVoDifficulty)[keyof typeof ProblemVoDifficulty];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemVoDifficulty = {
+  easy: "easy",
+  medium: "medium",
+  hard: "hard",
+} as const;

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 是否公开题目,0-未公开,1-公开
+ */
+export type ProblemVoIsPublic =
+  (typeof ProblemVoIsPublic)[keyof typeof ProblemVoIsPublic];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const ProblemVoIsPublic = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

+ 34 - 0
src/api/models/queryByPage1Params.ts

@@ -0,0 +1,34 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type QueryByPage1Params = {
+  /**
+   * 题目标题,模糊查询
+   */
+  title?: string;
+  /**
+   * 难度:easy-简单,medium-中等,hard-困难
+   */
+  difficulty?: string;
+  /**
+   * 分类ID
+   */
+  categoryId?: number;
+  /**
+   * 是否公开:1-公开,0-未公开
+   */
+  isPublic?: number;
+  /**
+   * 页码
+   */
+  pageNum?: number;
+  /**
+   * 分页大小
+   */
+  pageSize?: number;
+};

+ 26 - 0
src/api/models/queryByPage2Params.ts

@@ -0,0 +1,26 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type QueryByPage2Params = {
+  /**
+   * 比赛标题,模糊查询
+   */
+  title?: string;
+  /**
+   * 状态:pending-未开始,running-进行中,ended-已结束
+   */
+  status?: string;
+  /**
+   * 页码
+   */
+  pageNum?: number;
+  /**
+   * 分页大小
+   */
+  pageSize?: number;
+};

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

@@ -7,10 +7,28 @@
  */
 
 export type QueryByPageParams = {
+  /**
+   * 账号,模糊查询
+   */
   account: string;
+  /**
+   * 用户名,模糊查询
+   */
   username: string;
+  /**
+   * 角色:user-用户,coach-教练,admin-管理员
+   */
   role: string;
+  /**
+   * 状态:1-启用,0-禁用
+   */
   enable?: number;
+  /**
+   * 页码
+   */
   pageNum?: number;
+  /**
+   * 分页大小
+   */
   pageSize?: number;
 };

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

@@ -0,0 +1,23 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 比赛排行榜响应展示对象
+ */
+export interface RankVo {
+  /** AC最大耗时(分钟) */
+  maxAcMinutes?: number;
+  /** 报名时间 */
+  regTime?: string;
+  /** 总得分 */
+  totalScore?: number;
+  /** 用户ID */
+  userId?: number;
+  /** 用户名 */
+  username?: string;
+}

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

@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+export type RemoveProblemParams = {
+  /**
+   * 题目ID
+   */
+  problemId: number;
+};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 2 - 2
src/api/models/updateUserStatusDto.ts

@@ -5,6 +5,7 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
+import type { UpdateUserStatusDtoEnable } from "./updateUserStatusDtoEnable";
 
 /**
  * 用户状态更新使用的DTO
@@ -12,11 +13,10 @@
 export interface UpdateUserStatusDto {
   /**
    * 更新后的状态;1:启用,0:禁用
-   * @minLength 1
    * @minimum 0
    * @maximum 1
    */
-  enable: number;
+  enable: UpdateUserStatusDtoEnable;
   /**
    * 用户ID
    * @minLength 1

+ 21 - 0
src/api/models/updateUserStatusDtoEnable.ts

@@ -0,0 +1,21 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 更新后的状态;1:启用,0:禁用
+ * @minimum 0
+ * @maximum 1
+ */
+export type UpdateUserStatusDtoEnable =
+  (typeof UpdateUserStatusDtoEnable)[keyof typeof UpdateUserStatusDtoEnable];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const UpdateUserStatusDtoEnable = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

+ 3 - 2
src/api/models/userDto.ts

@@ -5,6 +5,7 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
+import type { UserDtoRole } from "./userDtoRole";
 
 /**
  * 用户新增和修改使用的DTO
@@ -25,11 +26,11 @@ export interface UserDto {
    */
   password: string;
   /**
-   * 用户角色
+   * 用户角色:user-用户,coach-教练,admin-管理员
    * @minLength 4
    * @maxLength 32
    */
-  role: string;
+  role: UserDtoRole;
   /**
    * 用户名称
    * @minLength 2

+ 21 - 0
src/api/models/userDtoRole.ts

@@ -0,0 +1,21 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 用户角色:user-用户,coach-教练,admin-管理员
+ * @minLength 4
+ * @maxLength 32
+ */
+export type UserDtoRole = (typeof UserDtoRole)[keyof typeof UserDtoRole];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const UserDtoRole = {
+  user: "user",
+  coach: "coach",
+  admin: "admin",
+} as const;

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

@@ -5,6 +5,8 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
+import type { UserVoEnable } from "./userVoEnable";
+import type { UserVoRole } from "./userVoRole";
 
 /**
  * 用户响应展示对象
@@ -15,11 +17,11 @@ export interface UserVo {
   /** 用户头像地址 */
   avatar?: string;
   /** 用户状态;0:禁用,1:启用 */
-  enable?: number;
+  enable?: UserVoEnable;
   /** 用户ID */
   id?: string;
-  /** 用户角色 */
-  role?: string;
+  /** 用户角色:user-用户,coach-教练,admin-管理员 */
+  role?: UserVoRole;
   /** 用户名称 */
   username?: string;
 }

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

@@ -0,0 +1,18 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 用户状态;0:禁用,1:启用
+ */
+export type UserVoEnable = (typeof UserVoEnable)[keyof typeof UserVoEnable];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const UserVoEnable = {
+  NUMBER_0: 0,
+  NUMBER_1: 1,
+} as const;

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

@@ -0,0 +1,19 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 用户角色:user-用户,coach-教练,admin-管理员
+ */
+export type UserVoRole = (typeof UserVoRole)[keyof typeof UserVoRole];
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const UserVoRole = {
+  user: "user",
+  coach: "coach",
+  admin: "admin",
+} as const;

+ 198 - 0
src/api/problem-controller.ts

@@ -0,0 +1,198 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  DeleteByIdsParams,
+  DeleteCaseParams,
+  DeleteTestParams,
+  ProblemCaseDto,
+  ProblemDto,
+  ProblemTestDto,
+  QueryByPage1Params,
+  ResponseBoolean,
+  ResponseListProblemTestVo,
+  ResponsePageVoListProblemVo,
+  ResponseProblemDetailVo,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getProblemController = () => {
+  /**
+   * @summary 分页查询题目列表
+   */
+  const queryByPage1 = (params: QueryByPage1Params) => {
+    return customAxiosInstance<ResponsePageVoListProblemVo>({
+      url: `/problem`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 修改题目
+   */
+  const edit1 = (problemDto: ProblemDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: problemDto,
+    });
+  };
+  /**
+   * @summary 新增题目
+   */
+  const add1 = (problemDto: ProblemDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: problemDto,
+    });
+  };
+  /**
+   * @summary 批量删除题目
+   */
+  const deleteByIds = (params: DeleteByIdsParams) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem`,
+      method: "DELETE",
+      params,
+    });
+  };
+  /**
+   * @summary 修改题目评测用例
+   */
+  const updateTest = (problemTestDto: ProblemTestDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem/test`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: problemTestDto,
+    });
+  };
+  /**
+   * @summary 删除题目评测用例
+   */
+  const deleteTest = (params: DeleteTestParams) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem/test`,
+      method: "DELETE",
+      params,
+    });
+  };
+  /**
+   * @summary 修改题目样例
+   */
+  const updateCase = (problemCaseDto: ProblemCaseDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem/case`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: problemCaseDto,
+    });
+  };
+  /**
+   * @summary 删除题目样例
+   */
+  const deleteCase = (params: DeleteCaseParams) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem/case`,
+      method: "DELETE",
+      params,
+    });
+  };
+  /**
+   * @summary 查询题目评测用例列表
+   */
+  const queryTests = (id: number) => {
+    return customAxiosInstance<ResponseListProblemTestVo>({
+      url: `/problem/${id}/test`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 新增题目评测用例
+   */
+  const addTest = (id: number, problemTestDto: ProblemTestDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem/${id}/test`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: problemTestDto,
+    });
+  };
+  /**
+   * @summary 新增题目样例
+   */
+  const addCase = (id: number, problemCaseDto: ProblemCaseDto) => {
+    return customAxiosInstance<ResponseBoolean>({
+      url: `/problem/${id}/case`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: problemCaseDto,
+    });
+  };
+  /**
+   * @summary 查询题目详情
+   */
+  const getDetail = (id: number) => {
+    return customAxiosInstance<ResponseProblemDetailVo>({
+      url: `/problem/${id}`,
+      method: "GET",
+    });
+  };
+  return {
+    queryByPage1,
+    edit1,
+    add1,
+    deleteByIds,
+    updateTest,
+    deleteTest,
+    updateCase,
+    deleteCase,
+    queryTests,
+    addTest,
+    addCase,
+    getDetail,
+  };
+};
+export type QueryByPage1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["queryByPage1"]>>
+>;
+export type Edit1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["edit1"]>>
+>;
+export type Add1Result = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["add1"]>>
+>;
+export type DeleteByIdsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["deleteByIds"]>>
+>;
+export type UpdateTestResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["updateTest"]>>
+>;
+export type DeleteTestResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["deleteTest"]>>
+>;
+export type UpdateCaseResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["updateCase"]>>
+>;
+export type DeleteCaseResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["deleteCase"]>>
+>;
+export type QueryTestsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["queryTests"]>>
+>;
+export type AddTestResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["addTest"]>>
+>;
+export type AddCaseResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["addCase"]>>
+>;
+export type GetDetailResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getProblemController>["getDetail"]>>
+>;

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

@@ -20,6 +20,10 @@ import type {
 import { customAxiosInstance } from "../util/axios-instance";
 
 export const getUserController = () => {
+  /**
+   * 按账号、用户名、角色、状态分页查询用户
+   * @summary 分页查询用户信息
+   */
   const queryByPage = (params: QueryByPageParams) => {
     return customAxiosInstance<ResponsePageVoListUserVo>({
       url: `/user`,
@@ -27,6 +31,9 @@ export const getUserController = () => {
       params,
     });
   };
+  /**
+   * @summary 编辑用户
+   */
   const edit = (userDto: UserDto) => {
     return customAxiosInstance<ResponseBoolean>({
       url: `/user`,
@@ -35,6 +42,9 @@ export const getUserController = () => {
       data: userDto,
     });
   };
+  /**
+   * @summary 新增用户
+   */
   const add = (userDto: UserDto) => {
     return customAxiosInstance<ResponseBoolean>({
       url: `/user`,
@@ -43,6 +53,9 @@ export const getUserController = () => {
       data: userDto,
     });
   };
+  /**
+   * @summary 删除用户
+   */
   const deleteById = (params: DeleteByIdParams) => {
     return customAxiosInstance<ResponseBoolean>({
       url: `/user`,
@@ -50,6 +63,9 @@ export const getUserController = () => {
       params,
     });
   };
+  /**
+   * @summary 更新用户状态
+   */
   const updateUserStatus = (updateUserStatusDto: UpdateUserStatusDto) => {
     return customAxiosInstance<Response>({
       url: `/user/updateStatus`,
@@ -58,6 +74,9 @@ export const getUserController = () => {
       data: updateUserStatusDto,
     });
   };
+  /**
+   * @summary 更新用户密码
+   */
   const updatePassword = (updateUserPasswordDto: UpdateUserPasswordDto) => {
     return customAxiosInstance<Response>({
       url: `/user/updatePassword`,
@@ -66,6 +85,9 @@ export const getUserController = () => {
       data: updateUserPasswordDto,
     });
   };
+  /**
+   * @summary 更新用户头像
+   */
   const updateUserAvatar = (updateUserAvatarDto: UpdateUserAvatarDto) => {
     return customAxiosInstance<Response>({
       url: `/user/updateAvatar`,
@@ -74,6 +96,9 @@ export const getUserController = () => {
       data: updateUserAvatarDto,
     });
   };
+  /**
+   * @summary 查询用户详情
+   */
   const queryById = (id: string) => {
     return customAxiosInstance<ResponseUserVo>({
       url: `/user/${id}`,

+ 1 - 1
src/view/LoginView.vue

@@ -230,7 +230,7 @@ const handleRegister = async () => {
   registerLoading.value = true
   try {
     const authApi = getAuthController()
-    const res = await authApi.register({
+    const res = await authApi.register1({
       account: registerForm.account,
       password: registerForm.password,
       username: registerForm.username

+ 5 - 5
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 { QueryByPageParams, UpdateUserStatusDto, 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'
@@ -25,7 +25,7 @@ const userForm = reactive<Partial<UserDto>>({
   username: '',
   account: '',
   password: '',
-  role: ''
+  role: undefined
 })
 const formRules: FormRules = {
   username: [
@@ -120,7 +120,7 @@ const handleSizeChange = (size: number) => {
 // 打开新增
 const handleAdd = () => {
   dialogTitle.value = '新增用户'
-  Object.assign(userForm, { id: undefined, username: '', account: '', password: '', role: '' })
+  Object.assign(userForm, { id: undefined, username: '', account: '', password: '', role: undefined })
   dialogVisible.value = true
 }
 
@@ -248,9 +248,9 @@ const handleBatchDelete = () => {
 // 切换启用状态
 const handleToggleEnable = async (row: UserVo, newEnable: number) => {
   const oldEnable = row.enable
-  row.enable = newEnable
+  row.enable = newEnable as UserVo['enable']
   try {
-    const res = await api.updateUserStatus({ id: row.id!, enable: newEnable })
+    const res = await api.updateUserStatus({ id: row.id!, enable: newEnable as UpdateUserStatusDto['enable'] })
     if (res?.code === 0 || res?.code === 200) {
       ElMessage.success('状态更新成功')
     } else {

+ 38 - 0
todo.md

@@ -0,0 +1,38 @@
+# 客户端开发计划 (todo)
+
+> 实现顺序:题目模块 → 比赛模块。提交/判题模块本期不实现:题目详情页暂不加代码编辑器;比赛排名接口数据为空(待提交模块)。后端每完成一批接口先 `npm run api:gen`。
+
+## Phase 0 路由与权限扩展
+- [ ] router 新增 `meta.requiresCoach`(admin | coach 可访问),保留 `requiresAdmin`(仅 admin,用户管理)
+- [ ] AdminLayout:菜单按角色过滤;UserLayout:后台管理入口对 admin 和 coach 均显示
+- Commit(ui): `# feat: 路由与布局支持coach后台入口`
+
+## Phase 1 题目模块 · API生成 + 用户浏览页
+- [ ] `npm run api:gen` → problem-controller.ts / category-controller.ts + models
+- [ ] `view/problem/ProblemListView.vue`(/problems):标题/难度/分类搜索、分页、难度与公开状态标签
+- [ ] `view/problem/ProblemDetailView.vue`(/problems/:id):描述、时间/内存限制、提交/通过数、样例、分类标签
+- [ ] 注册 userRoutes + 顶部导航菜单
+- Commit(ui): `# feat: 题目列表与详情页`
+
+## Phase 2 题目模块 · 管理后台页
+- [ ] `view/admin/ProblemManageView.vue`(/admin/problems):仿 UserView,搜索卡+表格+弹窗;编辑时维护分类勾选;详情弹窗管理样例与评测用例(含隐藏标记)
+- [ ] `view/admin/CategoryManageView.vue`(/admin/categories):分类 CRUD
+- [ ] 注册 adminRoutes + 侧边菜单
+- Commit(ui): `# feat: 题目管理与分类管理后台页面`
+
+## Phase 3 比赛模块 · API生成 + 用户浏览页
+- [ ] `npm run api:gen` → contest-controller.ts + models
+- [ ] `view/contest/ContestListView.vue`(/contests):标题/状态搜索、类型标签、起止时间、分页
+- [ ] `view/contest/ContestDetailView.vue`(/contests/:id):基本信息、题目列表(题号/分值)、报名(password 型先弹密码框再拉取)、已报名/比赛状态判断
+- [ ] `view/contest/ContestRankingView.vue`(/contests/:id/ranking):排名表格(名次/用户/总分/AC最大耗时)
+- [ ] 注册 userRoutes + 导航菜单
+- Commit(ui): `# feat: 比赛列表、详情与排名页面`
+
+## Phase 4 比赛模块 · 管理后台页
+- [ ] `view/admin/ContestManageView.vue`(/admin/contests):比赛 CRUD、题目配置弹窗(多选题目+displayOrder+score)、报名名单
+- [ ] 注册 adminRoutes + 侧边菜单
+- Commit(ui): `# feat: 比赛管理后台页面`
+
+## Phase 5 验证
+- [ ] `npm run build`(vue-tsc 严格模式)
+- [ ] `npm run dev` 手工联调:游客浏览公开题 → 登录 → 教练建题/配样例/配用例/建比赛 → 用户报名 → 排名页空列表正常显示

+ 219 - 0
todoDetail.md

@@ -0,0 +1,219 @@
+# 客户端开发计划 · 详细实现规划 (todoDetail)
+
+> 结合后端实际(`serve_template` 题目/比赛模块已全部实现并运行中,Swagger 在线 `:8080/v3/api-docs`)与前端现状(仅完成 auth + 用户管理)。
+> 实施节奏:一次性完成 Phase 0–5,最后统一 `npm run build` + 手工联调。
+> 提交风格沿用仓库实际约定:`# feat: ...` / `# api: ...` / `# style: ...`(todo.md 中的 `Commit(ui):` 为标注,不作为 commit message 前缀)。
+
+## 全局前置结论(必须遵守)
+
+| 事项 | 结论 |
+|---|---|
+| API 生成 | 后端已全部完成,**只需在 Phase 1 开头跑一次** `npm run api:gen`,一次性生成 `problem-controller.ts` / `category-controller.ts` / `contest-controller.ts` / `user-controller.ts` / `auth-controller.ts` + 全部 models |
+| 生成物约束 | `src/api/models/` 与 `src/api/*.ts` 由 orval `clean:true` 生成,**禁止手改**;所有手写扩展类型放 `src/type/` 或组件内 |
+| 类型缺口 | Swagger 将 `GET /contest/{id}` 声明为 `Response<ContestVo>`,真实返回 `ContestDetailVo`(继承 ContestVo,多 `description/creatorUsername/registered/problems`);`ContestProblemVo` 无 schema。**必须手写扩展类型并强转** |
+| 响应体 | 统一 `Response<T>`:`code` 200=成功,400=错误;`axios-instance.ts` 已兼容 code 0/200,无需改动 |
+| 角色 | 后端角色常量:`user` / `coach` / `admin`(`User.java`)。教练相关接口均用 `hasAnyRole('ROLE_coach','ROLE_admin')` |
+| 状态/难度/类型常量 | 比赛状态 `pending/running/ended`;比赛类型 `public/private/password`;题目难度 `easy/medium/hard`;`isPublic`、`isHidden` 均为 0/1 |
+
+---
+
+## Phase 0 路由与权限扩展
+
+### 改动清单
+
+1. `src/type/index.ts`
+   - `User.role` 联合类型由 `'admin' | 'user' | 'guest'` 改为 `'admin' | 'coach' | 'user' | 'guest'`(当前缺 `coach`,是 TS 硬伤,登录后角色即来自 `AuthTokenVo.role`)。
+
+2. `src/router/index.ts`
+   - 新增 `requiresCoach` 语义:admin 或 coach 可访问;`requiresAdmin` 保留为仅 admin。
+   - 路由守卫 `beforeEach` 增加分支:
+     ```
+     if (to.matched.some(r => r.meta.requiresAdmin))  -> 仅 admin,否则拒绝
+     else if (to.matched.some(r => r.meta.requiresCoach)) -> admin 或 coach,否则拒绝
+     未登录一律跳 /login?redirect=fullPath
+     ```
+   - `adminRoutes` 重构:
+     - 父节点(`/admin`)meta 去掉 `requiresAdmin`,改为 `requiresCoach`(否则 coach 进不了后台)。
+     - 用户管理子项保留 `requiresAdmin`(仅 admin)。
+     - 删除占位路由 `/admin/other`(组件为 `AdminLayout` 嵌套自身,属隐藏 bug)。
+     - 父节点增加默认跳转 `redirect: '/admin/problems'`(Phase 2 注册后生效;暂可先指 `/admin/users`)。
+
+3. `src/layout/AdminLayout.vue`
+   - `menus` 计算属性按角色过滤子路由:
+     - `meta.requiresAdmin` → 仅 admin 显示;
+     - `meta.requiresCoach` → admin|coach 显示;
+     - 无权限标记项不显示。
+
+4. `src/layout/UserLayout.vue`
+   - `isAdmin` 改为 `isStaff = computed(() => ['admin','coach'].includes(role))`;
+   - "后台管理"菜单项 `v-if="isStaff"`。
+
+### 验证
+- `npm run build` 通过。
+- 三种账号(admin/coach/user)登录后查看后台入口与后台菜单差异。
+
+### Commit
+`# feat: 路由与布局支持coach后台入口`
+
+---
+
+## Phase 1 题目模块 · API生成 + 用户浏览页
+
+### Step 1.1 `npm run api:gen`
+- 前置:后端在线(`curl http://localhost:8080/v3/api-docs` 返回 200)。
+- 生成后确认新文件:`src/api/problem-controller.ts`、`src/api/category-controller.ts`、`src/api/contest-controller.ts`;models 新增 `problemVo`、`problemDetailVo`、`problemDto`、`problemCaseVo/Dto`、`problemTestVo/Dto`、`categoryVo/Dto`、`contestVo/Dto`、`contestProblemDto`、`contestRegisterDto`、`contestRegistrationVo`、`rankVo`、`pageVoListProblemVo`、`pageVoListContestVo`、`responseContestVo` 等。
+
+### Step 1.2 `src/view/problem/ProblemListView.vue`(`/problems`)
+- 搜索卡:标题(模糊)、难度下拉(easy/medium/hard)、分类下拉(数据源 `GET /category` → `CategoryVo[]`)、公开状态(isPublic 0/1,可空)。
+- 列表:`GET /problem`,参数 `title/difficulty/categoryId/isPublic/pageNum/pageSize` → `ResponsePageVoListProblemVo`。
+- 行展示:标题、难度标签、公开标签、时间/内存限制、通过/提交数;行点击跳详情。
+- 分页复用 `UserView.vue` 的 `el-pagination` 模式。
+
+### Step 1.3 `src/view/problem/ProblemDetailView.vue`(`/problems/:id`)
+- `GET /problem/{id}`(游客可访问,SecurityConfig 已开放)→ `ProblemDetailVo`。
+- 展示:标题、难度/公开标签、`timeLimitMs`(转秒/ms 展示)、`memoryLimitMb`、`tryCount`/`acceptedCount`、`description`、分类标签、样例列表 `cases`(input/output/description/sortOrder)。
+- 后端字段 `createdByUsername` 可作作者展示。
+
+### Step 1.4 路由与菜单
+- userRoutes 新增两条:`/problems`、`/problems/:id`(meta 带 title 与 icon,icon 可选用 `Document` / `Files`)。
+- UserLayout 顶部导航自动渲染(现有 `menus` 逻辑复用 userRoutes[0].children)。
+
+### Commit
+- `# api: 题目与比赛模块api(通过orval生成)`(Step 1.1 单独提交)
+- `# feat: 题目列表与详情页`
+
+---
+
+## Phase 2 题目模块 · 管理后台页
+
+### Step 2.1 `src/view/admin/ProblemManageView.vue`(`/admin/problems`)
+- 整体仿 `UserView.vue`:搜索卡 + 工具栏 + 表格 + 弹窗 + 分页。
+- 列表搜索:标题、难度、公开状态(isPublic)、分类。
+- 新增/编辑弹窗(`ProblemDto`):标题、描述(textarea)、时间限制(ms)、内存限制(MB)、难度、是否公开;**分类勾选** `categoryIds: number[]`(el-checkbox-group / el-select multiple)。
+- **详情弹窗**(样例与评测用例管理,核心):
+  - 样例 Tab:数据取 `GET /problem/{id}` 的 `cases`;操作 `POST /problem/{id}/case`(body `ProblemCaseDto` 含 problemId)、`PUT /problem/case`、`DELETE /problem/case?id=`。字段:input/output/description/sortOrder。
+  - 评测用例 Tab:`GET /problem/{id}/test` → `ProblemTestVo[]`;操作 `POST /problem/{id}/test`、`PUT /problem/test`、`DELETE /problem/test?id=`。字段:input/output/sortOrder、**`isHidden` 隐藏标记(0/1,el-switch)**。该接口仅 coach/admin 可见。
+- **删除按钮仅 admin 显示**(后端 `DELETE /problem` 为 `hasRole('ROLE_admin')`)。
+
+### Step 2.2 `src/view/admin/CategoryManageView.vue`(`/admin/categories`)
+- CRUD:`GET /category`、`POST /category`、`PUT /category`(body `CategoryDto`:id/categoryName/description)、`DELETE /category?id=`(**单 id 查询参数**,与 user 的 `ids` 数组签名不同,以 orval 生成为准)。
+- 删除按钮 admin-only(coach 可增改)。
+
+### Step 2.3 路由与菜单
+- adminRoutes 新增 `/admin/problems`、`/admin/categories`,meta 标 `requiresCoach` + icon(`Collection` 等)。
+- AdminLayout 侧边菜单自动按角色过滤。
+
+### Commit
+`# feat: 题目管理与分类管理后台页面`
+
+---
+
+## Phase 3 比赛模块 · 用户浏览页
+
+### Step 3.1 手写扩展类型(新增 `src/type/contest.ts`)
+```ts
+import type { ContestVo } from '@/api/models'
+
+export interface ContestProblem {
+  problemId: number
+  title: string
+  difficulty: string
+  displayOrder: number
+  score: number
+}
+
+export interface ContestDetail extends ContestVo {
+  description?: string
+  creatorUsername?: string
+  registered?: boolean
+  problems?: ContestProblem[]
+}
+```
+组件内强转:`const detail = res.data as unknown as ContestDetail`(生成类型 `ResponseContestVo.data` 为 `ContestVo`)。
+
+### Step 3.2 `src/view/contest/ContestListView.vue`(`/contests`)
+- 搜索:标题、状态(pending/running/ended)。
+- `GET /contest`(游客可访问,仅见 public 型)→ `ResponsePageVoListContestVo`。
+- 行展示:标题、type 标签(public/private/password)、起止时间(ISO 格式化 `YYYY-MM-DD HH:mm`)、状态标签、分页。
+
+### Step 3.3 `src/view/contest/ContestDetailView.vue`(`/contests/:id`)
+- **需登录**(`/contest/{id}` 未对游客开放):未登录跳 `/login?redirect=...`;401 由 axios 拦截器兜底跳登录。
+- 详情流程:
+  1. `GET /contest/{id}`(不带 password);
+  2. 若 `type==='password'` 且返回体 **无 `problems` 字段**(锁定态 base VO)→ 弹密码框 → 带 `?password=` 重拉;
+  3. private 型无权限会返回错误消息(code 400),提示后返回。
+- 展示:标题、type/status 标签、起止时间、description、creatorUsername、**题目列表**(displayOrder 题号 / title / difficulty / score)。
+- 报名:
+  - 条件:`status` 为 pending/running 且 `registered !== true`。
+  - 点击 `POST /contest/{id}/register`,body `ContestRegisterDto { password? }`(密码型需再次传密码,后端会校验)。
+  - 成功重拉详情刷新 `registered`。
+- 已结束/进行中差异提示;进入排名入口链接到 `/contests/:id/ranking`。
+
+### Step 3.4 `src/view/contest/ContestRankingView.vue`(`/contests/:id/ranking`)
+- `GET /contest/{id}/ranking`(需登录)→ `RankVo[]`(userId/username/totalScore/maxAcMinutes/regTime)。
+- 表格:名次(序号)/用户/总分/AC最大耗时;**空列表显示空态提示**(无提交模块属正常)。
+- 需要比赛基本信息时可复用 `GET /contest/{id}`。
+
+### Step 3.5 路由与菜单
+- userRoutes 新增 `/contests`、`/contests/:id`、`/contests/:id/ranking`;菜单加"比赛"(icon `Trophy` 等)。
+
+### Commit
+`# feat: 比赛列表、详情与排名页面`
+
+---
+
+## Phase 4 比赛模块 · 管理后台页
+
+### Step 4.1 `src/view/admin/ContestManageView.vue`(`/admin/contests`)
+- 搜索 + 表格:标题、类型、状态、起止时间。
+- CRUD 弹窗(`ContestDto`):
+  - title / description / type(public/private/password)/ password(type=password 时必填)/ startDatetime / endDatetime。
+  - **时间控件**:`el-date-picker type="datetime" value-format="YYYY-MM-DDTHH:mm:ss"`,对齐后端 `LocalDateTime` 反序列化(ISO 格式)。
+  - 校验:start < end。
+- **题目配置弹窗**(核心):
+  - 左侧 `GET /problem`(分页 + 标题搜索 + 多选)选题目;
+  - 对已选题设置 `displayOrder`(显示序号)+ `score`(分值);
+  - 提交:逐条 `POST /contest/{id}/problem`(新增)/ `PUT /contest/{id}/problem`(改)/ `DELETE /contest/{id}/problem?problemId=`(移除),body `ContestProblemDto { problemId, score, displayOrder }`。
+  - 配置态展示可由 `GET /contest/{id}`(含 problems)回显。
+- **报名名单**:`GET /contest/{id}/registrations`(coach/admin)→ `ContestRegistrationVo[]`(id/userId/username/createdAt)表格,独立弹窗或 Tab。
+- 注意:比赛开始后后端禁止修改(`demand.md`),操作失败按后端消息提示。
+
+### Step 4.2 路由与菜单
+- adminRoutes 新增 `/admin/contests`,meta `requiresCoach`;侧边菜单自动渲染。
+
+### Commit
+`# feat: 比赛管理后台页面`
+
+---
+
+## Phase 5 验证
+
+### 5.1 构建
+- `npm run build`(vue-tsc 严格模式)全绿。
+- 重点排查点:`noUnusedLocals/noUnusedParameters` 严格报错;`ContestDetail` 强转写法;orval 生成类型使用是否正确。
+- 可选 `npx prettier --write .` 统一格式。
+
+### 5.2 手工联调(`npm run dev`,需后端在线)
+完整链路:
+1. 游客:浏览公开题目列表/详情、比赛列表 → 访问比赛详情/排名被引导登录。
+2. 登录(user 角色):查看题目详情(含样例)、比赛详情、报名 public 比赛(密码型走密码弹框)、排名页空态正常。
+3. 教练:后台入口可见(无"用户管理"菜单);建题 → 配样例 → 配评测用例(含隐藏标记)→ 建比赛 → 配题设分 → 查看报名名单;coach 无删除按钮。
+4. 管理员:后台可见"用户管理";删除题目/分类/比赛可用。
+5. 角色隔离:user 访问 `/admin/**` 被拒;coach 访问 `/admin/users` 被拒。
+
+### 5.3 收尾(可选)
+- 更新 `/home/yangyi/OJ/AGENTS.md`("仅 User 域实现"已过时,题目/比赛模块已上线)。
+
+---
+
+## 风险与注意点清单
+
+1. **`/contest/{id}` 类型不符**:Swagger 类型是 `ContestVo`,真实返回 `ContestDetailVo`,必须用 `src/type/contest.ts` 扩展 + 强转,不能依赖生成 models。
+2. **`coach` 角色缺失**:`src/type/index.ts` 未含 `coach`,Phase 0 必须补,否则登录为 coach 时 TS 报错。
+3. **时间格式**:前端提交比赛时间用 `YYYY-MM-DDTHH:mm:ss`,后端 `LocalDateTime` 才可反序列化;展示时 ISO → 可读格式。
+4. **删除权限差异**:题目/分类删除仅 admin,比赛删除 coach/admin(service 限制只能删自己创建的);前端按角色隐藏按钮,后端仍会兜底校验。
+5. **单删 vs 批量删参数**:user/contest 删除用 `ids` 数组;category/case/test 删除用单个 `id` 查询参数,按 orval 生成签名调用。
+6. **密码型比赛锁定态判定**:以返回体是否含 `problems` 字段区分 base VO 与 detail(Swagger 未提供显式标记)。
+7. **`api:gen` 前置**:需后端在线;`clean:true` 会重写全部 controller/models,属正常。
+8. **`/admin` 无默认页**:Phase 0 加 redirect;点击 UserLayout"后台管理"应先跳到默认后台页。
+9. **排名为空属正常**:提交/判题模块本期不实现,`RankVo[]` 空数组给空态 UI 即可。