Jelajahi Sumber

feat: frontend store/view real API integration, Orval regeneration

yangyi 3 hari lalu
induk
melakukan
988f925d01
60 mengubah file dengan 1485 tambahan dan 1122 penghapusan
  1. 0 966
      openapi.json
  2. 32 0
      src/api/attachment-controller.ts
  3. 9 0
      src/api/models/adminRechargeBody.ts
  4. 9 0
      src/api/models/applyWithdrawBody.ts
  5. 9 0
      src/api/models/createAndPayBody.ts
  6. 12 0
      src/api/models/getTransactionsParams.ts
  7. 37 0
      src/api/models/index.ts
  8. 13 0
      src/api/models/listOrdersParams.ts
  9. 14 0
      src/api/models/listPostsParams.ts
  10. 12 0
      src/api/models/listPreviousPostsParams.ts
  11. 19 0
      src/api/models/notificationVo.ts
  12. 22 0
      src/api/models/orderTipVo.ts
  13. 18 0
      src/api/models/pageVoListOrderTipVo.ts
  14. 18 0
      src/api/models/pageVoListPostVo.ts
  15. 26 0
      src/api/models/postDto.ts
  16. 27 0
      src/api/models/postVo.ts
  17. 22 0
      src/api/models/realnameAuthVo.ts
  18. 9 0
      src/api/models/rechargeBody.ts
  19. 20 0
      src/api/models/responseListNotificationVo.ts
  20. 20 0
      src/api/models/responseListRealnameAuthVo.ts
  21. 20 0
      src/api/models/responseMapStringLong.ts
  22. 12 0
      src/api/models/responseMapStringLongData.ts
  23. 20 0
      src/api/models/responseMapStringObject.ts
  24. 12 0
      src/api/models/responseMapStringObjectData.ts
  25. 20 0
      src/api/models/responseMapStringString.ts
  26. 12 0
      src/api/models/responseMapStringStringData.ts
  27. 19 0
      src/api/models/responseObject.ts
  28. 20 0
      src/api/models/responsePageVoListOrderTipVo.ts
  29. 20 0
      src/api/models/responsePageVoListPostVo.ts
  30. 20 0
      src/api/models/responsePostVo.ts
  31. 20 0
      src/api/models/responseRealnameAuthVo.ts
  32. 19 0
      src/api/models/responseVoid.ts
  33. 9 0
      src/api/models/reviewBody.ts
  34. 9 0
      src/api/models/submitBody.ts
  35. 9 0
      src/api/models/updateConfigBody.ts
  36. 9 0
      src/api/models/updateHitStatusBody.ts
  37. 9 0
      src/api/models/updateRoleBody.ts
  38. 9 0
      src/api/models/updateViewCountBody.ts
  39. 11 0
      src/api/models/uploadBody.ts
  40. 11 0
      src/api/models/uploadParams.ts
  41. 59 0
      src/api/notification-controller.ts
  42. 119 0
      src/api/post-controller.ts
  43. 73 0
      src/api/realname-auth-controller.ts
  44. 41 0
      src/api/system-config-controller.ts
  45. 45 0
      src/api/tip-order-controller.ts
  46. 25 0
      src/api/user-controller.ts
  47. 92 0
      src/api/wallet-controller.ts
  48. 1 16
      src/components/ExpertInfoCard.vue
  49. 0 3
      src/components/PostCard.vue
  50. 1 0
      src/store/index.ts
  51. 36 10
      src/store/notification.ts
  52. 37 15
      src/store/order.ts
  53. 68 10
      src/store/post.ts
  54. 78 9
      src/store/user.ts
  55. 70 0
      src/store/wallet.ts
  56. 21 19
      src/type/index.ts
  57. 2 2
      src/view/NotificationsView.vue
  58. 9 9
      src/view/OrdersView.vue
  59. 23 24
      src/view/PostDetailView.vue
  60. 47 39
      src/view/WalletView.vue

File diff ditekan karena terlalu besar
+ 0 - 966
openapi.json


+ 32 - 0
src/api/attachment-controller.ts

@@ -0,0 +1,32 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  ResponseMapStringString,
+  UploadBody,
+  UploadParams,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getAttachmentController = () => {
+  /**
+   * @summary 上传附件到OSS(需对接OSS配置)
+   */
+  const upload = (uploadBody: UploadBody, params?: UploadParams) => {
+    return customAxiosInstance<ResponseMapStringString>({
+      url: `/api/attachments/upload`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: uploadBody,
+      params,
+    });
+  };
+  return { upload };
+};
+export type UploadResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getAttachmentController>["upload"]>>
+>;

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

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

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

@@ -6,22 +6,59 @@
  * OpenAPI spec version: 0.0.1-SNAPSHOT
  */
 
+export * from "./adminRechargeBody";
+export * from "./applyWithdrawBody";
 export * from "./authTokenVo";
+export * from "./createAndPayBody";
 export * from "./deleteByIdParams";
 export * from "./fieldError";
+export * from "./getTransactionsParams";
+export * from "./listOrdersParams";
+export * from "./listPostsParams";
+export * from "./listPreviousPostsParams";
 export * from "./loginDto";
+export * from "./notificationVo";
+export * from "./orderTipVo";
+export * from "./pageVoListOrderTipVo";
+export * from "./pageVoListPostVo";
 export * from "./pageVoListUserVo";
+export * from "./postDto";
+export * from "./postVo";
 export * from "./queryByPageParams";
+export * from "./realnameAuthVo";
+export * from "./rechargeBody";
 export * from "./registerDto";
 export * from "./response";
 export * from "./responseAuthTokenVo";
 export * from "./responseBoolean";
 export * from "./responseListFieldError";
+export * from "./responseListNotificationVo";
+export * from "./responseListRealnameAuthVo";
+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 "./responseRealnameAuthVo";
 export * from "./responseUserVo";
+export * from "./responseVoid";
+export * from "./reviewBody";
+export * from "./submitBody";
+export * from "./updateConfigBody";
+export * from "./updateHitStatusBody";
+export * from "./updateRoleBody";
 export * from "./updateUserAvatarDto";
 export * from "./updateUserPasswordDto";
 export * from "./updateUserStatusDto";
+export * from "./updateViewCountBody";
+export * from "./uploadBody";
+export * from "./uploadParams";
 export * from "./userDto";
 export * from "./userVo";
 export * from "./websiteMetaDto";

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

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

+ 14 - 0
src/api/models/listPostsParams.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 ListPostsParams = {
+  keyword?: string;
+  status?: string;
+  pageNum?: number;
+  pageSize?: number;
+};

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

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

+ 19 - 0
src/api/models/notificationVo.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 NotificationVo {
+  content?: string;
+  createdAt?: string;
+  id?: string;
+  isRead?: boolean;
+  title?: string;
+  type?: string;
+}

+ 22 - 0
src/api/models/orderTipVo.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
+ */
+
+/**
+ * 打赏订单视图
+ */
+export interface OrderTipVo {
+  amount?: number;
+  createTime?: string;
+  expertId?: string;
+  expertName?: string;
+  id?: string;
+  postId?: string;
+  postTitle?: string;
+  status?: string;
+  userId?: string;
+}

+ 18 - 0
src/api/models/pageVoListOrderTipVo.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 { OrderTipVo } from "./orderTipVo";
+
+/**
+ * 分页数据对象
+ */
+export interface PageVoListOrderTipVo {
+  /** 当前页的数据 */
+  data?: OrderTipVo[];
+  /** 总数据条数 */
+  total?: number;
+}

+ 18 - 0
src/api/models/pageVoListPostVo.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 { PostVo } from "./postVo";
+
+/**
+ * 分页数据对象
+ */
+export interface PageVoListPostVo {
+  /** 当前页的数据 */
+  data?: PostVo[];
+  /** 总数据条数 */
+  total?: number;
+}

+ 26 - 0
src/api/models/postDto.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 interface PostDto {
+  /** 内容简介(公开) */
+  contentIntro?: string;
+  /** 付费内容 */
+  contentPaid?: string;
+  /** 过期时间,超出此时间自动转为公开 */
+  expireTime: string;
+  /** 打赏金额 */
+  price: number;
+  /**
+   * 帖子标题(含期号)
+   * @minLength 1
+   */
+  title: string;
+}

+ 27 - 0
src/api/models/postVo.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 PostVo {
+  contentIntro?: string;
+  contentPaid?: string;
+  expertAvatar?: string;
+  expertId?: string;
+  expertName?: string;
+  expireTime?: string;
+  hitStatus?: string;
+  id?: string;
+  isPaid?: boolean;
+  isPublic?: boolean;
+  price?: number;
+  publishTime?: string;
+  title?: string;
+  viewCount?: number;
+}

+ 22 - 0
src/api/models/realnameAuthVo.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
+ */
+
+/**
+ * 实名认证视图
+ */
+export interface RealnameAuthVo {
+  createdAt?: string;
+  id?: string;
+  idCard?: string;
+  idCardBack?: string;
+  idCardFront?: string;
+  realName?: string;
+  rejectReason?: string;
+  status?: string;
+  userId?: string;
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 19 - 0
src/api/models/responseObject.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 ResponseObject {
+  /** 状态码;200:成功 */
+  code?: number;
+  /** 响应的具体数据 */
+  data?: unknown;
+  /** 响应附加信息 */
+  message?: string;
+}

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

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

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

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

+ 19 - 0
src/api/models/responseVoid.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 ResponseVoid {
+  /** 状态码;200:成功 */
+  code?: number;
+  /** 响应的具体数据 */
+  data?: unknown;
+  /** 响应附加信息 */
+  message?: string;
+}

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

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

@@ -0,0 +1,9 @@
+/**
+ * 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 };

+ 11 - 0
src/api/models/uploadBody.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 UploadBody = {
+  file: Blob;
+};

+ 11 - 0
src/api/models/uploadParams.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 UploadParams = {
+  type?: string;
+};

+ 59 - 0
src/api/notification-controller.ts

@@ -0,0 +1,59 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  ResponseListNotificationVo,
+  ResponseMapStringLong,
+  ResponseVoid,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getNotificationController = () => {
+  /**
+   * @summary 标记已读
+   */
+  const markRead = (id: number) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/api/notifications/${id}/read`,
+      method: "PUT",
+    });
+  };
+  /**
+   * @summary 获取通知列表
+   */
+  const listNotifications = () => {
+    return customAxiosInstance<ResponseListNotificationVo>({
+      url: `/api/notifications`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 获取未读数
+   */
+  const getUnreadCount = () => {
+    return customAxiosInstance<ResponseMapStringLong>({
+      url: `/api/notifications/unread-count`,
+      method: "GET",
+    });
+  };
+  return { markRead, listNotifications, getUnreadCount };
+};
+export type MarkReadResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getNotificationController>["markRead"]>>
+>;
+export type ListNotificationsResult = NonNullable<
+  Awaited<
+    ReturnType<
+      ReturnType<typeof getNotificationController>["listNotifications"]
+    >
+  >
+>;
+export type GetUnreadCountResult = NonNullable<
+  Awaited<
+    ReturnType<ReturnType<typeof getNotificationController>["getUnreadCount"]>
+  >
+>;

+ 119 - 0
src/api/post-controller.ts

@@ -0,0 +1,119 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  ListPostsParams,
+  ListPreviousPostsParams,
+  PostDto,
+  ResponseMapStringObject,
+  ResponsePageVoListPostVo,
+  ResponsePostVo,
+  ResponseVoid,
+  UpdateHitStatusBody,
+  UpdateViewCountBody,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getPostController = () => {
+  /**
+   * @summary 修改查看人数(管理员)
+   */
+  const updateViewCount = (
+    id: number,
+    updateViewCountBody: UpdateViewCountBody,
+  ) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/api/posts/${id}/view-count`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: updateViewCountBody,
+    });
+  };
+  /**
+   * @summary 设置命中状态(管理员)
+   */
+  const updateHitStatus = (
+    id: number,
+    updateHitStatusBody: UpdateHitStatusBody,
+  ) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/api/posts/${id}/hit-status`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: updateHitStatusBody,
+    });
+  };
+  /**
+   * @summary 获取帖子列表
+   */
+  const listPosts = (params?: ListPostsParams) => {
+    return customAxiosInstance<ResponsePageVoListPostVo>({
+      url: `/api/posts`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 创建帖子(仅专家/管理员)
+   */
+  const createPost = (postDto: PostDto) => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/posts`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: postDto,
+    });
+  };
+  /**
+   * @summary 获取帖子详情
+   */
+  const getPostDetail = (id: number) => {
+    return customAxiosInstance<ResponsePostVo>({
+      url: `/api/posts/${id}`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 获取专家往期帖子
+   */
+  const listPreviousPosts = (
+    expertId: number,
+    params?: ListPreviousPostsParams,
+  ) => {
+    return customAxiosInstance<ResponsePageVoListPostVo>({
+      url: `/api/posts/expert/${expertId}/previous`,
+      method: "GET",
+      params,
+    });
+  };
+  return {
+    updateViewCount,
+    updateHitStatus,
+    listPosts,
+    createPost,
+    getPostDetail,
+    listPreviousPosts,
+  };
+};
+export type UpdateViewCountResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPostController>["updateViewCount"]>>
+>;
+export type UpdateHitStatusResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPostController>["updateHitStatus"]>>
+>;
+export type ListPostsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPostController>["listPosts"]>>
+>;
+export type CreatePostResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPostController>["createPost"]>>
+>;
+export type GetPostDetailResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPostController>["getPostDetail"]>>
+>;
+export type ListPreviousPostsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getPostController>["listPreviousPosts"]>>
+>;

+ 73 - 0
src/api/realname-auth-controller.ts

@@ -0,0 +1,73 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  ResponseListRealnameAuthVo,
+  ResponseRealnameAuthVo,
+  ResponseVoid,
+  ReviewBody,
+  SubmitBody,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getRealnameAuthController = () => {
+  /**
+   * @summary 管理员审核实名认证
+   */
+  const review = (id: number, reviewBody: ReviewBody) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/api/realname/${id}/review`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: reviewBody,
+    });
+  };
+  /**
+   * @summary 查询自己的认证信息
+   */
+  const getMyAuth = () => {
+    return customAxiosInstance<ResponseRealnameAuthVo>({
+      url: `/api/realname`,
+      method: "GET",
+    });
+  };
+  /**
+   * @summary 提交实名认证
+   */
+  const submit = (submitBody: SubmitBody) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/api/realname`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: submitBody,
+    });
+  };
+  /**
+   * @summary 获取所有待审核认证(管理员)
+   */
+  const listPending = () => {
+    return customAxiosInstance<ResponseListRealnameAuthVo>({
+      url: `/api/realname/pending`,
+      method: "GET",
+    });
+  };
+  return { review, getMyAuth, submit, listPending };
+};
+export type ReviewResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getRealnameAuthController>["review"]>>
+>;
+export type GetMyAuthResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getRealnameAuthController>["getMyAuth"]>>
+>;
+export type SubmitResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getRealnameAuthController>["submit"]>>
+>;
+export type ListPendingResult = NonNullable<
+  Awaited<
+    ReturnType<ReturnType<typeof getRealnameAuthController>["listPending"]>
+  >
+>;

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

@@ -0,0 +1,41 @@
+/**
+ * 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"]>
+  >
+>;

+ 45 - 0
src/api/tip-order-controller.ts

@@ -0,0 +1,45 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  CreateAndPayBody,
+  ListOrdersParams,
+  ResponseMapStringObject,
+  ResponsePageVoListOrderTipVo,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getTipOrderController = () => {
+  /**
+   * @summary 打赏并即时扣款
+   */
+  const createAndPay = (createAndPayBody: CreateAndPayBody) => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/orders/tip`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: createAndPayBody,
+    });
+  };
+  /**
+   * @summary 获取我的打赏订单列表
+   */
+  const listOrders = (params?: ListOrdersParams) => {
+    return customAxiosInstance<ResponsePageVoListOrderTipVo>({
+      url: `/api/orders`,
+      method: "GET",
+      params,
+    });
+  };
+  return { createAndPay, listOrders };
+};
+export type CreateAndPayResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getTipOrderController>["createAndPay"]>>
+>;
+export type ListOrdersResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getTipOrderController>["listOrders"]>>
+>;

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

@@ -10,8 +10,11 @@ import type {
   QueryByPageParams,
   Response,
   ResponseBoolean,
+  ResponseMapStringObject,
   ResponsePageVoListUserVo,
   ResponseUserVo,
+  ResponseVoid,
+  UpdateRoleBody,
   UpdateUserAvatarDto,
   UpdateUserPasswordDto,
   UpdateUserStatusDto,
@@ -50,6 +53,14 @@ export const getUserController = () => {
       params,
     });
   };
+  const updateRole = (id: number, updateRoleBody: UpdateRoleBody) => {
+    return customAxiosInstance<ResponseVoid>({
+      url: `/user/${id}/role`,
+      method: "PUT",
+      headers: { "Content-Type": "application/json" },
+      data: updateRoleBody,
+    });
+  };
   const updateUserStatus = (updateUserStatusDto: UpdateUserStatusDto) => {
     return customAxiosInstance<Response>({
       url: `/user/updateStatus`,
@@ -80,15 +91,23 @@ export const getUserController = () => {
       method: "GET",
     });
   };
+  const profile = () => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/user/profile`,
+      method: "GET",
+    });
+  };
   return {
     queryByPage,
     edit,
     add,
     deleteById,
+    updateRole,
     updateUserStatus,
     updatePassword,
     updateUserAvatar,
     queryById,
+    profile,
   };
 };
 export type QueryByPageResult = NonNullable<
@@ -103,6 +122,9 @@ export type AddResult = NonNullable<
 export type DeleteByIdResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["deleteById"]>>
 >;
+export type UpdateRoleResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getUserController>["updateRole"]>>
+>;
 export type UpdateUserStatusResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["updateUserStatus"]>>
 >;
@@ -115,3 +137,6 @@ export type UpdateUserAvatarResult = NonNullable<
 export type QueryByIdResult = NonNullable<
   Awaited<ReturnType<ReturnType<typeof getUserController>["queryById"]>>
 >;
+export type ProfileResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getUserController>["profile"]>>
+>;

+ 92 - 0
src/api/wallet-controller.ts

@@ -0,0 +1,92 @@
+/**
+ * Generated by orval v7.0.1 🍺
+ * Do not edit manually.
+ * Serve API
+ * Serve应用接口文档
+ * OpenAPI spec version: 0.0.1-SNAPSHOT
+ */
+import type {
+  AdminRechargeBody,
+  ApplyWithdrawBody,
+  GetTransactionsParams,
+  RechargeBody,
+  ResponseMapStringObject,
+} from "./models";
+import { customAxiosInstance } from "../util/axios-instance";
+
+export const getWalletController = () => {
+  /**
+   * @summary 提现申请
+   */
+  const applyWithdraw = (applyWithdrawBody: ApplyWithdrawBody) => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/wallet/withdraw`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: applyWithdrawBody,
+    });
+  };
+  /**
+   * @summary 充值(预留接口,待对接支付平台)
+   */
+  const recharge = (rechargeBody: RechargeBody) => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/wallet/recharge`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: rechargeBody,
+    });
+  };
+  /**
+   * @summary 管理员代充值
+   */
+  const adminRecharge = (adminRechargeBody: AdminRechargeBody) => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/wallet/admin-recharge`,
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      data: adminRechargeBody,
+    });
+  };
+  /**
+   * @summary 资金明细
+   */
+  const getTransactions = (params?: GetTransactionsParams) => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/wallet/transactions`,
+      method: "GET",
+      params,
+    });
+  };
+  /**
+   * @summary 查询余额
+   */
+  const getBalance = () => {
+    return customAxiosInstance<ResponseMapStringObject>({
+      url: `/api/wallet/balance`,
+      method: "GET",
+    });
+  };
+  return {
+    applyWithdraw,
+    recharge,
+    adminRecharge,
+    getTransactions,
+    getBalance,
+  };
+};
+export type ApplyWithdrawResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getWalletController>["applyWithdraw"]>>
+>;
+export type RechargeResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getWalletController>["recharge"]>>
+>;
+export type AdminRechargeResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getWalletController>["adminRecharge"]>>
+>;
+export type GetTransactionsResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getWalletController>["getTransactions"]>>
+>;
+export type GetBalanceResult = NonNullable<
+  Awaited<ReturnType<ReturnType<typeof getWalletController>["getBalance"]>>
+>;

+ 1 - 16
src/components/ExpertInfoCard.vue

@@ -6,33 +6,18 @@
     <div class="expert-info">
       <div class="expert-name-row">
         <span class="expert-name">{{ expert.name }}</span>
-        <el-tag v-if="expert.realnameVerified" size="small" type="success" effect="dark">已认证</el-tag>
       </div>
-      <div class="expert-level">{{ levelLabel }}</div>
-      <p class="expert-brief">{{ expert.brief }}</p>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
-
-const props = defineProps<{
+defineProps<{
   expert: {
     name: string
     avatar: string
-    level: number
-    realnameVerified: boolean
-    brief: string
   }
 }>()
-
-const levelLabel = computed(() => {
-  if (props.expert.level >= 80) return '大师'
-  if (props.expert.level >= 50) return '钻石'
-  if (props.expert.level >= 20) return '黄金'
-  return '初级'
-})
 </script>
 
 <style scoped>

+ 0 - 3
src/components/PostCard.vue

@@ -11,9 +11,6 @@
         {{ post.viewCount }}
       </span>
     </div>
-    <div class="card-tags">
-      <el-tag v-for="tag in post.tags" :key="tag" size="small" class="tag">{{ tag }}</el-tag>
-    </div>
     <div class="card-footer">
       <span class="price">{{ post.price ? `¥${post.price}` : '免费' }}</span>
       <span class="time">{{ relativeTime(post.publishTime) }}</span>

+ 1 - 0
src/store/index.ts

@@ -3,3 +3,4 @@ export { useLoginUserStore } from "./user"
 export { usePostStore } from "./post"
 export { useOrderStore } from "./order"
 export { useNotificationStore } from "./notification"
+export { useWalletStore } from "./wallet"

+ 36 - 10
src/store/notification.ts

@@ -1,27 +1,53 @@
 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"
+
+function mapNotifVo(v: NotificationVo): NotificationItem {
+  return {
+    id: v.id ?? '',
+    type: v.type ?? '',
+    title: v.title ?? '',
+    content: v.content ?? '',
+    createdAt: v.createdAt ?? '',
+    isRead: v.isRead ?? false,
+  }
+}
 
 export const useNotificationStore = defineStore('notification', () => {
   const messages = ref<NotificationItem[]>([])
   const unreadCount = ref(0)
   const loading = ref(false)
 
-  function fetchNotifications(): void {
+  async function fetchNotifications(): Promise<void> {
     loading.value = true
-    // Placeholder
-    setTimeout(() => {
+    try {
+      const res: ResponseListNotificationVo = await getNotificationController().listNotifications()
+      if (res.code === 200 && res.data) {
+        messages.value = res.data.map(mapNotifVo)
+      }
+      const ucRes: ResponseMapStringLong = await getNotificationController().getUnreadCount()
+      if (ucRes.code === 200 && ucRes.data) {
+        unreadCount.value = (ucRes.data as { count?: number }).count ?? 0
+      }
+    } catch {
       messages.value = []
-      unreadCount.value = 0
+    } finally {
       loading.value = false
-    }, 300)
+    }
   }
 
-  function markRead(id: string): void {
-    const msg = messages.value.find(m => m.id === id)
-    if (msg && !msg.isRead) {
-      msg.isRead = true
-      unreadCount.value = Math.max(0, unreadCount.value - 1)
+  async function markRead(id: string): Promise<void> {
+    try {
+      await getNotificationController().markRead(Number(id))
+      const msg = messages.value.find(m => m.id === id)
+      if (msg && !msg.isRead) {
+        msg.isRead = true
+        unreadCount.value = Math.max(0, unreadCount.value - 1)
+      }
+    } catch {
+      // ignore
     }
   }
 

+ 37 - 15
src/store/order.ts

@@ -1,32 +1,54 @@
 import { defineStore } from "pinia"
 import { ref } from "vue"
 import type { OrderItem } from "../type"
+import { getTipOrderController } from "../api/tip-order-controller"
+import type { ResponsePageVoListOrderTipVo, OrderTipVo } from "../api/models"
+
+function mapOrderVo(v: OrderTipVo): OrderItem {
+  return {
+    id: v.id ?? '',
+    postTitle: v.postTitle ?? '',
+    expertName: v.expertName ?? '',
+    amount: v.amount ?? 0,
+    status: v.status ?? '',
+    createTime: v.createTime ?? '',
+  }
+}
 
 export const useOrderStore = defineStore('order', () => {
   const orders = ref<OrderItem[]>([])
-  const activeFilter = ref('全部')
+  const activeFilter = ref('all')
   const loading = ref(false)
 
-  function fetchOrders(filter?: string): void {
+  async function fetchOrders(filter?: string): Promise<void> {
     if (filter !== undefined) activeFilter.value = filter
     loading.value = true
-    // Placeholder
-    setTimeout(() => {
+    try {
+      const res: ResponsePageVoListOrderTipVo = await getTipOrderController().listOrders({
+        status: activeFilter.value,
+        pageNum: 1,
+        pageSize: 50,
+      })
+      if (res.code === 200 && res.data) {
+        orders.value = (res.data.data ?? []).map(mapOrderVo)
+      } else {
+        orders.value = []
+      }
+    } catch {
       orders.value = []
+    } finally {
       loading.value = false
-    }, 300)
-  }
-
-  function createAndPayOrder(_postId: string): Promise<boolean> {
-    return new Promise((resolve) => {
-      // Placeholder
-      setTimeout(() => resolve(true), 500)
-    })
+    }
   }
 
-  function cancelOrder(_id: string): void {
-    // Placeholder
+  async function createAndPayOrder(postId: string): Promise<boolean> {
+    try {
+      const res = await getTipOrderController().createAndPay({ postId: Number(postId) })
+      return res.code === 200
+    } catch {
+      return false
+    }
   }
 
-  return { orders, activeFilter, loading, fetchOrders, createAndPayOrder, cancelOrder }
+  return { orders, activeFilter, loading, fetchOrders, createAndPayOrder }
 })

+ 68 - 10
src/store/post.ts

@@ -1,34 +1,92 @@
 import { defineStore } from "pinia"
 import { reactive, ref } from "vue"
 import type { PostItem, PostDetail, Pagination } from "../type"
+import { getPostController } from "../api/post-controller"
+import type { ResponsePostVo, ResponsePageVoListPostVo, PostVo } from "../api/models"
+
+function mapPostVo(v: PostVo): PostItem {
+  const now = new Date()
+  const pub = v.publishTime ? new Date(v.publishTime) : new Date(0)
+  const isToday =
+    pub.getFullYear() === now.getFullYear() &&
+    pub.getMonth() === now.getMonth() &&
+    pub.getDate() === now.getDate()
+  return {
+    id: v.id ?? '',
+    title: v.title ?? '',
+    expertName: v.expertName ?? '',
+    expertAvatar: v.expertAvatar ?? '',
+    price: v.price ?? 0,
+    publishTime: v.publishTime ?? '',
+    viewCount: v.viewCount ?? 0,
+    isPublic: v.isPublic ?? false,
+    hitStatus: v.hitStatus ?? 'pending',
+    isTodayNew: isToday,
+    expireTime: v.expireTime ?? '',
+  }
+}
 
 export const usePostStore = defineStore('post', () => {
   const posts = ref<PostItem[]>([])
   const currentDetail = ref<PostDetail | null>(null)
-  const currentStatus = ref('全部')
+  const currentStatus = ref('all')
   const searchKeyword = ref('')
   const pagination = reactive<Pagination>({ page: 1, pageSize: 10, total: 0 })
   const loading = ref(false)
 
-  function fetchPosts(status?: string, keyword?: string): void {
+  async function fetchPosts(status?: string, keyword?: string): Promise<void> {
     if (status !== undefined) currentStatus.value = status
     if (keyword !== undefined) searchKeyword.value = keyword
     loading.value = true
-    // Placeholder — replace with actual API call
-    setTimeout(() => {
+    try {
+      const res: ResponsePageVoListPostVo = await getPostController().listPosts({
+        status: currentStatus.value,
+        keyword: searchKeyword.value,
+        pageNum: pagination.page,
+        pageSize: pagination.pageSize,
+      })
+      if (res.code === 200 && res.data) {
+        posts.value = (res.data.data ?? []).map(mapPostVo)
+        pagination.total = res.data.total ?? 0
+      }
+    } catch {
       posts.value = []
-      pagination.total = 0
+    } finally {
       loading.value = false
-    }, 300)
+    }
   }
 
-  function fetchPostDetail(_id: string): void {
+  async function fetchPostDetail(id: string): Promise<void> {
     loading.value = true
-    // Placeholder
-    setTimeout(() => {
+    try {
+      const res: ResponsePostVo = await getPostController().getPostDetail(Number(id))
+      if (res.code === 200 && res.data) {
+        const v = res.data
+        currentDetail.value = {
+          id: v.id ?? '',
+          title: v.title ?? '',
+          contentIntro: v.contentIntro ?? '',
+          contentPaid: v.contentPaid ?? '',
+          price: v.price ?? 0,
+          isPaid: v.isPaid ?? false,
+          isPublic: v.isPublic ?? false,
+          hitStatus: v.hitStatus ?? 'pending',
+          publishTime: v.publishTime ?? '',
+          expireTime: v.expireTime ?? '',
+          expert: {
+            name: v.expertName ?? '',
+            avatar: v.expertAvatar ?? '',
+          },
+          previousPosts: [],
+        }
+      } else {
+        currentDetail.value = null
+      }
+    } catch {
       currentDetail.value = null
+    } finally {
       loading.value = false
-    }, 300)
+    }
   }
 
   function loadMore(): void {

+ 78 - 9
src/store/user.ts

@@ -1,6 +1,9 @@
 import { defineStore } from "pinia"
 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"
 
 export const useLoginUserStore = defineStore('loginUser', () => {
   const loginUser = reactive<LoginUser>({
@@ -8,8 +11,8 @@ export const useLoginUserStore = defineStore('loginUser', () => {
     user: {
       id: "",
       name: "未登录",
-      role: "guest"
-    }
+      role: "guest",
+    },
   })
 
   const userInfo = reactive<UserProfile>({
@@ -18,9 +21,9 @@ export const useLoginUserStore = defineStore('loginUser', () => {
     avatar: '',
     mobile: '',
     role: 'guest',
-    level: 0,
+    level: 'gold',
     balance: 0,
-    isRealname: false
+    isRealname: false,
   })
 
   const token = ref('')
@@ -28,9 +31,9 @@ export const useLoginUserStore = defineStore('loginUser', () => {
   const userId = computed(() => loginUser.user?.id)
   const isLoggedIn = computed(() => loginUser.isLogin)
   const levelBadge = computed(() => {
-    if (userInfo.level >= 80) return '大师'
-    if (userInfo.level >= 50) return '钻石'
-    if (userInfo.level >= 20) return '黄金'
+    if (userInfo.level === 'master') return '大师'
+    if (userInfo.level === 'diamond') return '钻石'
+    if (userInfo.level === 'gold') return '黄金'
     return '初级'
   })
 
@@ -54,7 +57,7 @@ export const useLoginUserStore = defineStore('loginUser', () => {
     userInfo.username = ''
     userInfo.avatar = ''
     userInfo.role = 'guest'
-    userInfo.level = 0
+    userInfo.level = 'gold'
     userInfo.balance = 0
     userInfo.mobile = ''
     userInfo.isRealname = false
@@ -81,9 +84,75 @@ export const useLoginUserStore = defineStore('loginUser', () => {
     Object.assign(userInfo, data)
   }
 
+  async function login(account: string, password: string): Promise<boolean> {
+    try {
+      const res: ResponseAuthTokenVo = await getAuthController().login({ account, password })
+      if (res.code === 200 && res.data) {
+        const t = res.data
+        loginSuccess(
+          {
+            id: String(t.userId ?? ''),
+            name: t.username ?? '',
+            role: (t.role as User['role']) ?? 'user',
+          },
+          t.token ?? '',
+        )
+        await fetchProfile()
+        return true
+      }
+      return false
+    } catch {
+      return false
+    }
+  }
+
+  async function register(account: string, password: string, username: string): Promise<boolean> {
+    try {
+      const res: ResponseAuthTokenVo = await getAuthController().register({ account, password, username })
+      if (res.code === 200 && res.data) {
+        const t = res.data
+        loginSuccess(
+          {
+            id: String(t.userId ?? ''),
+            name: t.username ?? '',
+            role: (t.role as User['role']) ?? 'user',
+          },
+          t.token ?? '',
+        )
+        await fetchProfile()
+        return true
+      }
+      return false
+    } catch {
+      return false
+    }
+  }
+
+  async function fetchProfile(): Promise<void> {
+    try {
+      const res: ResponseMapStringObject = await getUserController().profile()
+      if (res.code === 200 && res.data) {
+        const p = res.data as Record<string, unknown>
+        fetchUserInfo({
+          id: String(p.id ?? ''),
+          username: String(p.username ?? ''),
+          avatar: String(p.avatar ?? ''),
+          mobile: String(p.phoneNumber ?? ''),
+          role: String(p.role ?? 'user'),
+          level: String(p.level ?? 'gold'),
+          balance: Number(p.balance ?? 0),
+          isRealname: Boolean(p.isRealname ?? false),
+        })
+      }
+    } catch {
+      // ignore
+    }
+  }
+
   return {
     loginUser, userInfo, token,
     userId, isLoggedIn, levelBadge,
-    updateUser, loginSuccess, logoutUser, fetchUserInfo, updateBalance, updateProfile
+    updateUser, loginSuccess, logoutUser, fetchUserInfo, updateBalance, updateProfile,
+    login, register, fetchProfile,
   }
 }, { persist: true })

+ 70 - 0
src/store/wallet.ts

@@ -0,0 +1,70 @@
+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"
+
+export const useWalletStore = defineStore('wallet', () => {
+  const balance = ref(0)
+  const transactions = ref<TransactionItem[]>([])
+  const loading = ref(false)
+
+  async function fetchBalance(): Promise<void> {
+    try {
+      const res: ResponseMapStringObject = await getWalletController().getBalance()
+      if (res.code === 200 && res.data) {
+        balance.value = (res.data as { balance?: number }).balance ?? 0
+      }
+    } catch {
+      balance.value = 0
+    }
+  }
+
+  async function fetchTransactions(pageNum = 1, pageSize = 20): Promise<void> {
+    loading.value = true
+    try {
+      const res: ResponseMapStringObject = 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) => ({
+          id: String(i.id ?? ''),
+          type: String(i.type ?? ''),
+          amount: Number(i.amount ?? 0),
+          balanceBefore: Number(i.balanceBefore ?? 0),
+          balanceAfter: Number(i.balanceAfter ?? 0),
+          status: String(i.status ?? ''),
+          remark: String(i.remark ?? ''),
+          createdAt: String(i.createdAt ?? ''),
+        }))
+      }
+    } catch {
+      transactions.value = []
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function recharge(amount: number): Promise<boolean> {
+    try {
+      const res: ResponseMapStringObject = await getWalletController().recharge({ amount })
+      if (res.code === 200) {
+        await fetchBalance()
+        return true
+      }
+      return false
+    } catch {
+      return false
+    }
+  }
+
+  async function applyWithdraw(amount: number): Promise<boolean> {
+    try {
+      const res: ResponseMapStringObject = await getWalletController().applyWithdraw({ amount })
+      return res.code === 200
+    } catch {
+      return false
+    }
+  }
+
+  return { balance, transactions, loading, fetchBalance, fetchTransactions, recharge, applyWithdraw }
+})

+ 21 - 19
src/type/index.ts

@@ -17,7 +17,7 @@ export interface UserProfile {
   avatar: string
   mobile: string
   role: string
-  level: number
+  level: string
   balance: number
   isRealname: boolean
 }
@@ -27,36 +27,35 @@ export interface PostItem {
   title: string
   expertName: string
   expertAvatar: string
-  expertLevel: number
-  tags: string[]
   price: number
   publishTime: string
   viewCount: number
-  status: string
+  isPublic: boolean
+  hitStatus: string
   isTodayNew: boolean
+  expireTime: string
 }
 
 export interface PostDetail {
   id: string
   title: string
-  content: string
-  disclaimer: string
-  payContent: string
+  contentIntro: string
+  contentPaid: string
   price: number
   isPaid: boolean
+  isPublic: boolean
+  hitStatus: string
+  publishTime: string
+  expireTime: string
   expert: {
     name: string
     avatar: string
-    level: number
-    realnameVerified: boolean
-    brief: string
   }
-  tags: string[]
   previousPosts: Array<{
     id: string
     title: string
-    date: string
-    hit: boolean
+    publishTime: string
+    hitStatus: string
   }>
 }
 
@@ -66,24 +65,27 @@ export interface OrderItem {
   expertName: string
   amount: number
   status: string
-  createdAt: string
-  expireAt?: string
+  createTime: string
 }
 
 export interface NotificationItem {
   id: string
   type: string
   title: string
-  summary: string
-  timestamp: string
+  content: string
+  createdAt: string
   isRead: boolean
 }
 
 export interface TransactionItem {
   id: string
-  time: string
-  amount: number
   type: string
+  amount: number
+  balanceBefore: number
+  balanceAfter: number
+  status: string
+  remark: string
+  createdAt: string
 }
 
 export interface Pagination {

+ 2 - 2
src/view/NotificationsView.vue

@@ -12,9 +12,9 @@
         <div class="notif-content">
           <div class="notif-header">
             <span class="notif-title">{{ msg.title }}</span>
-            <span class="notif-time">{{ msg.timestamp }}</span>
+            <span class="notif-time">{{ msg.createdAt }}</span>
           </div>
-          <p class="notif-summary">{{ msg.summary }}</p>
+          <p class="notif-summary">{{ msg.content }}</p>
         </div>
       </div>
       <EmptyState v-if="!notifStore.loading && notifStore.messages.length === 0" description="暂无任何消息通知" />

+ 9 - 9
src/view/OrdersView.vue

@@ -2,10 +2,10 @@
   <div class="orders-page">
     <div class="filter-bar">
       <el-radio-group v-model="activeFilter" @change="doFilter">
-        <el-radio-button value="全部">全部</el-radio-button>
-        <el-radio-button value="未支付">未支付</el-radio-button>
-        <el-radio-button value="已取消">已取消</el-radio-button>
-        <el-radio-button value="已完成">已完成</el-radio-button>
+        <el-radio-button value="all">全部</el-radio-button>
+        <el-radio-button value="unpaid">未支付</el-radio-button>
+        <el-radio-button value="cancelled">已取消</el-radio-button>
+        <el-radio-button value="completed">已完成</el-radio-button>
       </el-radio-group>
     </div>
 
@@ -20,7 +20,7 @@
           <span class="order-amount">¥{{ order.amount }}</span>
         </div>
         <div class="order-bottom">
-          <span class="order-time">{{ order.createdAt }}</span>
+          <span class="order-time">{{ order.createTime }}</span>
           <el-button v-if="order.status === '未支付'" size="small" type="primary">去支付</el-button>
         </div>
       </div>
@@ -35,16 +35,16 @@ import { useOrderStore } from '../store'
 import EmptyState from '../components/EmptyState.vue'
 
 const orderStore = useOrderStore()
-const activeFilter = ref('全部')
+const activeFilter = ref('all')
 
 function doFilter() {
   orderStore.fetchOrders(activeFilter.value)
 }
 
 function statusType(status: string): string {
-  if (status === '已完成') return 'success'
-  if (status === '未支付') return 'warning'
-  if (status === '已取消') return 'info'
+  if (status === 'completed') return 'success'
+  if (status === 'unpaid') return 'warning'
+  if (status === 'cancelled') return 'info'
   return ''
 }
 

+ 23 - 24
src/view/PostDetailView.vue

@@ -5,16 +5,12 @@
 
       <div class="section">
         <h2 class="post-title">{{ detail.title }}</h2>
-        <div class="post-content" v-html="detail.content"></div>
-        <el-alert v-if="detail.disclaimer" :title="detail.disclaimer" type="warning" :closable="false" show-icon class="disclaimer" />
-      </div>
-
-      <div class="section tags-section">
-        <el-tag v-for="tag in detail.tags" :key="tag" class="tag">{{ tag }}</el-tag>
+        <div class="post-content" v-html="detail.contentIntro"></div>
+        <el-alert title="本站所有内容仅代表发布者个人观点,平台对内容的真实性、完整性、及时性不做任何保证,请用户理性参考,谨慎打赏。" type="warning" :closable="false" show-icon class="disclaimer" />
       </div>
 
       <div class="section pay-section">
-        <div v-if="detail.isPaid" class="pay-content" v-html="detail.payContent"></div>
+        <div v-if="detail.isPublic || detail.isPaid" class="pay-content" v-html="detail.contentPaid"></div>
         <div v-else class="pay-cover">
           <el-icon class="lock-icon"><Lock /></el-icon>
           <p class="pay-tip">付费后可查看完整内容</p>
@@ -23,15 +19,16 @@
         </div>
       </div>
 
-      <div v-if="detail.previousPosts.length" class="section">
+      <div class="section">
         <h3 class="section-title">往期命中</h3>
+        <div v-if="detail.previousPosts.length === 0" class="no-data">暂无往期记录</div>
         <div v-for="item in detail.previousPosts" :key="item.id" class="prev-item">
           <div class="prev-info">
             <span class="prev-title">{{ item.title }}</span>
-            <span class="prev-date">{{ item.date }}</span>
+            <span class="prev-date">{{ item.publishTime }}</span>
           </div>
-          <el-tag :type="item.hit ? 'success' : 'info'" size="small">
-            {{ item.hit ? '已命中' : '未命中' }}
+          <el-tag :type="item.hitStatus === 'hit' ? 'success' : 'info'" size="small">
+            {{ item.hitStatus === 'hit' ? '已命中' : item.hitStatus === 'miss' ? '未命中' : '待确认' }}
           </el-tag>
         </div>
       </div>
@@ -56,7 +53,7 @@ import { ref, computed, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import { Lock } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
-import { usePostStore, useLoginUserStore } from '../store'
+import { usePostStore, useLoginUserStore, useOrderStore } from '../store'
 import ExpertInfoCard from '../components/ExpertInfoCard.vue'
 import PayConfirm from '../components/PayConfirm.vue'
 import EmptyState from '../components/EmptyState.vue'
@@ -86,9 +83,14 @@ async function confirmPay() {
   if (!detail.value) return
   paying.value = true
   try {
-    await postStore.fetchPostDetail(route.params.id as string)
-    ElMessage.success('打赏成功')
-    showPay.value = false
+    const ok = await useOrderStore().createAndPayOrder(detail.value.id)
+    if (ok) {
+      await postStore.fetchPostDetail(route.params.id as string)
+      ElMessage.success('打赏成功')
+      showPay.value = false
+    } else {
+      ElMessage.error('支付失败')
+    }
   } catch {
     ElMessage.error('支付失败')
   } finally {
@@ -125,15 +127,6 @@ onMounted(() => {
 .disclaimer {
   margin-top: 12px;
 }
-.tags-section {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 6px;
-  padding: 12px 16px;
-}
-.tag {
-  font-size: 12px;
-}
 .pay-section {
   text-align: center;
 }
@@ -187,4 +180,10 @@ onMounted(() => {
   font-size: 11px;
   color: var(--color-text-secondary, #999);
 }
+.no-data {
+  font-size: 13px;
+  color: var(--color-text-secondary, #999);
+  text-align: center;
+  padding: 16px 0;
+}
 </style>

+ 47 - 39
src/view/WalletView.vue

@@ -2,7 +2,7 @@
   <div class="wallet-page">
     <div class="balance-card">
       <p class="balance-label">当前余额(元)</p>
-      <p class="balance-amount">¥{{ userStore.userInfo.balance.toFixed(2) }}</p>
+      <p class="balance-amount">¥{{ walletStore.balance.toFixed(2) }}</p>
       <div class="balance-actions">
         <el-button type="primary" @click="showRecharge = true">充值</el-button>
         <el-button @click="showWithdraw = true">提现</el-button>
@@ -12,15 +12,15 @@
     <el-divider />
 
     <h3 class="section-title">资金明细</h3>
-    <div class="transaction-list">
-      <div v-for="item in transactions" :key="item.id" class="tx-item">
+    <div v-loading="walletStore.loading" class="transaction-list">
+      <div v-for="item in walletStore.transactions" :key="item.id" class="tx-item">
         <span class="tx-type">{{ typeLabel(item.type) }}</span>
-        <span class="tx-amount" :class="item.amount > 0 ? 'income' : 'expense'">
-          {{ item.amount > 0 ? '+' : '' }}{{ item.amount.toFixed(2) }}
+        <span class="tx-amount" :class="(item.amount ?? 0) >= 0 ? 'income' : 'expense'">
+          {{ (item.amount ?? 0) >= 0 ? '+' : '' }}{{ (item.amount ?? 0).toFixed(2) }}
         </span>
-        <span class="tx-time">{{ item.time }}</span>
+        <span class="tx-time">{{ item.createdAt }}</span>
       </div>
-      <EmptyState v-if="transactions.length === 0" description="暂无资金明细" />
+      <EmptyState v-if="!walletStore.loading && walletStore.transactions.length === 0" description="暂无资金明细" />
     </div>
 
     <el-dialog v-model="showRecharge" title="充值" width="85%">
@@ -38,7 +38,7 @@
     <el-dialog v-model="showWithdraw" title="提现" width="85%">
       <el-form>
         <el-form-item label="提现金额">
-          <el-input-number v-model="withdrawAmount" :min="1" :max="userStore.userInfo.balance" />
+          <el-input-number v-model="withdrawAmount" :min="1" :max="walletStore.balance" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -50,61 +50,69 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
-import { useLoginUserStore } from '../store'
+import { useWalletStore } from '../store'
 import EmptyState from '../components/EmptyState.vue'
-import type { TransactionItem } from '../type'
 
-const userStore = useLoginUserStore()
+const walletStore = useWalletStore()
 const showRecharge = ref(false)
 const showWithdraw = ref(false)
 const rechargeAmount = ref(100)
 const withdrawAmount = ref(100)
 const recharging = ref(false)
 const withdrawing = ref(false)
-const transactions = ref<TransactionItem[]>([])
 
 function typeLabel(type: string): string {
-  const map: Record<string, string> = { recharge: '充值', withdraw: '提现', payment: '打赏支出' }
+  const map: Record<string, string> = { recharge: '充值', withdraw: '提现', tip_out: '打赏支出', admin_adjust: '管理员调整' }
   return map[type] || type
 }
 
-function doRecharge() {
+async function doRecharge() {
   recharging.value = true
-  setTimeout(() => {
-    userStore.updateBalance(rechargeAmount.value)
-    transactions.value.unshift({
-      id: Date.now().toString(),
-      time: new Date().toLocaleString(),
-      amount: rechargeAmount.value,
-      type: 'recharge'
-    })
-    ElMessage.success('充值成功')
-    showRecharge.value = false
+  try {
+    const ok = await walletStore.recharge(rechargeAmount.value)
+    if (ok) {
+      ElMessage.success('充值成功')
+      showRecharge.value = false
+      await walletStore.fetchTransactions()
+    } else {
+      ElMessage.error('充值失败')
+    }
+  } catch {
+    ElMessage.error('充值失败')
+  } finally {
     recharging.value = false
-  }, 500)
+  }
 }
 
-function doWithdraw() {
-  if (withdrawAmount.value > userStore.userInfo.balance) {
+async function doWithdraw() {
+  if (withdrawAmount.value > walletStore.balance) {
     ElMessage.warning('余额不足')
     return
   }
   withdrawing.value = true
-  setTimeout(() => {
-    userStore.updateBalance(-withdrawAmount.value)
-    transactions.value.unshift({
-      id: Date.now().toString(),
-      time: new Date().toLocaleString(),
-      amount: -withdrawAmount.value,
-      type: 'withdraw'
-    })
-    ElMessage.success('提现申请已提交,等待处理')
-    showWithdraw.value = false
+  try {
+    const ok = await walletStore.applyWithdraw(withdrawAmount.value)
+    if (ok) {
+      ElMessage.success('提现申请已提交,等待处理')
+      showWithdraw.value = false
+      await walletStore.fetchBalance()
+      await walletStore.fetchTransactions()
+    } else {
+      ElMessage.error('提现申请失败')
+    }
+  } catch {
+    ElMessage.error('提现申请失败')
+  } finally {
     withdrawing.value = false
-  }, 500)
+  }
 }
+
+onMounted(() => {
+  walletStore.fetchBalance()
+  walletStore.fetchTransactions()
+})
 </script>
 
 <style scoped>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini