2 İşlemeler a6a7b88f8f ... 7e15a03cc8

Yazar SHA1 Mesaj Tarih
  yangyi 7e15a03cc8 feat: post create/edit with Quill editor via route pages 2 gün önce
  yangyi c818b4f36d feat:管理后台路由权限校验使用更细粒度的校验,按用户角色进行校验;完善用户管理;对接服务端API参数使用具体的类型; 2 gün önce
59 değiştirilmiş dosya ile 847 ekleme ve 288 silme
  1. 74 0
      package-lock.json
  2. 1 0
      package.json
  3. 3 7
      src/api/attachment-controller.ts
  4. 29 4
      src/api/meta-controller.ts
  5. 0 9
      src/api/models/adminRechargeBody.ts
  6. 19 0
      src/api/models/adminRechargeDto.ts
  7. 0 9
      src/api/models/applyWithdrawBody.ts
  8. 0 9
      src/api/models/createAndPayBody.ts
  9. 5 2
      src/api/models/createPostVo.ts
  10. 15 0
      src/api/models/createTipOrderDto.ts
  11. 19 0
      src/api/models/createTipOrderVo.ts
  12. 26 18
      src/api/models/index.ts
  13. 25 0
      src/api/models/ossConfigDto.ts
  14. 31 0
      src/api/models/profileVo.ts
  15. 17 0
      src/api/models/realnameReviewDto.ts
  16. 27 0
      src/api/models/realnameSubmitDto.ts
  17. 7 1
      src/api/models/rechargeDto.ts
  18. 3 3
      src/api/models/responseCreatePostVo.ts
  19. 3 3
      src/api/models/responseCreateTipOrderVo.ts
  20. 3 3
      src/api/models/responseProfileVo.ts
  21. 2 2
      src/api/models/responseString.ts
  22. 20 0
      src/api/models/responseUnreadCountVo.ts
  23. 20 0
      src/api/models/responseUploadVo.ts
  24. 20 0
      src/api/models/responseWalletVo.ts
  25. 20 0
      src/api/models/responseWithdrawApplyVo.ts
  26. 0 9
      src/api/models/reviewBody.ts
  27. 0 9
      src/api/models/reviewWithdrawBody.ts
  28. 0 9
      src/api/models/submitBody.ts
  29. 15 0
      src/api/models/unreadCountVo.ts
  30. 0 9
      src/api/models/updateConfigBody.ts
  31. 0 9
      src/api/models/updateHitStatusBody.ts
  32. 18 0
      src/api/models/updateHitStatusDto.ts
  33. 0 9
      src/api/models/updateRoleBody.ts
  34. 18 0
      src/api/models/updateRoleDto.ts
  35. 0 9
      src/api/models/updateViewCountBody.ts
  36. 15 0
      src/api/models/updateViewCountDto.ts
  37. 5 2
      src/api/models/uploadVo.ts
  38. 8 0
      src/api/models/userDto.ts
  39. 2 0
      src/api/models/userVo.ts
  40. 5 2
      src/api/models/walletVo.ts
  41. 19 0
      src/api/models/withdrawApplyVo.ts
  42. 15 0
      src/api/models/withdrawDto.ts
  43. 17 0
      src/api/models/withdrawReviewDto.ts
  44. 5 5
      src/api/notification-controller.ts
  45. 16 16
      src/api/post-controller.ts
  46. 10 10
      src/api/realname-auth-controller.ts
  47. 0 41
      src/api/system-config-controller.ts
  48. 7 7
      src/api/tip-order-controller.ts
  49. 8 9
      src/api/user-controller.ts
  50. 27 28
      src/api/wallet-controller.ts
  51. 8 4
      src/layout/AdminLayout.vue
  52. 28 8
      src/router/index.ts
  53. 3 3
      src/store/notification.ts
  54. 2 2
      src/store/user.ts
  55. 7 8
      src/store/wallet.ts
  56. 149 0
      src/view/PostEditorView.vue
  57. 15 2
      src/view/PostManageView.vue
  58. 12 3
      src/view/SiteSettingsView.vue
  59. 24 5
      src/view/UserView.vue

+ 74 - 0
package-lock.json

@@ -8,6 +8,7 @@
       "name": "ui",
       "version": "0.0.0",
       "dependencies": {
+        "@vueup/vue-quill": "^1.5.3",
         "axios": "^1.15.2",
         "element-plus": "^2.13.6",
         "orval": "7.0",
@@ -2102,6 +2103,19 @@
         }
       }
     },
+    "node_modules/@vueup/vue-quill": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/@vueup/vue-quill/-/vue-quill-1.5.3.tgz",
+      "integrity": "sha512-iklLMFjyChKGmYAUqoIx2t1rigCUSilDo1xJpYk9FLBmXPV3lmsy7fgbYZ11DNbXXGP6TXaQBQhgIy1uHo2rpA==",
+      "license": "MIT",
+      "dependencies": {
+        "quill": "2.0.2 || >=2.0.4 <3",
+        "quill-delta": "^5.1.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.41"
+      }
+    },
     "node_modules/@vueuse/core": {
       "version": "12.0.0",
       "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.0.0.tgz",
@@ -3012,6 +3026,12 @@
         "node": ">=6"
       }
     },
+    "node_modules/eventemitter3": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+      "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
+      "license": "MIT"
+    },
     "node_modules/execa": {
       "version": "5.1.1",
       "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
@@ -3047,6 +3067,12 @@
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
+    "node_modules/fast-diff": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+      "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+      "license": "Apache-2.0"
+    },
     "node_modules/fast-glob": {
       "version": "3.3.3",
       "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -4033,6 +4059,12 @@
         "lodash-es": "*"
       }
     },
+    "node_modules/lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+      "license": "MIT"
+    },
     "node_modules/lodash.get": {
       "version": "4.4.2",
       "resolved": "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -4044,6 +4076,13 @@
       "resolved": "https://registry.npmmirror.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
       "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
     },
+    "node_modules/lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+      "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+      "license": "MIT"
+    },
     "node_modules/lodash.omit": {
       "version": "4.18.0",
       "resolved": "https://registry.npmmirror.com/lodash.omit/-/lodash.omit-4.18.0.tgz",
@@ -4534,6 +4573,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/parchment": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
+      "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/path-browserify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -4723,6 +4768,35 @@
         }
       ]
     },
+    "node_modules/quill": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
+      "integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "eventemitter3": "^5.0.1",
+        "lodash-es": "^4.17.21",
+        "parchment": "^3.0.0",
+        "quill-delta": "^5.1.0"
+      },
+      "engines": {
+        "npm": ">=8.2.3"
+      }
+    },
+    "node_modules/quill-delta": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
+      "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-diff": "^1.3.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.isequal": "^4.5.0"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      }
+    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",

+ 1 - 0
package.json

@@ -10,6 +10,7 @@
     "api:gen": "orval"
   },
   "dependencies": {
+    "@vueup/vue-quill": "^1.5.3",
     "axios": "^1.15.2",
     "element-plus": "^2.13.6",
     "orval": "7.0",

+ 3 - 7
src/api/attachment-controller.ts

@@ -5,11 +5,7 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
-import type {
-  ResponseMapStringString,
-  UploadBody,
-  UploadParams,
-} from "./models";
+import type { ResponseUploadVo, UploadBody, UploadParams } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
 
 export const getAttachmentController = () => {
@@ -17,8 +13,8 @@ export const getAttachmentController = () => {
    * @summary 上传附件到OSS(需对接OSS配置)
    */
   const upload = (uploadBody: UploadBody, params?: UploadParams) => {
-    return customAxiosInstance<ResponseMapStringString>({
-      url: `/api/attachments/upload`,
+    return customAxiosInstance<ResponseUploadVo>({
+      url: `/attachments/upload`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
       data: uploadBody,

+ 29 - 4
src/api/meta-controller.ts

@@ -5,29 +5,54 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
-import type { Response, WebsiteMetaDto } from "./models";
+import type {
+  OssConfigDto,
+  ResponseObject,
+  ResponseVoid,
+  WebsiteMetaDto,
+} from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
 
 export const getMetaController = () => {
   const updateWebsiteMeta = (websiteMetaDto: WebsiteMetaDto) => {
-    return customAxiosInstance<Response>({
+    return customAxiosInstance<ResponseVoid>({
       url: `/meta/updateWebsiteMeta`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
       data: websiteMetaDto,
     });
   };
+  const updateOssConfig = (ossConfigDto: OssConfigDto) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/meta/updateOssConfig`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: ossConfigDto,
+    });
+  };
   const getWebsiteMeta = () => {
-    return customAxiosInstance<Response>({
+    return customAxiosInstance<ResponseObject>({
       url: `/meta/getWebsiteMeta`,
       method: "GET",
     });
   };
-  return { updateWebsiteMeta, getWebsiteMeta };
+  const getOssConfig = () => {
+    return customAxiosInstance<ResponseObject>({
+      url: `/meta/getOssConfig`,
+      method: "GET",
+    });
+  };
+  return { updateWebsiteMeta, updateOssConfig, getWebsiteMeta, getOssConfig };
 };
 export type UpdateWebsiteMetaResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getMetaController>["updateWebsiteMeta"]>>
 >;
+export type UpdateOssConfigResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getMetaController>["updateOssConfig"]>>
+>;
 export type GetWebsiteMetaResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getMetaController>["getWebsiteMeta"]>>
 >;
+export type GetOssConfigResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getMetaController>["getOssConfig"]>>
+>;

+ 0 - 9
src/api/models/adminRechargeBody.ts

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

+ 19 - 0
src/api/models/adminRechargeDto.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 AdminRechargeDto {
+  /** 充值金额 */
+  amount: number;
+  /** 备注 */
+  remark?: string;
+  /** 目标用户ID */
+  userId: number;
+}

+ 0 - 9
src/api/models/applyWithdrawBody.ts

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

+ 0 - 9
src/api/models/createAndPayBody.ts

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

+ 5 - 2
src/api/models/responseMapStringObjectData.ts → src/api/models/createPostVo.ts

@@ -7,6 +7,9 @@
  */
 
 /**
- * 响应的具体数据
+ * 创建帖子结果视图
  */
-export type ResponseMapStringObjectData = { [key: string]: unknown };
+export interface CreatePostVo {
+  /** 帖子ID */
+  id?: string;
+}

+ 15 - 0
src/api/models/createTipOrderDto.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
+ */
+
+/**
+ * 打赏请求参数
+ */
+export interface CreateTipOrderDto {
+  /** 帖子ID */
+  postId: number;
+}

+ 19 - 0
src/api/models/createTipOrderVo.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 CreateTipOrderVo {
+  /** 金额 */
+  amount?: number;
+  /** 订单ID */
+  orderId?: string;
+  /** 状态 */
+  status?: string;
+}

+ 26 - 18
src/api/models/index.ts

@@ -6,10 +6,11 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 
-export * from "./adminRechargeBody";
-export * from "./applyWithdrawBody";
+export * from "./adminRechargeDto";
 export * from "./authTokenVo";
-export * from "./createAndPayBody";
+export * from "./createPostVo";
+export * from "./createTipOrderDto";
+export * from "./createTipOrderVo";
 export * from "./deleteByIdParams";
 export * from "./fieldError";
 export * from "./getTransactionsParams";
@@ -20,49 +21,56 @@ export * from "./listPreviousPostsParams";
 export * from "./loginDto";
 export * from "./notificationVo";
 export * from "./orderTipVo";
+export * from "./ossConfigDto";
 export * from "./pageVoListOrderTipVo";
 export * from "./pageVoListPostVo";
 export * from "./pageVoListUserVo";
 export * from "./postDto";
 export * from "./postVo";
+export * from "./profileVo";
 export * from "./queryByPageParams";
 export * from "./realnameAuthVo";
-export * from "./rechargeBody";
+export * from "./realnameReviewDto";
+export * from "./realnameSubmitDto";
+export * from "./rechargeDto";
 export * from "./registerDto";
-export * from "./response";
 export * from "./responseAuthTokenVo";
 export * from "./responseBoolean";
+export * from "./responseCreatePostVo";
+export * from "./responseCreateTipOrderVo";
 export * from "./responseListFieldError";
 export * from "./responseListNotificationVo";
 export * from "./responseListRealnameAuthVo";
 export * from "./responseListWalletTransactionVo";
-export * from "./responseMapStringLong";
-export * from "./responseMapStringLongData";
-export * from "./responseMapStringObject";
-export * from "./responseMapStringObjectData";
-export * from "./responseMapStringString";
-export * from "./responseMapStringStringData";
 export * from "./responseObject";
 export * from "./responsePageVoListOrderTipVo";
 export * from "./responsePageVoListPostVo";
 export * from "./responsePageVoListUserVo";
 export * from "./responsePostVo";
+export * from "./responseProfileVo";
 export * from "./responseRealnameAuthVo";
+export * from "./responseString";
+export * from "./responseUnreadCountVo";
+export * from "./responseUploadVo";
 export * from "./responseUserVo";
 export * from "./responseVoid";
-export * from "./reviewBody";
-export * from "./reviewWithdrawBody";
-export * from "./submitBody";
-export * from "./updateConfigBody";
-export * from "./updateHitStatusBody";
-export * from "./updateRoleBody";
+export * from "./responseWalletVo";
+export * from "./responseWithdrawApplyVo";
+export * from "./unreadCountVo";
+export * from "./updateHitStatusDto";
+export * from "./updateRoleDto";
 export * from "./updateUserAvatarDto";
 export * from "./updateUserPasswordDto";
 export * from "./updateUserStatusDto";
-export * from "./updateViewCountBody";
+export * from "./updateViewCountDto";
 export * from "./uploadBody";
 export * from "./uploadParams";
+export * from "./uploadVo";
 export * from "./userDto";
 export * from "./userVo";
 export * from "./walletTransactionVo";
+export * from "./walletVo";
 export * from "./websiteMetaDto";
+export * from "./withdrawApplyVo";
+export * from "./withdrawDto";
+export * from "./withdrawReviewDto";

+ 25 - 0
src/api/models/ossConfigDto.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
+ */
+
+/**
+ * OSS配置DTO,用于更新OSS存储配置信息
+ */
+export interface OssConfigDto {
+  /** Access Key */
+  accessKey?: string;
+  /** Bucket */
+  bucket?: string;
+  /** Endpoint */
+  endpoint?: string;
+  /** 公开访问域名 */
+  publicDomain?: string;
+  /** Region */
+  region?: string;
+  /** Secret Key */
+  secretKey?: string;
+}

+ 31 - 0
src/api/models/profileVo.ts

@@ -0,0 +1,31 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 用户个人信息视图
+ */
+export interface ProfileVo {
+  /** 账号 */
+  account?: string;
+  /** 头像URL */
+  avatar?: string;
+  /** 余额 */
+  balance?: number;
+  /** 用户ID */
+  id?: string;
+  /** 是否实名认证 */
+  isRealname?: boolean;
+  /** 会员等级 */
+  level?: string;
+  /** 手机号 */
+  phoneNumber?: string;
+  /** 角色 */
+  role?: string;
+  /** 用户名 */
+  username?: string;
+}

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

@@ -0,0 +1,17 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 实名认证审核请求参数
+ */
+export interface RealnameReviewDto {
+  /** 是否通过 */
+  approved: boolean;
+  /** 驳回原因 */
+  rejectReason?: string;
+}

+ 27 - 0
src/api/models/realnameSubmitDto.ts

@@ -0,0 +1,27 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 实名认证提交请求参数
+ */
+export interface RealnameSubmitDto {
+  /**
+   * 身份证号
+   * @minLength 1
+   */
+  idCard: string;
+  /** 身份证反面照URL */
+  idCardBack?: string;
+  /** 身份证正面照URL */
+  idCardFront?: string;
+  /**
+   * 真实姓名
+   * @minLength 1
+   */
+  realName: string;
+}

+ 7 - 1
src/api/models/rechargeBody.ts → src/api/models/rechargeDto.ts

@@ -6,4 +6,10 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 
-export type RechargeBody = { [key: string]: number };
+/**
+ * 充值请求参数
+ */
+export interface RechargeDto {
+  /** 充值金额 */
+  amount: number;
+}

+ 3 - 3
src/api/models/responseMapStringLong.ts → src/api/models/responseCreatePostVo.ts

@@ -5,16 +5,16 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
-import type { ResponseMapStringLongData } from "./responseMapStringLongData";
+import type { CreatePostVo } from "./createPostVo";
 
 /**
  * 后端统一的响应实体
  */
-export interface ResponseMapStringLong {
+export interface ResponseCreatePostVo {
   /** 状态码;200:成功 */
   code?: number;
   /** 响应的具体数据 */
-  data?: ResponseMapStringLongData;
+  data?: CreatePostVo;
   /** 响应附加信息 */
   message?: string;
 }

+ 3 - 3
src/api/models/responseMapStringString.ts → src/api/models/responseCreateTipOrderVo.ts

@@ -5,16 +5,16 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
-import type { ResponseMapStringStringData } from "./responseMapStringStringData";
+import type { CreateTipOrderVo } from "./createTipOrderVo";
 
 /**
  * 后端统一的响应实体
  */
-export interface ResponseMapStringString {
+export interface ResponseCreateTipOrderVo {
   /** 状态码;200:成功 */
   code?: number;
   /** 响应的具体数据 */
-  data?: ResponseMapStringStringData;
+  data?: CreateTipOrderVo;
   /** 响应附加信息 */
   message?: string;
 }

+ 3 - 3
src/api/models/responseMapStringObject.ts → src/api/models/responseProfileVo.ts

@@ -5,16 +5,16 @@
  * Serve应用接口文档
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
-import type { ResponseMapStringObjectData } from "./responseMapStringObjectData";
+import type { ProfileVo } from "./profileVo";
 
 /**
  * 后端统一的响应实体
  */
-export interface ResponseMapStringObject {
+export interface ResponseProfileVo {
   /** 状态码;200:成功 */
   code?: number;
   /** 响应的具体数据 */
-  data?: ResponseMapStringObjectData;
+  data?: ProfileVo;
   /** 响应附加信息 */
   message?: string;
 }

+ 2 - 2
src/api/models/response.ts → src/api/models/responseString.ts

@@ -9,11 +9,11 @@
 /**
  * 后端统一的响应实体
  */
-export interface Response {
+export interface ResponseString {
   /** 状态码;200:成功 */
   code?: number;
   /** 响应的具体数据 */
-  data?: unknown;
+  data?: string;
   /** 响应附加信息 */
   message?: string;
 }

+ 20 - 0
src/api/models/responseUnreadCountVo.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 { UnreadCountVo } from "./unreadCountVo";
+
+/**
+ * 后端统一的响应实体
+ */
+export interface ResponseUnreadCountVo {
+  /** 状态码;200:成功 */
+  code?: number;
+  /** 响应的具体数据 */
+  data?: UnreadCountVo;
+  /** 响应附加信息 */
+  message?: string;
+}

+ 20 - 0
src/api/models/responseUploadVo.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 { UploadVo } from "./uploadVo";
+
+/**
+ * 后端统一的响应实体
+ */
+export interface ResponseUploadVo {
+  /** 状态码;200:成功 */
+  code?: number;
+  /** 响应的具体数据 */
+  data?: UploadVo;
+  /** 响应附加信息 */
+  message?: string;
+}

+ 20 - 0
src/api/models/responseWalletVo.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 { WalletVo } from "./walletVo";
+
+/**
+ * 后端统一的响应实体
+ */
+export interface ResponseWalletVo {
+  /** 状态码;200:成功 */
+  code?: number;
+  /** 响应的具体数据 */
+  data?: WalletVo;
+  /** 响应附加信息 */
+  message?: string;
+}

+ 20 - 0
src/api/models/responseWithdrawApplyVo.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 { WithdrawApplyVo } from "./withdrawApplyVo";
+
+/**
+ * 后端统一的响应实体
+ */
+export interface ResponseWithdrawApplyVo {
+  /** 状态码;200:成功 */
+  code?: number;
+  /** 响应的具体数据 */
+  data?: WithdrawApplyVo;
+  /** 响应附加信息 */
+  message?: string;
+}

+ 0 - 9
src/api/models/reviewBody.ts

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

+ 0 - 9
src/api/models/reviewWithdrawBody.ts

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

+ 0 - 9
src/api/models/submitBody.ts

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

+ 15 - 0
src/api/models/unreadCountVo.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
+ */
+
+/**
+ * 未读通知数量视图
+ */
+export interface UnreadCountVo {
+  /** 未读数量 */
+  count?: number;
+}

+ 0 - 9
src/api/models/updateConfigBody.ts

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

+ 0 - 9
src/api/models/updateHitStatusBody.ts

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

+ 18 - 0
src/api/models/updateHitStatusDto.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
+ */
+
+/**
+ * 更新帖子命中状态请求参数
+ */
+export interface UpdateHitStatusDto {
+  /**
+   * 命中状态(pending/hit/miss)
+   * @minLength 1
+   */
+  hitStatus: string;
+}

+ 0 - 9
src/api/models/updateRoleBody.ts

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

+ 18 - 0
src/api/models/updateRoleDto.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
+ */
+
+/**
+ * 更新用户角色请求参数
+ */
+export interface UpdateRoleDto {
+  /**
+   * 角色(user/expert/admin)
+   * @minLength 1
+   */
+  role: string;
+}

+ 0 - 9
src/api/models/updateViewCountBody.ts

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

+ 15 - 0
src/api/models/updateViewCountDto.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
+ */
+
+/**
+ * 修改帖子查看人数请求参数
+ */
+export interface UpdateViewCountDto {
+  /** 新的查看人数 */
+  viewCount: number;
+}

+ 5 - 2
src/api/models/responseMapStringStringData.ts → src/api/models/uploadVo.ts

@@ -7,6 +7,9 @@
  */
 
 /**
- * 响应的具体数据
+ * 附件上传结果视图
  */
-export type ResponseMapStringStringData = { [key: string]: string };
+export interface UploadVo {
+  /** 文件URL */
+  url?: string;
+}

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

@@ -16,6 +16,8 @@ export interface UserDto {
    * @maxLength 20
    */
   account: string;
+  /** 用户头像地址 */
+  avatar?: string;
   /** 用户ID */
   id?: string;
   /**
@@ -24,6 +26,12 @@ export interface UserDto {
    * @maxLength 20
    */
   password: string;
+  /**
+   * 用户手机号
+   * @minLength 11
+   * @maxLength 11
+   */
+  phoneNumber?: string;
   /**
    * 用户角色
    * @minLength 4

+ 2 - 0
src/api/models/userVo.ts

@@ -18,6 +18,8 @@ export interface UserVo {
   enable?: number;
   /** 用户ID */
   id?: string;
+  /** 用户手机号 */
+  phoneNumber?: string;
   /** 用户角色 */
   role?: string;
   /** 用户名称 */

+ 5 - 2
src/api/models/responseMapStringLongData.ts → src/api/models/walletVo.ts

@@ -7,6 +7,9 @@
  */
 
 /**
- * 响应的具体数据
+ * 钱包信息视图
  */
-export type ResponseMapStringLongData = { [key: string]: number };
+export interface WalletVo {
+  /** 余额 */
+  balance?: number;
+}

+ 19 - 0
src/api/models/withdrawApplyVo.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 WithdrawApplyVo {
+  /** 金额 */
+  amount?: number;
+  /** 状态 */
+  status?: string;
+  /** 交易ID */
+  transactionId?: string;
+}

+ 15 - 0
src/api/models/withdrawDto.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
+ */
+
+/**
+ * 提现请求参数
+ */
+export interface WithdrawDto {
+  /** 提现金额 */
+  amount: number;
+}

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

@@ -0,0 +1,17 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+
+/**
+ * 提现审核请求参数
+ */
+export interface WithdrawReviewDto {
+  /** 是否通过 */
+  approved: boolean;
+  /** 驳回原因 */
+  rejectReason?: string;
+}

+ 5 - 5
src/api/notification-controller.ts

@@ -7,7 +7,7 @@
  */
 import type {
   ResponseListNotificationVo,
-  ResponseMapStringLong,
+  ResponseUnreadCountVo,
   ResponseVoid,
 } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
@@ -18,7 +18,7 @@ export const getNotificationController = () => {
    */
   const markRead = (id: number) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/notifications/${id}/read`,
+      url: `/notifications/${id}/read`,
       method: "PUT",
     });
   };
@@ -27,7 +27,7 @@ export const getNotificationController = () => {
    */
   const listNotifications = () => {
     return customAxiosInstance<ResponseListNotificationVo>({
-      url: `/api/notifications`,
+      url: `/notifications`,
       method: "GET",
     });
   };
@@ -35,8 +35,8 @@ export const getNotificationController = () => {
    * @summary 获取未读数
    */
   const getUnreadCount = () => {
-    return customAxiosInstance<ResponseMapStringLong>({
-      url: `/api/notifications/unread-count`,
+    return customAxiosInstance<ResponseUnreadCountVo>({
+      url: `/notifications/unread-count`,
       method: "GET",
     });
   };

+ 16 - 16
src/api/post-controller.ts

@@ -9,12 +9,12 @@ import type {
   ListPostsParams,
   ListPreviousPostsParams,
   PostDto,
-  ResponseMapStringObject,
+  ResponseCreatePostVo,
   ResponsePageVoListPostVo,
   ResponsePostVo,
   ResponseVoid,
-  UpdateHitStatusBody,
-  UpdateViewCountBody,
+  UpdateHitStatusDto,
+  UpdateViewCountDto,
 } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
 
@@ -24,7 +24,7 @@ export const getPostController = () => {
    */
   const getPostDetail = (id: number) => {
     return customAxiosInstance<ResponsePostVo>({
-      url: `/api/posts/${id}`,
+      url: `/posts/${id}`,
       method: "GET",
     });
   };
@@ -33,7 +33,7 @@ export const getPostController = () => {
    */
   const updatePost = (id: number, postDto: PostDto) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/posts/${id}`,
+      url: `/posts/${id}`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
       data: postDto,
@@ -44,7 +44,7 @@ export const getPostController = () => {
    */
   const deletePost = (id: number) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/posts/${id}`,
+      url: `/posts/${id}`,
       method: "DELETE",
     });
   };
@@ -53,13 +53,13 @@ export const getPostController = () => {
    */
   const updateViewCount = (
     id: number,
-    updateViewCountBody: UpdateViewCountBody,
+    updateViewCountDto: UpdateViewCountDto,
   ) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/posts/${id}/view-count`,
+      url: `/posts/${id}/view-count`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
-      data: updateViewCountBody,
+      data: updateViewCountDto,
     });
   };
   /**
@@ -67,13 +67,13 @@ export const getPostController = () => {
    */
   const updateHitStatus = (
     id: number,
-    updateHitStatusBody: UpdateHitStatusBody,
+    updateHitStatusDto: UpdateHitStatusDto,
   ) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/posts/${id}/hit-status`,
+      url: `/posts/${id}/hit-status`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
-      data: updateHitStatusBody,
+      data: updateHitStatusDto,
     });
   };
   /**
@@ -81,7 +81,7 @@ export const getPostController = () => {
    */
   const listPosts = (params?: ListPostsParams) => {
     return customAxiosInstance<ResponsePageVoListPostVo>({
-      url: `/api/posts`,
+      url: `/posts`,
       method: "GET",
       params,
     });
@@ -90,8 +90,8 @@ export const getPostController = () => {
    * @summary 创建帖子(仅专家/管理员)
    */
   const createPost = (postDto: PostDto) => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/posts`,
+    return customAxiosInstance<ResponseCreatePostVo>({
+      url: `/posts`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
       data: postDto,
@@ -105,7 +105,7 @@ export const getPostController = () => {
     params?: ListPreviousPostsParams,
   ) => {
     return customAxiosInstance<ResponsePageVoListPostVo>({
-      url: `/api/posts/expert/${expertId}/previous`,
+      url: `/posts/expert/${expertId}/previous`,
       method: "GET",
       params,
     });

+ 10 - 10
src/api/realname-auth-controller.ts

@@ -6,11 +6,11 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 import type {
+  RealnameReviewDto,
+  RealnameSubmitDto,
   ResponseListRealnameAuthVo,
   ResponseRealnameAuthVo,
   ResponseVoid,
-  ReviewBody,
-  SubmitBody,
 } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
 
@@ -18,12 +18,12 @@ export const getRealnameAuthController = () => {
   /**
    * @summary 管理员审核实名认证
    */
-  const review = (id: number, reviewBody: ReviewBody) => {
+  const review = (id: number, realnameReviewDto: RealnameReviewDto) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/realname/${id}/review`,
+      url: `/realname/${id}/review`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
-      data: reviewBody,
+      data: realnameReviewDto,
     });
   };
   /**
@@ -31,19 +31,19 @@ export const getRealnameAuthController = () => {
    */
   const getMyAuth = () => {
     return customAxiosInstance<ResponseRealnameAuthVo>({
-      url: `/api/realname`,
+      url: `/realname`,
       method: "GET",
     });
   };
   /**
    * @summary 提交实名认证
    */
-  const submit = (submitBody: SubmitBody) => {
+  const submit = (realnameSubmitDto: RealnameSubmitDto) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/realname`,
+      url: `/realname`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
-      data: submitBody,
+      data: realnameSubmitDto,
     });
   };
   /**
@@ -51,7 +51,7 @@ export const getRealnameAuthController = () => {
    */
   const listPending = () => {
     return customAxiosInstance<ResponseListRealnameAuthVo>({
-      url: `/api/realname/pending`,
+      url: `/realname/pending`,
       method: "GET",
     });
   };

+ 0 - 41
src/api/system-config-controller.ts

@@ -1,41 +0,0 @@
-/**
- * Generated by orval v7.0.1 🍺
- * Do not edit manually.
- * Serve API
- * Serve应用接口文档
- * OpenAPI spec version: 0.0.1-SNAPSHOT
- */
-import type { ResponseObject, ResponseVoid, UpdateConfigBody } from "./models";
-import { customAxiosInstance } from "../util/axios-instance";
-
-export const getSystemConfigController = () => {
-  /**
-   * @summary 获取配置
-   */
-  const getConfig = (key: string) => {
-    return customAxiosInstance<ResponseObject>({
-      url: `/api/config/${key}`,
-      method: "GET",
-    });
-  };
-  /**
-   * @summary 更新配置(管理员)
-   */
-  const updateConfig = (key: string, updateConfigBody: UpdateConfigBody) => {
-    return customAxiosInstance<ResponseVoid>({
-      url: `/api/config/${key}`,
-      method: "PUT",
-      headers: { "Content-Type": "application/json" },
-      data: updateConfigBody,
-    });
-  };
-  return { getConfig, updateConfig };
-};
-export type GetConfigResult = NonNullable<
-  Awaited<ReturnType<ReturnType<typeof getSystemConfigController>["getConfig"]>>
->;
-export type UpdateConfigResult = NonNullable<
-  Awaited<
-    ReturnType<ReturnType<typeof getSystemConfigController>["updateConfig"]>
-  >
->;

+ 7 - 7
src/api/tip-order-controller.ts

@@ -6,9 +6,9 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 import type {
-  CreateAndPayBody,
+  CreateTipOrderDto,
   ListOrdersParams,
-  ResponseMapStringObject,
+  ResponseCreateTipOrderVo,
   ResponsePageVoListOrderTipVo,
 } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
@@ -17,12 +17,12 @@ export const getTipOrderController = () => {
   /**
    * @summary 打赏并即时扣款
    */
-  const createAndPay = (createAndPayBody: CreateAndPayBody) => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/orders/tip`,
+  const createAndPay = (createTipOrderDto: CreateTipOrderDto) => {
+    return customAxiosInstance<ResponseCreateTipOrderVo>({
+      url: `/orders/tip`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
-      data: createAndPayBody,
+      data: createTipOrderDto,
     });
   };
   /**
@@ -30,7 +30,7 @@ export const getTipOrderController = () => {
    */
   const listOrders = (params?: ListOrdersParams) => {
     return customAxiosInstance<ResponsePageVoListOrderTipVo>({
-      url: `/api/orders`,
+      url: `/orders`,
       method: "GET",
       params,
     });

+ 8 - 9
src/api/user-controller.ts

@@ -8,13 +8,12 @@
 import type {
   DeleteByIdParams,
   QueryByPageParams,
-  Response,
   ResponseBoolean,
-  ResponseMapStringObject,
   ResponsePageVoListUserVo,
+  ResponseProfileVo,
   ResponseUserVo,
   ResponseVoid,
-  UpdateRoleBody,
+  UpdateRoleDto,
   UpdateUserAvatarDto,
   UpdateUserPasswordDto,
   UpdateUserStatusDto,
@@ -53,16 +52,16 @@ export const getUserController = () => {
       params,
     });
   };
-  const updateRole = (id: number, updateRoleBody: UpdateRoleBody) => {
+  const updateRole = (id: number, updateRoleDto: UpdateRoleDto) => {
     return customAxiosInstance<ResponseVoid>({
       url: `/user/${id}/role`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
-      data: updateRoleBody,
+      data: updateRoleDto,
     });
   };
   const updateUserStatus = (updateUserStatusDto: UpdateUserStatusDto) => {
-    return customAxiosInstance<Response>({
+    return customAxiosInstance<ResponseBoolean>({
       url: `/user/updateStatus`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
@@ -70,7 +69,7 @@ export const getUserController = () => {
     });
   };
   const updatePassword = (updateUserPasswordDto: UpdateUserPasswordDto) => {
-    return customAxiosInstance<Response>({
+    return customAxiosInstance<ResponseBoolean>({
       url: `/user/updatePassword`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
@@ -78,7 +77,7 @@ export const getUserController = () => {
     });
   };
   const updateUserAvatar = (updateUserAvatarDto: UpdateUserAvatarDto) => {
-    return customAxiosInstance<Response>({
+    return customAxiosInstance<ResponseBoolean>({
       url: `/user/updateAvatar`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
@@ -92,7 +91,7 @@ export const getUserController = () => {
     });
   };
   const profile = () => {
-    return customAxiosInstance<ResponseMapStringObject>({
+    return customAxiosInstance<ResponseProfileVo>({
       url: `/user/profile`,
       method: "GET",
     });

+ 27 - 28
src/api/wallet-controller.ts

@@ -6,15 +6,17 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 import type {
-  AdminRechargeBody,
-  ApplyWithdrawBody,
+  AdminRechargeDto,
   GetTransactionsParams,
   ListPendingWithdrawalsParams,
-  RechargeBody,
+  RechargeDto,
   ResponseListWalletTransactionVo,
-  ResponseMapStringObject,
+  ResponseString,
   ResponseVoid,
-  ReviewWithdrawBody,
+  ResponseWalletVo,
+  ResponseWithdrawApplyVo,
+  WithdrawDto,
+  WithdrawReviewDto,
 } from "./models";
 import { customAxiosInstance } from "../util/axios-instance";
 
@@ -22,48 +24,45 @@ export const getWalletController = () => {
   /**
    * @summary 审核提现(管理员)
    */
-  const reviewWithdraw = (
-    id: number,
-    reviewWithdrawBody: ReviewWithdrawBody,
-  ) => {
+  const reviewWithdraw = (id: number, withdrawReviewDto: WithdrawReviewDto) => {
     return customAxiosInstance<ResponseVoid>({
-      url: `/api/wallet/withdraw/${id}/review`,
+      url: `/wallet/withdraw/${id}/review`,
       method: "PUT",
       headers: { "Content-Type": "application/json" },
-      data: reviewWithdrawBody,
+      data: withdrawReviewDto,
     });
   };
   /**
    * @summary 提现申请
    */
-  const applyWithdraw = (applyWithdrawBody: ApplyWithdrawBody) => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/wallet/withdraw`,
+  const applyWithdraw = (withdrawDto: WithdrawDto) => {
+    return customAxiosInstance<ResponseWithdrawApplyVo>({
+      url: `/wallet/withdraw`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
-      data: applyWithdrawBody,
+      data: withdrawDto,
     });
   };
   /**
    * @summary 充值(预留接口,待对接支付平台)
    */
-  const recharge = (rechargeBody: RechargeBody) => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/wallet/recharge`,
+  const recharge = (rechargeDto: RechargeDto) => {
+    return customAxiosInstance<ResponseString>({
+      url: `/wallet/recharge`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
-      data: rechargeBody,
+      data: rechargeDto,
     });
   };
   /**
    * @summary 管理员代充值
    */
-  const adminRecharge = (adminRechargeBody: AdminRechargeBody) => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/wallet/admin-recharge`,
+  const adminRecharge = (adminRechargeDto: AdminRechargeDto) => {
+    return customAxiosInstance<ResponseString>({
+      url: `/wallet/admin-recharge`,
       method: "POST",
       headers: { "Content-Type": "application/json" },
-      data: adminRechargeBody,
+      data: adminRechargeDto,
     });
   };
   /**
@@ -71,7 +70,7 @@ export const getWalletController = () => {
    */
   const listPendingWithdrawals = (params?: ListPendingWithdrawalsParams) => {
     return customAxiosInstance<ResponseListWalletTransactionVo>({
-      url: `/api/wallet/withdraw/pending`,
+      url: `/wallet/withdraw/pending`,
       method: "GET",
       params,
     });
@@ -80,8 +79,8 @@ export const getWalletController = () => {
    * @summary 资金明细
    */
   const getTransactions = (params?: GetTransactionsParams) => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/wallet/transactions`,
+    return customAxiosInstance<ResponseListWalletTransactionVo>({
+      url: `/wallet/transactions`,
       method: "GET",
       params,
     });
@@ -90,8 +89,8 @@ export const getWalletController = () => {
    * @summary 查询余额
    */
   const getBalance = () => {
-    return customAxiosInstance<ResponseMapStringObject>({
-      url: `/api/wallet/balance`,
+    return customAxiosInstance<ResponseWalletVo>({
+      url: `/wallet/balance`,
       method: "GET",
     });
   };

+ 8 - 4
src/layout/AdminLayout.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 import {useLoginUserStore, useMetaStore} from "../store";
-import {computed, type ComputedRef, onMounted, ref, watch} from "vue";
-import type {RouteRecordRaw} from "vue-router";
+import {computed, onMounted, ref, watch} from "vue";
 import router, {adminRoutes} from "../router";
 
 const { websiteMeta,  fetchMeta } = useMetaStore()
@@ -14,8 +13,13 @@ const logoutHandler = () => {
   loginUserStore.logoutUser()
   router.push('/')
 }
-const menus:ComputedRef<RouteRecordRaw[] | undefined> = computed(()=>{
-  return adminRoutes[0].children;
+const menus = computed(() => {
+  const userRole = loginUserStore.loginUser.user?.role
+  return adminRoutes[0].children?.filter(child => {
+    if (child.meta?.hidden) return false
+    const allowedRoles = child.meta?.roles as string[] | undefined
+    return !allowedRoles || allowedRoles.includes(userRole ?? '')
+  })
 })
 const isCollapse = ref(false)
 const mobileMenuVisible = ref(false)

+ 28 - 8
src/router/index.ts

@@ -14,6 +14,7 @@ import AdminLayout from "../layout/AdminLayout.vue"
 import UserView from "../view/UserView.vue"
 import SiteSettingsView from "../view/SiteSettingsView.vue"
 import PostManageView from "../view/PostManageView.vue"
+import PostEditorView from "../view/PostEditorView.vue"
 import RealnameReviewView from "../view/RealnameReviewView.vue"
 import WithdrawReviewView from "../view/WithdrawReviewView.vue"
 import {
@@ -96,34 +97,46 @@ export const adminRoutes: RouteRecordRaw[] = [
         path: '/admin/users',
         name: 'users',
         component: UserView,
-        meta: { title: '用户管理', icon: User, requiresAdmin: true }
+        meta: { title: '用户管理', icon: User, roles: ['admin'] }
       },
       {
         path: '/admin/posts',
         name: 'adminPosts',
         component: PostManageView,
-        meta: { title: '帖子管理', icon: Document, requiresAdmin: true }
+        meta: { title: '帖子管理', icon: Document, roles: ['admin', 'expert'] }
+      },
+      {
+        path: '/admin/posts/create',
+        name: 'adminPostCreate',
+        component: PostEditorView,
+        meta: { title: '新建帖子', roles: ['admin', 'expert'], hidden: true }
+      },
+      {
+        path: '/admin/posts/:id/edit',
+        name: 'adminPostEdit',
+        component: PostEditorView,
+        meta: { title: '编辑帖子', roles: ['admin', 'expert'], hidden: true }
       },
       {
         path: '/admin/realname',
         name: 'adminRealname',
         component: RealnameReviewView,
-        meta: { title: '实名审核', icon: Edit, requiresAdmin: true }
+        meta: { title: '实名审核', icon: Edit, roles: ['admin'] }
       },
       {
         path: '/admin/withdraw',
         name: 'adminWithdraw',
         component: WithdrawReviewView,
-        meta: { title: '提现审核', icon: Edit, requiresAdmin: true }
+        meta: { title: '提现审核', icon: Edit, roles: ['admin'] }
       },
       {
         path: '/admin/settings',
         name: 'settings',
         component: SiteSettingsView,
-        meta: { title: '网站设置', icon: Setting, requiresAdmin: true }
+        meta: { title: '网站设置', icon: Setting, roles: ['admin'] }
       }
     ],
-    meta: { requiresAdmin: true }
+    meta: { roles: ['admin', 'expert'] }
   }
 ]
 
@@ -137,12 +150,19 @@ const router: Router = createRouter({
 })
 
 router.beforeEach((to, _from, next) => {
-  if (to.matched.some(record => record.meta.requiresAdmin)) {
+  const restrictedRecords = to.matched.filter(record => record.meta.roles)
+  if (restrictedRecords.length > 0) {
     const loginUserStore = useLoginUserStore()
     if (!loginUserStore.loginUser.isLogin) {
       ElMessage.warning('请先登录')
       next({ path: '/login', query: { redirect: to.fullPath } })
-    } else if (loginUserStore.loginUser.user?.role !== 'admin') {
+      return
+    }
+    const userRole = loginUserStore.loginUser.user?.role ?? ''
+    const hasAccess = restrictedRecords.every(record =>
+      (record.meta.roles as string[]).includes(userRole)
+    )
+    if (!hasAccess) {
       ElMessage.error('无权访问')
       next('/')
     } else {

+ 3 - 3
src/store/notification.ts

@@ -2,7 +2,7 @@ import { defineStore } from "pinia"
 import { ref } from "vue"
 import type { NotificationItem } from "../type"
 import { getNotificationController } from "../api/notification-controller"
-import type { NotificationVo, ResponseListNotificationVo, ResponseMapStringLong } from "../api/models"
+import type { NotificationVo, ResponseListNotificationVo, ResponseUnreadCountVo } from "../api/models"
 
 function mapNotifVo(v: NotificationVo): NotificationItem {
   return {
@@ -27,9 +27,9 @@ export const useNotificationStore = defineStore('notification', () => {
       if (res.code === 200 && res.data) {
         messages.value = res.data.map(mapNotifVo)
       }
-      const ucRes: ResponseMapStringLong = await getNotificationController().getUnreadCount()
+      const ucRes: ResponseUnreadCountVo = await getNotificationController().getUnreadCount()
       if (ucRes.code === 200 && ucRes.data) {
-        unreadCount.value = (ucRes.data as { count?: number }).count ?? 0
+        unreadCount.value = ucRes.data.count ?? 0
       }
     } catch {
       messages.value = []

+ 2 - 2
src/store/user.ts

@@ -3,7 +3,7 @@ import { computed, reactive, ref } from "vue"
 import type { LoginUser, User, UserProfile } from "../type"
 import { getAuthController } from "../api/auth-controller"
 import { getUserController } from "../api/user-controller"
-import type { ResponseAuthTokenVo, ResponseMapStringObject } from "../api/models"
+import type { ResponseAuthTokenVo, ResponseProfileVo } from "../api/models"
 
 export const useLoginUserStore = defineStore('loginUser', () => {
   const loginUser = reactive<LoginUser>({
@@ -130,7 +130,7 @@ export const useLoginUserStore = defineStore('loginUser', () => {
 
   async function fetchProfile(): Promise<void> {
     try {
-      const res: ResponseMapStringObject = await getUserController().profile()
+      const res: ResponseProfileVo = await getUserController().profile()
       if (res.code === 200 && res.data) {
         const p = res.data as Record<string, unknown>
         fetchUserInfo({

+ 7 - 8
src/store/wallet.ts

@@ -2,7 +2,7 @@ import { defineStore } from "pinia"
 import { ref } from "vue"
 import type { TransactionItem } from "../type"
 import { getWalletController } from "../api/wallet-controller"
-import type { ResponseMapStringObject } from "../api/models"
+import type { ResponseWalletVo, ResponseListWalletTransactionVo, ResponseString, ResponseWithdrawApplyVo } from "../api/models"
 
 export const useWalletStore = defineStore('wallet', () => {
   const balance = ref(0)
@@ -11,9 +11,9 @@ export const useWalletStore = defineStore('wallet', () => {
 
   async function fetchBalance(): Promise<void> {
     try {
-      const res: ResponseMapStringObject = await getWalletController().getBalance()
+      const res: ResponseWalletVo = await getWalletController().getBalance()
       if (res.code === 200 && res.data) {
-        balance.value = (res.data as { balance?: number }).balance ?? 0
+        balance.value = res.data.balance ?? 0
       }
     } catch {
       balance.value = 0
@@ -23,10 +23,9 @@ export const useWalletStore = defineStore('wallet', () => {
   async function fetchTransactions(pageNum = 1, pageSize = 20): Promise<void> {
     loading.value = true
     try {
-      const res: ResponseMapStringObject = await getWalletController().getTransactions({ pageNum, pageSize })
+      const res: ResponseListWalletTransactionVo = await getWalletController().getTransactions({ pageNum, pageSize })
       if (res.code === 200 && res.data) {
-        const rawList = (res.data as { list?: unknown[] }).list ?? []
-        transactions.value = (rawList as Record<string, unknown>[]).map((i) => ({
+        transactions.value = (res.data as Record<string, unknown>[]).map((i) => ({
           id: String(i.id ?? ''),
           type: String(i.type ?? ''),
           amount: Number(i.amount ?? 0),
@@ -46,7 +45,7 @@ export const useWalletStore = defineStore('wallet', () => {
 
   async function recharge(amount: number): Promise<boolean> {
     try {
-      const res: ResponseMapStringObject = await getWalletController().recharge({ amount })
+      const res: ResponseString = await getWalletController().recharge({ amount })
       if (res.code === 200) {
         await fetchBalance()
         return true
@@ -59,7 +58,7 @@ export const useWalletStore = defineStore('wallet', () => {
 
   async function applyWithdraw(amount: number): Promise<boolean> {
     try {
-      const res: ResponseMapStringObject = await getWalletController().applyWithdraw({ amount })
+      const res: ResponseWithdrawApplyVo = await getWalletController().applyWithdraw({ amount })
       return res.code === 200
     } catch {
       return false

+ 149 - 0
src/view/PostEditorView.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="post-editor">
+    <el-card>
+      <template #header>
+        <div class="card-header">
+          <span>{{ isEdit ? '编辑帖子' : '新建帖子' }}</span>
+          <el-button @click="goBack">返回</el-button>
+        </div>
+      </template>
+
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="editor-form">
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="form.title" placeholder="帖子标题(含期号)" />
+        </el-form-item>
+
+        <el-form-item label="价格" prop="price">
+          <el-input-number v-model="form.price" :min="0" :precision="2" :step="10" />
+        </el-form-item>
+
+        <el-form-item label="过期时间" prop="expireTime">
+          <el-date-picker v-model="form.expireTime" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选择过期时间" style="width: 100%" />
+        </el-form-item>
+
+        <el-form-item label="内容简介">
+          <QuillEditor v-model:content="form.contentIntro" content-type="html" :toolbar="toolbar" class="quill-editor" />
+        </el-form-item>
+
+        <el-form-item label="付费内容">
+          <QuillEditor v-model:content="form.contentPaid" content-type="html" :toolbar="toolbar" class="quill-editor" />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" :loading="submitting" @click="handleSubmit">{{ isEdit ? '保存' : '创建' }}</el-button>
+          <el-button @click="goBack">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { QuillEditor } from '@vueup/vue-quill'
+import '@vueup/vue-quill/dist/vue-quill.snow.css'
+import { getPostController } from '../api/post-controller'
+
+const route = useRoute()
+const router = useRouter()
+const api = getPostController()
+
+const isEdit = computed(() => !!route.params.id)
+const submitting = ref(false)
+const formRef = ref()
+
+const toolbar = [
+  ['bold', 'italic', 'underline', 'strike'],
+  [{ list: 'ordered' }, { list: 'bullet' }],
+  [{ color: [] }, { background: [] }],
+  ['link', 'image'],
+  ['clean'],
+]
+
+const form = reactive({
+  title: '',
+  contentIntro: '',
+  contentPaid: '',
+  price: 0,
+  expireTime: '',
+})
+
+const rules = {
+  title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
+  price: [{ required: true, message: '请设置价格', trigger: 'blur' }],
+  expireTime: [{ required: true, message: '请选择过期时间', trigger: 'blur' }],
+}
+
+function goBack() {
+  router.push('/admin/posts')
+}
+
+async function loadPost() {
+  if (!isEdit.value) return
+  try {
+    const res = await api.getPostDetail(Number(route.params.id))
+    if (res.code === 200 && res.data) {
+      form.title = res.data.title ?? ''
+      form.contentIntro = res.data.contentIntro ?? ''
+      form.contentPaid = res.data.contentPaid ?? ''
+      form.price = res.data.price ?? 0
+      form.expireTime = res.data.expireTime ?? ''
+    } else {
+      ElMessage.error('帖子不存在')
+      goBack()
+    }
+  } catch {
+    ElMessage.error('获取帖子失败')
+    goBack()
+  }
+}
+
+async function handleSubmit() {
+  if (submitting.value) return
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  submitting.value = true
+  const dto = { ...form }
+  try {
+    if (isEdit.value) {
+      await api.updatePost(Number(route.params.id), dto)
+      ElMessage.success('保存成功')
+    } else {
+      await api.createPost(dto)
+      ElMessage.success('创建成功')
+    }
+    goBack()
+  } catch {
+    ElMessage.error(isEdit.value ? '保存失败' : '创建失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+onMounted(() => loadPost())
+</script>
+
+<style scoped>
+.post-editor {
+  padding: 16px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 16px;
+  font-weight: 600;
+}
+.editor-form {
+  max-width: 800px;
+}
+.quill-editor {
+  --qt-height: 300px;
+}
+.quill-editor :deep(.ql-editor) {
+  min-height: 250px;
+}
+</style>

+ 15 - 2
src/view/PostManageView.vue

@@ -4,6 +4,7 @@
       <template #header>
         <div class="card-header">
           <span>帖子管理</span>
+          <el-button type="primary" @click="router.push('/admin/posts/create')">新增帖子</el-button>
         </div>
       </template>
 
@@ -46,8 +47,9 @@
         </el-table-column>
         <el-table-column prop="publishTime" label="发布时间" width="160" />
         <el-table-column prop="expireTime" label="过期时间" width="160" />
-        <el-table-column label="操作" width="200" fixed="right">
+        <el-table-column label="操作" width="280" fixed="right">
           <template #default="{ row }">
+            <el-button v-if="canEdit(row)" size="small" @click="router.push('/admin/posts/' + row.id + '/edit')">编辑</el-button>
             <el-button size="small" @click="editHitStatus(row)">设命中</el-button>
             <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
           </template>
@@ -80,11 +82,22 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue'
+import { ref, reactive, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { getPostController } from '../api/post-controller'
+import { useLoginUserStore } from '../store'
 import type { PostVo, ListPostsParams } from '../api/models'
 
+const router = useRouter()
+const userStore = useLoginUserStore()
+const currentRole = computed(() => userStore.loginUser.user?.role)
+const currentUserId = computed(() => userStore.loginUser.user?.id)
+
+function canEdit(row: PostVo) {
+  return currentRole.value === 'admin' || (currentRole.value === 'expert' && row.expertId === currentUserId.value)
+}
+
 const api = getPostController()
 const tableData = ref<(PostVo & { _viewCount?: number })[]>([])
 const loading = ref(false)

+ 12 - 3
src/view/SiteSettingsView.vue

@@ -2,7 +2,8 @@
 import { onMounted, reactive, ref } from 'vue'
 import { useMetaStore } from '../store'
 import { getMetaController } from '../api/meta-controller'
-import { getSystemConfigController } from '../api/system-config-controller'
+import type { ResponseObject, ResponseVoid } from '../api/models'
+import { customAxiosInstance } from '../util/axios-instance'
 import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
 
 const activeTab = ref('basic')
@@ -43,7 +44,10 @@ onMounted(async () => {
   form.announcement = websiteMeta.announcement
 
   try {
-    const res = await getSystemConfigController().getConfig('oss')
+    const res = await customAxiosInstance<ResponseObject>({
+      url: '/meta/getOssConfig',
+      method: 'GET',
+    })
     if (res.code === 200 && res.data) {
       const d = res.data as Record<string, unknown>
       ossForm.endpoint = String(d.endpoint ?? '')
@@ -85,7 +89,12 @@ const handleSubmit = async () => {
 async function handleOssSave() {
   ossSaving.value = true
   try {
-    await getSystemConfigController().updateConfig('oss', { value: ossForm })
+    await customAxiosInstance<ResponseVoid>({
+      url: '/meta/updateOssConfig',
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      data: ossForm,
+    })
     ElMessage.success('OSS 配置已保存')
   } catch {
     ElMessage.error('保存失败')

+ 24 - 5
src/view/UserView.vue

@@ -25,7 +25,9 @@ const userForm = reactive<Partial<UserDto>>({
   username: '',
   account: '',
   password: '',
-  role: ''
+  role: '',
+  phoneNumber: '',
+  avatar: ''
 })
 const formRules: FormRules = {
   username: [
@@ -157,7 +159,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: '', phoneNumber: '', avatar: '' })
   dialogVisible.value = true
 }
 
@@ -173,7 +175,9 @@ const handleEdit = async (row: UserVo) => {
         username: res.data?.username,
         account: res.data?.account,
         password: '',
-        role: res.data?.role
+        role: res.data?.role,
+        phoneNumber: res.data?.phoneNumber ?? '',
+        avatar: res.data?.avatar ?? ''
       })
       dialogVisible.value = true
     }
@@ -197,7 +201,9 @@ const handleSave = async () => {
         const data: Partial<UserDto> = {
           id: userForm.id,
           ...baseData,
-          ...(userForm.password ? { password: userForm.password } : {})
+          ...(userForm.password ? { password: userForm.password } : {}),
+          phoneNumber: userForm.phoneNumber || undefined,
+          avatar: userForm.avatar || undefined
         }
         const res = await api.edit(data as UserDto)
         if (res?.code === 0 || res?.code === 200) {
@@ -214,7 +220,9 @@ const handleSave = async () => {
         }
         const data: UserDto = {
           ...baseData,
-          password: userForm.password!
+          password: userForm.password!,
+          ...(userForm.phoneNumber ? { phoneNumber: userForm.phoneNumber } : {}),
+          ...(userForm.avatar ? { avatar: userForm.avatar } : {})
         }
         const res = await api.add(data)
         if (res?.code === 0 || res?.code === 200) {
@@ -442,6 +450,17 @@ fetchData()
             <el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
+        <el-form-item label="手机号" prop="phoneNumber">
+          <el-input v-model="userForm.phoneNumber" placeholder="请输入手机号" maxlength="11" />
+        </el-form-item>
+        <el-form-item label="头像" prop="avatar">
+          <el-input v-model="userForm.avatar" placeholder="请输入头像URL地址">
+            <template #append>
+              <el-avatar v-if="userForm.avatar" :src="userForm.avatar" :size="28" style="vertical-align: middle" />
+              <span v-else style="padding: 0 8px; color: #999">预览</span>
+            </template>
+          </el-input>
+        </el-form-item>
       </el-form>
       <template #footer>
         <el-button @click="dialogVisible = false">取消</el-button>