16 Commits 2f4b5859c9 ... 94554d8188

Tác giả SHA1 Thông báo Ngày
  zhuli 94554d8188 Merge branch 'development_new' into development 5 ngày trước cách đây
  zhuli fba5eeff4a 动态 5 ngày trước cách đây
  congxuesong 888c58a536 注释代码 1 tuần trước cách đây
  congxuesong 6bc19e0011 feat(ai): 集成AI服务登录与活动宣传图生成功能 1 tuần trước cách đây
  zhuli 0e74bc2964 feat(dynamicManagement): 优化动态列表与详情页点赞逻辑及UI结构 1 tuần trước cách đây
  zhuli 2796fdc59c 修正 1 tuần trước cách đây
  zhuli 3e48a375b0 修正 1 tuần trước cách đây
  sunshibo d55f93d98a 接口对接 黑名单列表。拉黑,解除拉黑,动态广场列表,关注取消关注,动态详情,关注详情,发布动态,他人主页 1 tuần trước cách đây
  zhuli 25bef9d670 动态 1 tuần trước cách đây
  zhuli 4a090b4fc4 动态 1 tuần trước cách đây
  congxuesong af3248d67b 提交代码 1 tuần trước cách đây
  congxuesong 83fb0193de Merge branch 'development' into development_new 1 tuần trước cách đây
  congxuesong e84e4d47c9 feat(dynamicManagement): 新增好友关系与评论申诉管理功能 1 tuần trước cách đây
  congxuesong 6bc1cb9002 Merge branch 'development' into development_new 1 tuần trước cách đây
  congxuesong 2bc9ab3e23 feat(dynamicManagement): 新增好友优惠券管理功能 1 tuần trước cách đây
  zhuli 95507e05a3 动态 1 tuần trước cách đây
32 tập tin đã thay đổi với 12804 bổ sung11 xóa
  1. 1 1
      .env.development
  2. 4 0
      .env.production
  3. 1 1
      build/getEnv.ts
  4. 223 0
      src/api/indexAi.ts
  5. 287 0
      src/api/modules/dynamicManagement.ts
  6. 30 0
      src/api/modules/friendCoupon.ts
  7. 48 0
      src/api/modules/friendRelation.ts
  8. 125 0
      src/api/modules/newLoginApi.ts
  9. 10 0
      src/api/modules/operationManagement.ts
  10. 35 0
      src/api/modules/reviewAppeal.ts
  11. 2 2
      src/api/modules/upload.ts
  12. 21 0
      src/api/modules/user.ts
  13. 175 0
      src/assets/json/authMenuList.json
  14. 1 0
      src/languages/modules/zh.ts
  15. 8 0
      src/layouts/components/Header/components/Avatar.vue
  16. 245 0
      src/layouts/components/Header/components/BlackListDialog.vue
  17. 1 0
      src/utils/permission.ts
  18. 435 0
      src/views/dynamicManagement/draftDynamic.vue
  19. 454 0
      src/views/dynamicManagement/friendCoupon.vue
  20. 324 0
      src/views/dynamicManagement/friendCouponDetail.vue
  21. 553 0
      src/views/dynamicManagement/friendRelation.vue
  22. 2318 0
      src/views/dynamicManagement/index.vue
  23. 2822 0
      src/views/dynamicManagement/myDynamic.vue
  24. 985 0
      src/views/dynamicManagement/publishDynamic.vue
  25. 689 0
      src/views/dynamicManagement/reviewAppeal.vue
  26. 361 0
      src/views/dynamicManagement/reviewAppealDetail.vue
  27. 293 0
      src/views/dynamicManagement/reviewAppealHistory.vue
  28. 2117 0
      src/views/dynamicManagement/userDynamic.vue
  29. 24 1
      src/views/login/components/LoginForm.vue
  30. 8 0
      src/views/login/index.vue
  31. 200 5
      src/views/operationManagement/newActivity.vue
  32. 4 1
      vite.config.ts

+ 1 - 1
.env.development

@@ -16,7 +16,7 @@ VITE_PWA = false
 
 # 开发环境接口地址
 # VITE_API_URL_STORE = /api  # 开发环境使用
-VITE_API=/api
+VITE_API = /api
 VITE_API_URL_STORE = /api/alienStore #生产环境使用
 VITE_API_URL = /api/alienStore
 VITE_API_URL_SECOND = /api/alienSecond

+ 4 - 0
.env.production

@@ -25,6 +25,7 @@ VITE_PWA = true
 # VITE_PROXY = [["/api","http://localhost:8888"]]
 
 # 生产环境接口地址
+VITE_API = /api
 VITE_API_URL_STORE = /api/alienStore
 VITE_API_URL = /api/alienStore
 VITE_API_URL_SECOND = /api/alienSecond
@@ -32,3 +33,6 @@ VITE_API_URL_PLATFORM = /api/alienStorePlatform
 
 # 生产环境跨域代理,支持配置多个
 VITE_PROXY = [["/alienStore","https://api.ailien.shop/alienStore"]]
+
+# AI接口
+VITE_PROXY_AI = [["/ai-api","http://192.168.2.250:9000"]]

+ 1 - 1
build/getEnv.ts

@@ -27,7 +27,7 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
     let realName = envConf[envName].replace(/\\n/g, "\n");
     realName = realName === "true" ? true : realName === "false" ? false : realName;
     if (envName === "VITE_PORT") realName = Number(realName);
-    if (envName === "VITE_PROXY") {
+    if (envName === "VITE_PROXY" || envName === "VITE_PROXY_AI") {
       try {
         realName = JSON.parse(realName);
       } catch (error) {}

+ 223 - 0
src/api/indexAi.ts

@@ -0,0 +1,223 @@
+import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
+import { showFullScreenLoading, tryHideFullScreenLoading } from "@/components/Loading/fullScreen";
+import { LOGIN_URL } from "@/config";
+import { ElMessage } from "element-plus";
+import { ResultData } from "@/api/interface";
+import { ResultEnum } from "@/enums/httpEnum";
+import { checkStatus } from "./helper/checkStatus";
+import { AxiosCanceler } from "./helper/axiosCancel";
+import { useUserStore } from "@/stores/modules/user";
+import router from "@/routers";
+import { localGet, localSet } from "@/utils";
+
+export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
+  loading?: boolean;
+  cancel?: boolean;
+}
+
+const config = {
+  // AI服务地址,使用 /ai-api 前缀,由 VITE_PROXY_AI 代理到 http://192.168.2.250:9000
+  baseURL: "/ai-api",
+  // 设置超时时间
+  timeout: ResultEnum.TIMEOUT as number,
+  // 跨域时候允许携带凭证
+  withCredentials: true
+};
+
+const axiosCanceler = new AxiosCanceler();
+
+// AI Token存储key
+const AI_TOKEN_KEY = "ai-service-token";
+
+/**
+ * 获取AI服务Token
+ */
+const getAiToken = (): string | null => {
+  return localGet(AI_TOKEN_KEY) || null;
+};
+
+/**
+ * 设置AI服务Token
+ */
+const setAiToken = (token: string) => {
+  localSet(AI_TOKEN_KEY, token);
+};
+
+/**
+ * AI服务登录
+ * @returns AI服务token,如果登录失败返回null
+ */
+export const aiLogin = async (): Promise<string | null> => {
+  try {
+    // 获取主系统的用户信息
+    const userInfo = localGet("geeker-user");
+    if (!userInfo || !userInfo.userInfo) {
+      console.error("无法获取用户信息,AI服务登录失败");
+      return null;
+    }
+
+    // 创建一个临时的axios实例用于登录(不使用拦截器,避免循环)
+    const loginInstance = axios.create({
+      baseURL: "/ai-api",
+      timeout: ResultEnum.TIMEOUT as number,
+      withCredentials: true
+    });
+
+    // 调用AI登录接口,使用主系统的用户名和密码
+    // 根据实际接口要求调整参数
+    const loginParams: any = {
+      username: userInfo.userInfo.nickName,
+      password: userInfo.userInfo.password
+    };
+
+    // 如果没有密码,可能需要使用主系统的token来换取AI服务的token
+    // 或者使用其他认证方式,这里需要根据实际接口文档调整
+    if (!loginParams.password) {
+      // 如果主系统没有存储密码,可能需要使用token或其他方式
+      // 暂时尝试使用主系统的token
+      loginParams.token = userInfo.token;
+    }
+
+    const response = await loginInstance.post("/ai/user-auth-core/api/v1/auth/login", loginParams);
+
+    // 根据实际返回格式调整
+    if (response.data) {
+      // 可能返回格式:{ code: 200, data: { token: "..." } }
+      // 或者:{ code: 200, token: "..." }
+      const token = response.data.data?.token || response.data.token;
+      if (token) {
+        setAiToken(token);
+        return token;
+      }
+    }
+    return null;
+  } catch (error: any) {
+    console.error("AI服务登录失败:", error);
+    // 如果登录失败,返回null,让请求失败
+    return null;
+  }
+};
+
+class RequestHttp {
+  service: AxiosInstance;
+  private isLoggingIn = false; // 防止重复登录
+  private loginPromise: Promise<string | null> | null = null; // 登录Promise,用于并发请求时共享登录结果
+
+  public constructor(config: AxiosRequestConfig) {
+    // instantiation
+    this.service = axios.create(config);
+
+    /**
+     * @description 请求拦截器
+     * 客户端发送请求 -> [请求拦截器] -> 服务器
+     * token校验(JWT) : 先获取AI服务token,如果没有则先登录获取token
+     */
+    this.service.interceptors.request.use(
+      async (config: CustomAxiosRequestConfig) => {
+        // 重复请求不需要取消,在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
+        config.cancel ??= true;
+        config.cancel && axiosCanceler.addPending(config);
+        // 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
+        config.loading ??= true;
+        config.loading && showFullScreenLoading();
+
+        // 获取AI服务token
+        let aiToken = getAiToken();
+
+        // 如果没有token,先登录获取token
+        if (!aiToken) {
+          // 如果正在登录,等待登录完成
+          if (this.isLoggingIn && this.loginPromise) {
+            aiToken = await this.loginPromise;
+          } else {
+            // 开始登录
+            this.isLoggingIn = true;
+            this.loginPromise = aiLogin();
+            aiToken = await this.loginPromise;
+            this.isLoggingIn = false;
+            this.loginPromise = null;
+          }
+        }
+
+        // 设置Authorization header
+        if (config.headers) {
+          if (typeof config.headers.set === "function") {
+            config.headers.set("Authorization", aiToken || "");
+          } else {
+            (config.headers as any)["Authorization"] = aiToken || "";
+          }
+        }
+
+        return config;
+      },
+      (error: AxiosError) => {
+        return Promise.reject(error);
+      }
+    );
+
+    /**
+     * @description 响应拦截器
+     *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
+     */
+    this.service.interceptors.response.use(
+      (response: AxiosResponse & { config: CustomAxiosRequestConfig }) => {
+        const { data, config } = response;
+        const userStore = useUserStore();
+        axiosCanceler.removePending(config);
+        config.loading && tryHideFullScreenLoading();
+        // 登录失效 - AI服务token过期,清除token,下次请求时会重新登录
+        if (data.code == ResultEnum.OVERDUE || data.code === 401) {
+          localSet(AI_TOKEN_KEY, null);
+          // 如果是登录接口本身失败,不显示错误(避免循环)
+          if (!config.url?.includes("/auth/login")) {
+            ElMessage.error(data.msg || "AI服务认证失败,请重试");
+          }
+          return Promise.reject(data);
+        }
+        // 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
+        if (data.code && data.code !== ResultEnum.SUCCESS) {
+          ElMessage.error(data.msg);
+          return Promise.reject(data);
+        }
+        // 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
+        return data;
+      },
+      async (error: AxiosError) => {
+        const { response } = error;
+        tryHideFullScreenLoading();
+        // 请求超时 && 网络错误单独判断,没有 response
+        if (error.message.indexOf("timeout") !== -1) ElMessage.error("请求超时!请您稍后重试");
+        if (error.message.indexOf("Network Error") !== -1) ElMessage.error("网络错误!请您稍后重试");
+        // 根据服务器响应的错误状态码,做不同的处理
+        if (response && response.data && (response.data as any).message) {
+          ElMessage.error((response.data as any).message);
+        } else {
+          if (response) checkStatus(response.status);
+        }
+        // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
+        if (!window.navigator.onLine) router.replace("/500");
+        return Promise.reject(error);
+      }
+    );
+  }
+  /**
+   * @description 常用请求方法封装
+   */
+  get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
+    return this.service.get(url, { params, ..._object });
+  }
+  post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
+    return this.service.post(url, params, _object);
+  }
+  put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
+    return this.service.put(url, params, _object);
+  }
+  delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
+    return this.service.delete(url, { params, ..._object });
+  }
+  download(url: string, params?: object, _object = {}): Promise<BlobPart> {
+    return this.service.post(url, params, { ..._object, responseType: "blob" });
+  }
+}
+
+export default new RequestHttp(config);

+ 287 - 0
src/api/modules/dynamicManagement.ts

@@ -0,0 +1,287 @@
+import { PORT_NONE } from "@/api/config/servicePort";
+import http from "@/api";
+import httpStore from "@/api/indexStore";
+
+/**
+ * 动态管理相关接口
+ */
+
+// 获取动态列表(旧接口)
+export const getDynamicList = (params: {
+  type: string; // recommend 推荐, follow 关注
+  page: number;
+  pageSize: number;
+}) => {
+  return http.get(PORT_NONE + `/dynamic/getDynamicList`, params);
+};
+
+// 获取用户动态列表(新接口)
+// 使用 httpStore,baseURL 为 /api/alienStore
+// 最终请求路径:/api/alienStore/userDynamics/getUserDynamics
+export const getUserDynamics = (params: {
+  type: number; // 2 表示动态类型
+  isfollowed: number; // 0 推荐, 1 关注
+  myself: number; // 0 他人, 1 自己
+  page: number;
+  size: number;
+  phoneId: string; // 店铺ID
+}) => {
+  return httpStore.get(PORT_NONE + `/userDynamics/getUserDynamics`, params);
+};
+
+// 获取他人动态列表
+export const getUserDynamicsList = (params: {
+  myselfPhoneId: string; // 当前登录用户的phoneId
+  phoneId: string; // 目标用户的phoneId
+  type: number; // 2 表示动态类型
+  page: number;
+  size: number;
+}) => {
+  return httpStore.get(PORT_NONE + `/userDynamics/getUserDynamicsList`, params);
+};
+
+// 获取用户动态详情
+export const getUserDynamicDetail = (params: {
+  dynamicId: number | string; // 动态ID
+  phoneId: string; // 店铺ID
+}) => {
+  return httpStore.get(PORT_NONE + `/userDynamics/getUserDynamicDetail`, params);
+};
+
+// 发布动态(旧接口)
+export const publishDynamic = (params: { title: string; content: string; imageUrl: string }) => {
+  return http.post(PORT_NONE + `/dynamic/publishDynamic`, params);
+};
+
+// 发布/更新动态(新接口)
+export const addOrUpdateDynamic = (params: {
+  address?: string; // 经纬度
+  addressName?: string; // 地址名称
+  addressProvince?: string; // 省市
+  context: string; // 正文内容
+  createId: number | string; // 创建者ID
+  draft: number; // 0表示发布,1表示草稿
+  imagePath: string; // 图片/视频路径(多个用逗号分隔)
+  phoneId: string; // 店铺ID
+  title: string; // 标题
+  type: string; // "2" 表示动态类型
+  id?: number | string; // 动态ID(更新时需要)
+}) => {
+  return httpStore.post(PORT_NONE + `/userDynamics/addOrUpdate`, params);
+};
+
+// 删除动态
+export const deleteDynamic = (params: { id: number }) => {
+  return http.post(PORT_NONE + `/dynamic/deleteDynamic`, params);
+};
+
+// 点赞动态
+export const likeDynamic = (params: { id: number }) => {
+  return http.post(PORT_NONE + `/dynamic/likeDynamic`, params);
+};
+
+// 取消点赞
+export const unlikeDynamic = (params: { id: number }) => {
+  return http.post(PORT_NONE + `/dynamic/unlikeDynamic`, params);
+};
+
+// 获取动态详情
+export const getDynamicDetail = (params: { id: number }) => {
+  return http.get(PORT_NONE + `/dynamic/getDynamicDetail`, params);
+};
+
+// 上传图片
+export const uploadDynamicImage = (params: FormData) => {
+  return http.post(PORT_NONE + `/dynamic/uploadImage`, params);
+};
+
+// 保存草稿
+export const saveDraft = (params: {
+  title: string;
+  content: string;
+  images: string[];
+  location?: string;
+  locationId?: string;
+}) => {
+  return http.post(PORT_NONE + `/dynamic/saveDraft`, params);
+};
+
+// 获取草稿列表
+export const getDraftList = (params: { page: number; pageSize: number }) => {
+  return http.get(PORT_NONE + `/dynamic/getDraftList`, params);
+};
+
+// 删除草稿
+export const deleteDraft = (params: { id: number }) => {
+  return http.post(PORT_NONE + `/dynamic/deleteDraft`, params);
+};
+
+// 获取位置列表
+export const getLocationList = (params: { keyword?: string }) => {
+  return http.get(PORT_NONE + `/dynamic/getLocationList`, params);
+};
+
+// 获取我的动态列表
+export const getMyDynamicList = (params: { page: number; pageSize: number }) => {
+  return http.get(PORT_NONE + `/dynamic/getMyDynamicList`, params);
+};
+
+// 获取我赞过的列表
+export const getMyLikedList = (params: { page: number; pageSize: number }) => {
+  return http.get(PORT_NONE + `/dynamic/getMyLikedList`, params);
+};
+
+// 获取用户信息
+export const getUserInfo = () => {
+  return http.get(PORT_NONE + `/dynamic/getUserInfo`);
+};
+
+// 获取关系列表(好友/关注/粉丝)
+export const getRelationList = (params: { type: "friend" | "follow" | "fans" }) => {
+  return http.get(PORT_NONE + `/dynamic/getRelationList`, params);
+};
+
+// 关注用户(旧接口)
+export const followUser = (params: { userId: number }) => {
+  return http.post(PORT_NONE + `/dynamic/followUser`, params);
+};
+
+// 取消关注用户(旧接口)
+export const unfollowUser = (params: { userId: number }) => {
+  return http.post(PORT_NONE + `/dynamic/unfollowUser`, params);
+};
+
+// 关注/取消关注用户(新接口)
+export const toggleFollowUser = (params: {
+  followedId: string | number; // 被关注用户phoneId
+  fansId: string; // 当前用户phoneId
+  fansType: number; // 2表示关注
+}) => {
+  return httpStore.post(PORT_NONE + `/user/addFans`, params);
+};
+
+// 取消关注用户
+export const cancelFollowed = (params: {
+  followedId: string; // 被关注用户phoneId
+  fansId: string; // 当前用户phoneId
+}) => {
+  return httpStore.post(PORT_NONE + `/user/cancelFollewed`, params);
+};
+
+// 点赞动态(新接口)- 表单方式提交
+export const likeDynamicNew = (params: {
+  userId: string; // 当前用户phoneId
+  huifuId: number; // 动态ID
+  type: number; // 类型:2表示点赞
+}) => {
+  const formData = new FormData();
+  formData.append("userId", params.userId);
+  formData.append("huifuId", String(params.huifuId));
+  formData.append("type", String(params.type));
+  return httpStore.post(PORT_NONE + `/comment/like`, formData);
+};
+
+// 取消点赞动态(新接口)- 表单方式提交
+export const unlikeDynamicNew = (params: {
+  userId: string; // 当前用户phoneId
+  huifuId: number; // 动态ID
+  type: number; // 类型:2表示点赞
+}) => {
+  const formData = new FormData();
+  formData.append("userId", params.userId);
+  formData.append("huifuId", String(params.huifuId));
+  formData.append("type", String(params.type));
+  return httpStore.post(PORT_NONE + `/comment/cancelLike`, formData);
+};
+
+// 列表点赞动态 - 表单方式提交
+export const likeDynamicList = (params: {
+  dynamicId: number; // 动态ID
+  phoneId: string; // 当前用户phoneId
+}) => {
+  const formData = new FormData();
+  formData.append("dynamicId", String(params.dynamicId));
+  formData.append("phoneId", params.phoneId);
+  return httpStore.post(PORT_NONE + `/userDynamics/like`, formData);
+};
+
+// 举报动态
+export const reportDynamic = (params: { dynamicId: number; reason: string; description: string; images: string[] }) => {
+  return http.post(PORT_NONE + `/dynamic/reportDynamic`, params);
+};
+
+// 举报动态(新接口)
+export const reportUserViolation = (params: {
+  dynamicsId: number | string;
+  otherReasonContent: string;
+  reportContextType: string;
+  reportEvidenceImg: string;
+  reportedUserId: string | number;
+  reportedUserType: number;
+  reportingUserId: string | number;
+  reportingUserType: number;
+  violationType: number;
+}) => {
+  return httpStore.post(PORT_NONE + `/user-violation/reporting`, params);
+};
+
+// 拉黑用户
+export const blockUser = (params: {
+  blockerType: number; // 拉黑者类型:1表示商家
+  blockedType: number; // 被拉黑者类型:1表示商家
+  blockerId: number | string; // 拉黑者ID
+  blockedId: number | string; // 被拉黑者ID
+}) => {
+  return httpStore.post(PORT_NONE + `/life-blacklist/blackList`, params);
+};
+
+// 根据手机号获取用户ID
+export const getUserByPhone = (params: {
+  phone: string; // 手机号
+}) => {
+  return httpStore.get(PORT_NONE + `/store/user/getUserByPhone`, params);
+};
+
+// 获取黑名单列表
+export const getBlackList = (params: {
+  userType: number; // 用户类型:1表示商家
+  userId: number | string; // 用户ID(store_user表的ID)
+}) => {
+  return httpStore.get(PORT_NONE + `/life-blacklist/blackListByUserId`, params);
+};
+
+// 取消拉黑
+export const unblockUser = (params: {
+  id: number | string; // 拉黑记录ID
+  blockedId: number | string; // 被拉黑方ID
+  blockedType: number; // 被拉黑方类型:1商户,2用户
+  blockerId: number | string; // 拉黑方ID
+  blockerType: number; // 拉黑方类型:1商户,2用户
+}) => {
+  return httpStore.post(PORT_NONE + `/life-blacklist/cancelBlacklist`, params);
+};
+
+// 获取评论列表
+export const getCommentsList = (params: { dynamicId: number; sortType: string }) => {
+  return http.get(PORT_NONE + `/dynamic/getCommentsList`, params);
+};
+
+// 提交评论
+export const submitComment = (params: { dynamicId: number; content: string; parentId?: number }) => {
+  return http.post(PORT_NONE + `/dynamic/submitComment`, params);
+};
+
+// 点赞评论
+export const likeComment = (params: { commentId: number }) => {
+  return http.post(PORT_NONE + `/dynamic/likeComment`, params);
+};
+
+// 取消点赞评论
+export const unlikeComment = (params: { commentId: number }) => {
+  return http.post(PORT_NONE + `/dynamic/unlikeComment`, params);
+};
+
+//评论列表
+export const commentList = (params: { commentId: number }) => {
+  return http.post(PORT_NONE + `/storeComment/getList`, params);
+};

+ 30 - 0
src/api/modules/friendCoupon.ts

@@ -0,0 +1,30 @@
+import http from "@/api";
+
+/**
+ * @name 好友优惠券管理模块
+ */
+
+// 获取好友优惠券列表
+export const getFriendCouponList = (params: any) => {
+  return http.post(`/life-discount-coupon-store-friend/getReceivedSendFriendCouponList`, params);
+};
+
+// 赠送优惠券给好友
+export const sendCouponToFriend = (params: { friendId: string | number; couponId: string | number; quantity: number }) => {
+  return http.post(`/api/friendCoupon/send`, params);
+};
+
+// 删除好友优惠券记录
+export const deleteFriendCoupon = (params: { id: string | number }) => {
+  return http.post(`/api/friendCoupon/delete`, params);
+};
+
+// 获取好友列表
+export const getFriendList = () => {
+  return http.get(`/api/friendCoupon/friendList`);
+};
+
+// 获取可赠送的优惠券列表
+export const getAvailableCouponList = () => {
+  return http.get(`/api/friendCoupon/availableCoupons`);
+};

+ 48 - 0
src/api/modules/friendRelation.ts

@@ -0,0 +1,48 @@
+import http from "@/api";
+
+/**
+ * @name 好友关系管理模块
+ */
+
+// 获取好友关系列表
+export const getFriendRelationList = (params: any) => {
+  return http.post(`/api/friendRelation/list`, params);
+};
+
+// 添加好友
+export const addFriendRelation = (params: {
+  friendName: string;
+  friendPhone: string;
+  remark?: string;
+  relationType: string | number;
+  couponList?: Array<{ couponId: string | number; quantity: number }>;
+}) => {
+  return http.post(`/api/friendRelation/add`, params);
+};
+
+// 编辑好友信息
+export const updateFriendRelation = (params: {
+  id: string | number;
+  friendName: string;
+  friendPhone: string;
+  remark?: string;
+  relationType: string | number;
+  couponList?: Array<{ couponId: string | number; quantity: number }>;
+}) => {
+  return http.post(`/api/friendRelation/update`, params);
+};
+
+// 删除好友
+export const deleteFriendRelation = (params: { id: string | number }) => {
+  return http.post(`/api/friendRelation/delete`, params);
+};
+
+// 同意好友申请
+export const approveFriend = (params: { id: string | number }) => {
+  return http.post(`/api/friendRelation/approve`, params);
+};
+
+// 拒绝好友申请
+export const rejectFriend = (params: { id: string | number; reason?: string }) => {
+  return http.post(`/api/friendRelation/reject`, params);
+};

+ 125 - 0
src/api/modules/newLoginApi.ts

@@ -73,3 +73,128 @@ export const getAiapprovestoreInfo = params => {
 export const getStoreOcrData = params => {
   return httpLogin.get(`/alienStore/store/info/getStoreOcrData`, params);
 };
+// 获取好友优惠券列表
+export const getFriendCouponList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getReceivedSendFriendCouponList`, params);
+};
+
+// 获取好友优惠券列表
+export const getFriendCouponDetail = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon/getCounponDetailById`, params);
+};
+
+// 好友列表
+export const getMutualAttention = (params: any) => {
+  return httpLogin.get(`/alienStore/stores/getMutualAttention`, params);
+};
+
+//关注列表
+export const getMyFollowed = (params: any) => {
+  return httpLogin.get(`/alienStore/stores/getMyFollowed`, params);
+};
+
+//粉丝列表
+export const getMyUserFans = (params: any) => {
+  return httpLogin.get(`/alienStore/stores/getMyUserFans`, params);
+};
+
+// 优惠券列表
+export const getCouponList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon/getStoreAllCouponListPaginateNot`, params);
+};
+
+// 赠送好友优惠券
+export const setFriendCoupon = (params: any) => {
+  return httpLogin.post(`/alienStore/life-discount-coupon-store-friend/setFriendCoupon`, params);
+};
+
+//好友关系管理列表
+export const getRuleList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getRuleList`, params);
+};
+
+//添加活动-选择商家
+export const getReceivedFriendCouponList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getReceivedFriendCouponList`, params);
+};
+
+//保存活动
+export const saveFriendCouponRule = (params: any) => {
+  return httpLogin.post(`/alienStore/life-discount-coupon-store-friend/saveFriendCouponRule`, params);
+};
+//删除活动
+export const delFriendCouponRule = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/delFriendCouponRule`, params);
+};
+
+//编辑回显
+export const getRuleById = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getRuleById`, params);
+};
+
+//查询评价列表
+export const getList = (params: any) => {
+  return httpLogin.get(`/alienStore/storeComment/getList`, params);
+};
+
+//申诉历史
+export const getAppealHistory = (params: any) => {
+  return httpLogin.get(`/alienStore/storeCommentAppeal/getAppealHistory`, params);
+};
+
+//申诉详情
+export const getAppealDetail = (params: any) => {
+  return httpLogin.get(`/alienStore/storeCommentAppeal/getAppealDetail`, params);
+};
+
+//申诉删除
+
+export const addAppealNew = (params: any) => {
+  return httpLogin.post(`/alienStore/storeCommentAppeal/addAppealNew`, params);
+};
+
+//发表评论
+export const saveComment = (params: any) => {
+  const formData = new FormData();
+  Object.keys(params).forEach(key => {
+    if (params[key] !== undefined && params[key] !== null) {
+      formData.append(key, params[key]);
+    }
+  });
+  return httpLogin.post(`/alienStore/storeComment/saveComment`, formData, {
+    "Content-Type": "multipart/form-data"
+  });
+};
+
+//评论列表
+export const commentList = (params: any) => {
+  return httpLogin.get(`/alienStore/storeComment/getList`, params);
+};
+
+//我的动态
+export const getUserDynamics = (params: any) => {
+  return httpLogin.get(`/alienStore/userDynamics/getUserDynamics`, params);
+};
+
+//本地草稿
+export const getUserDraftDynamics = (params: any) => {
+  return httpLogin.get(`/alienStore/userDynamics/getUserDraftDynamics`, params);
+};
+//我的动态   好友   关注    粉丝
+export const getHomePageInfo = (params: any) => {
+  return httpLogin.get(`/alienStore/stores/getHomePageInfo`, params);
+};
+
+//我的动态   赞过列表
+export const getDianZanList = (params: any) => {
+  return httpLogin.get(`/alienStore/userDynamics/getDianZanList`, params);
+};
+
+//确认分享好友
+export const addTransferCount = (params: any) => {
+  return httpLogin.get(`/alienStore/userDynamics/addTransferCount`, params);
+};
+//删除草稿
+export const deleteDynamicsById = (params: any) => {
+  return httpLogin.get(`/alienStore/userDynamics/deleteDynamicsById`, params);
+};

+ 10 - 0
src/api/modules/operationManagement.ts

@@ -2,6 +2,7 @@ import { ResPage, StoreUser } from "@/api/interface/index";
 import { PORT_NONE } from "@/api/config/servicePort";
 import http from "@/api";
 import http_store from "@/api/indexStore";
+import http_ai from "@/api/indexAi";
 
 /**
  * 获取优惠券列表
@@ -83,3 +84,12 @@ export const updateActivityStatus = params => {
 export const getActivityRuleOptions = (params?: any) => {
   return http_store.get<any>(PORT_NONE + `/userActionRewardRule/getRewardRuleList`, params);
 };
+
+/**
+ * AI生成活动宣传图
+ * @param params 请求参数 { text: string } - 活动描述文本
+ * @returns 生成的图片数据
+ */
+export const generatePromotionImage = (params: { text: string }) => {
+  return http_ai.post<any>(`/ai/life-manager/api/v1/promotion_image/generate`, params);
+};

+ 35 - 0
src/api/modules/reviewAppeal.ts

@@ -0,0 +1,35 @@
+import http from "@/api";
+
+/**
+ * @name 评论申诉管理模块
+ */
+
+// 获取评论列表
+export const getReviewList = (params: { type?: string; page?: number; pageSize?: number }) => {
+  return http.post(`/api/review/list`, params);
+};
+
+// 获取申诉历史列表
+export const getAppealHistory = (params: { type?: string; page?: number; pageSize?: number }) => {
+  return http.post(`/api/review/appealHistory`, params);
+};
+
+// 获取申诉详情
+export const getAppealDetail = (params: { id: string | number }) => {
+  return http.get(`/api/review/appealDetail`, { params });
+};
+
+// 提交评论申诉
+export const submitReviewAppeal = (params: { reviewId: string | number; reason: string; images?: string[] }) => {
+  return http.post(`/api/review/submitAppeal`, params);
+};
+
+// 撤销申诉
+export const cancelAppeal = (params: { appealId: string | number }) => {
+  return http.post(`/api/review/cancelAppeal`, params);
+};
+
+// 获取评论统计数据
+export const getReviewStatistics = () => {
+  return http.get(`/api/review/statistics`);
+};

+ 2 - 2
src/api/modules/upload.ts

@@ -1,7 +1,7 @@
 import { Upload } from "@/api/interface/index";
 import { PORT1 } from "@/api/config/servicePort";
 import { PORT_NONE } from "@/api/config/servicePort";
-
+import httpStore from "@/api/indexStore";
 import http from "@/api";
 
 /**
@@ -9,7 +9,7 @@ import http from "@/api";
  */
 // 图片上传
 export const uploadImg = (params: FormData) => {
-  return http.post<Upload.ResFileUrl>(PORT_NONE + `/file/uploadMore`, params, { cancel: false });
+  return httpStore.post<Upload.ResFileUrl>(PORT_NONE + `/file/uploadMore`, params, { cancel: false });
 };
 
 // 视频上传

+ 21 - 0
src/api/modules/user.ts

@@ -0,0 +1,21 @@
+import { PORT_NONE } from "@/api/config/servicePort";
+import http from "@/api";
+
+/**
+ * 用户相关接口
+ */
+
+// 获取黑名单列表
+export const getBlacklist = () => {
+  return http.get(PORT_NONE + `/user/getBlacklist`);
+};
+
+// 解除拉黑用户
+export const unblockUser = (params: { userId: number }) => {
+  return http.post(PORT_NONE + `/user/unblockUser`, params);
+};
+
+// 拉黑用户
+export const blockUser = (params: { userId: number }) => {
+  return http.post(PORT_NONE + `/user/blockUser`, params);
+};

+ 175 - 0
src/assets/json/authMenuList.json

@@ -658,6 +658,181 @@
           }
         }
       ]
+    },
+    {
+      "path": "/dynamicManagement",
+      "name": "dynamicManagement",
+      "redirect": "/dynamicManagement/index",
+      "meta": {
+        "icon": "Operation",
+        "title": "动态",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": false
+      },
+      "children": [
+        {
+          "path": "/dynamicManagement/index",
+          "name": "dynamicManagementIndex",
+          "component": "/dynamicManagement/index",
+          "meta": {
+            "icon": "Setting",
+            "title": "动态广场",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/publishDynamic",
+          "name": "publishDynamic",
+          "component": "/dynamicManagement/publishDynamic",
+          "meta": {
+            "icon": "Setting",
+            "title": "动态发布",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/myDynamic",
+          "name": "myDynamic",
+          "component": "/dynamicManagement/myDynamic",
+          "meta": {
+            "icon": "Setting",
+            "title": "我的动态",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/userDynamic",
+          "name": "userDynamic",
+          "component": "/dynamicManagement/userDynamic",
+          "meta": {
+            "icon": "User",
+            "title": "他人动态主页",
+            "activeMenu": "/dynamicManagement/index",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/reviewAppeal",
+          "name": "reviewAppeal",
+          "component": "/dynamicManagement/reviewAppeal",
+          "meta": {
+            "icon": "ChatDotSquare",
+            "title": "评论申诉",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/reviewAppealHistory",
+          "name": "reviewAppealHistory",
+          "component": "/dynamicManagement/reviewAppealHistory",
+          "meta": {
+            "icon": "Document",
+            "title": "申诉历史",
+            "activeMenu": "/dynamicManagement/reviewAppeal",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/reviewAppealDetail",
+          "name": "reviewAppealDetail",
+          "component": "/dynamicManagement/reviewAppealDetail",
+          "meta": {
+            "icon": "Document",
+            "title": "申诉详情",
+            "activeMenu": "/dynamicManagement/reviewAppeal",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/friendRelation",
+          "name": "friendRelation",
+          "component": "/dynamicManagement/friendRelation",
+          "meta": {
+            "icon": "User",
+            "title": "好友关系管理",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/friendCoupon",
+          "name": "friendCoupon",
+          "component": "/dynamicManagement/friendCoupon",
+          "meta": {
+            "icon": "Tickets",
+            "title": "好友赠券管理",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/friendCouponDetail",
+          "name": "friendCouponDetail",
+          "component": "/dynamicManagement/friendCouponDetail",
+          "meta": {
+            "icon": "Document",
+            "title": "好友优惠券详情",
+            "activeMenu": "/dynamicManagement/friendCoupon",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/draftDynamic",
+          "name": "draftDynamic",
+          "component": "/dynamicManagement/draftDynamic",
+          "meta": {
+            "icon": "Document",
+            "title": "好友优惠券详情",
+            "activeMenu": "/dynamicManagement/draftDynamic",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        }
+      ]
     }
   ],
   "msg": "成功"

+ 1 - 0
src/languages/modules/zh.ts

@@ -24,6 +24,7 @@ export default {
     exitFullScreen: "退出全屏",
     personalData: "个人信息",
     changePassword: "修改登录密码",
+    blacklist: "黑名单",
     logout: "退出登录"
   }
 };

+ 8 - 0
src/layouts/components/Header/components/Avatar.vue

@@ -11,6 +11,9 @@
         <el-dropdown-item @click="openDialog('passwordRef')">
           <el-icon><Edit /></el-icon>{{ $t("header.changePassword") }}
         </el-dropdown-item>
+        <el-dropdown-item @click="openDialog('blackListRef')">
+          <el-icon><Edit /></el-icon>{{ $t("header.blacklist") }}
+        </el-dropdown-item>
         <el-dropdown-item divided @click="logout">
           <el-icon><SwitchButton /></el-icon>{{ $t("header.logout") }}
         </el-dropdown-item>
@@ -21,6 +24,8 @@
   <InfoDialog ref="infoRef" @avatar-updated="handleAvatarUpdated" />
   <!-- passwordDialog -->
   <PasswordDialog ref="passwordRef" />
+  <!---blackListDialog--->
+  <BlackListDialog ref="blackListRef" />
 </template>
 
 <script setup lang="ts">
@@ -35,6 +40,7 @@ import { useKeepAliveStore } from "@/stores/modules/keepAlive";
 import { ElMessageBox, ElMessage } from "element-plus";
 import InfoDialog from "./InfoDialog.vue";
 import PasswordDialog from "./PasswordDialog.vue";
+import BlackListDialog from "./BlackListDialog.vue";
 import { getMerchantByPhone } from "@/api/modules/homeEntry";
 import { localGet, localSet } from "@/utils/index";
 import { resetRouter } from "@/routers/index";
@@ -185,9 +191,11 @@ const logout = () => {
 // 打开修改密码和个人信息弹窗
 const infoRef = ref<InstanceType<typeof InfoDialog> | null>(null);
 const passwordRef = ref<InstanceType<typeof PasswordDialog> | null>(null);
+const blackListRef = ref<InstanceType<typeof BlackListDialog> | null>(null);
 const openDialog = (ref: string) => {
   if (ref == "infoRef") infoRef.value?.openDialog();
   if (ref == "passwordRef") passwordRef.value?.openDialog();
+  if (ref == "blackListRef") blackListRef.value?.openDialog();
 };
 </script>
 

+ 245 - 0
src/layouts/components/Header/components/BlackListDialog.vue

@@ -0,0 +1,245 @@
+<template>
+  <el-dialog v-model="dialogVisible" title="黑名单" width="600px" destroy-on-close @close="handleClose">
+    <div class="blacklist-content">
+      <!-- 黑名单列表 -->
+      <div class="blacklist-list">
+        <div v-for="user in blacklist" :key="user.id" class="blacklist-item">
+          <div class="user-info">
+            <div class="user-avatar">
+              <img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
+              <el-icon v-else :size="40">
+                <Avatar />
+              </el-icon>
+            </div>
+            <span class="user-name">{{ user.name }}</span>
+          </div>
+          <el-button type="primary" plain size="small" :loading="user.unblocking" @click="handleUnblock(user)">
+            解除拉黑
+          </el-button>
+        </div>
+
+        <!-- 空状态 -->
+        <el-empty v-if="blacklist.length === 0" description="暂无黑名单用户" />
+      </div>
+
+      <!-- 已解除拉黑的用户提示 -->
+      <div v-if="unblockedUsers.length > 0" class="unblocked-section">
+        <div class="unblocked-title">已解除拉黑 ({{ unblockedUsers.length }})</div>
+        <div class="unblocked-list">
+          <el-tag v-for="user in unblockedUsers" :key="user.id" type="info" closable @close="removeUnblockedUser(user.id)">
+            {{ user.name }}
+          </el-tag>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { Avatar } from "@element-plus/icons-vue";
+import { getBlackList, unblockUser } from "@/api/modules/dynamicManagement";
+import { useUserStore } from "@/stores/modules/user";
+
+interface BlacklistUser {
+  id: number;
+  name: string;
+  avatar: string;
+  unblocking: boolean;
+  blockedId: number | string; // 被拉黑方ID
+  blockedType: number; // 被拉黑方类型
+  blockerId: number | string; // 拉黑方ID
+  blockerType: number; // 拉黑方类型
+}
+
+const userStore = useUserStore();
+
+const dialogVisible = ref(false);
+const blacklist = ref<BlacklistUser[]>([]);
+const unblockedUsers = ref<BlacklistUser[]>([]);
+const submitting = ref(false);
+
+// 打开对话框
+const openDialog = () => {
+  dialogVisible.value = true;
+  loadBlacklist();
+  unblockedUsers.value = [];
+};
+
+// 暴露方法给父组件使用
+defineExpose({
+  openDialog
+});
+
+// 加载黑名单列表
+const loadBlacklist = async () => {
+  try {
+    // 获取当前用户信息
+    const userId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
+    const userType = userStore.userInfo?.userType || userStore.userInfo?.type || 1; // 用户类型:1商家,2用户,默认1
+
+    if (!userId) {
+      ElMessage.warning("用户信息异常");
+      return;
+    }
+
+    console.log("当前用户信息:", userStore.userInfo);
+    console.log("当前用户类型:", userType);
+
+    const res = await getBlackList({
+      userType: userType, // 使用动态获取的用户类型
+      userId: userId
+    });
+
+    // 处理返回数据
+    if (res.data) {
+      const responseData = res.data as any;
+      const list = responseData.records || responseData.list || responseData || [];
+      blacklist.value = list.map((item: any) => ({
+        id: item.id,
+        name: item.blockedName || item.userName || item.name || "用户",
+        avatar: item.blockedAvatar || item.avatar || "",
+        unblocking: false,
+        blockedId: item.blockedId,
+        blockedType: item.blockedType || 1,
+        blockerId: item.blockerId,
+        blockerType: item.blockerType || 1
+      }));
+    }
+  } catch (error) {
+    console.error("加载黑名单失败:", error);
+    ElMessage.error("加载黑名单失败");
+  }
+};
+
+// 解除拉黑
+const handleUnblock = async (user: BlacklistUser) => {
+  try {
+    await ElMessageBox.confirm(`确定要将"${user.name}"移出黑名单吗?`, "解除拉黑", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+
+    user.unblocking = true;
+
+    // 调用取消拉黑接口
+    await unblockUser({
+      id: user.id,
+      blockedId: user.blockedId,
+      blockedType: user.blockedType,
+      blockerId: user.blockerId,
+      blockerType: user.blockerType
+    });
+
+    // 从黑名单中移除
+    const index = blacklist.value.findIndex(u => u.id === user.id);
+    if (index > -1) {
+      const removedUser = blacklist.value.splice(index, 1)[0];
+      unblockedUsers.value.push(removedUser);
+    }
+
+    ElMessage.success("解除拉黑成功");
+  } catch (error) {
+    if (error !== "cancel") {
+      console.error("解除拉黑失败:", error);
+      ElMessage.error("解除拉黑失败");
+    }
+  } finally {
+    user.unblocking = false;
+  }
+};
+
+// 移除已解除的用户标签
+const removeUnblockedUser = (userId: number) => {
+  const index = unblockedUsers.value.findIndex(u => u.id === userId);
+  if (index > -1) {
+    unblockedUsers.value.splice(index, 1);
+  }
+};
+
+// 提交(关闭对话框)
+const handleSubmit = () => {
+  if (unblockedUsers.value.length > 0) {
+    ElMessage.success(`已成功解除 ${unblockedUsers.value.length} 位用户的拉黑`);
+  }
+  handleClose();
+};
+
+// 关闭对话框
+const handleClose = () => {
+  dialogVisible.value = false;
+  unblockedUsers.value = [];
+};
+</script>
+
+<style scoped lang="scss">
+.blacklist-content {
+  .blacklist-list {
+    max-height: 400px;
+    overflow-y: auto;
+    .blacklist-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 16px 0;
+      border-bottom: 1px solid #f0f0f0;
+      &:last-child {
+        border-bottom: none;
+      }
+      .user-info {
+        display: flex;
+        flex: 1;
+        gap: 12px;
+        align-items: center;
+        .user-avatar {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 40px;
+          height: 40px;
+          overflow: hidden;
+          background: #f5f7fa;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+        .user-name {
+          font-size: 15px;
+          font-weight: 500;
+          color: #303133;
+        }
+      }
+    }
+  }
+  .unblocked-section {
+    padding-top: 24px;
+    margin-top: 24px;
+    border-top: 1px solid #e4e7ed;
+    .unblocked-title {
+      margin-bottom: 12px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #606266;
+    }
+    .unblocked-list {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      .el-tag {
+        font-size: 13px;
+      }
+    }
+  }
+}
+.dialog-footer {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+}
+</style>

+ 1 - 0
src/utils/permission.ts

@@ -186,6 +186,7 @@ export async function checkMenuClickPermission(path?: string): Promise<{
         messages.push("合同已到期,请上传最新合同。");
       }
       if (foodBusinessLicense) {
+        console.log(foodBusinessLicense);
         messages.push("食品经营许可证已到期,请上传最新许可证。");
       }
       if (entertainmentBusinessLicense) {

+ 435 - 0
src/views/dynamicManagement/draftDynamic.vue

@@ -0,0 +1,435 @@
+<template>
+  <div class="draft-dynamic-container">
+    <!-- 头部 -->
+    <div class="header-section">
+      <el-button @click="handleGoBack"> 返回 </el-button>
+      <div class="header-title">本地草稿</div>
+      <div class="header-right" />
+    </div>
+
+    <!-- 草稿列表 -->
+    <div v-if="draftList.length > 0" class="content-section">
+      <div class="content-grid">
+        <div v-for="item in draftList" :key="item.id" class="content-card" @click="handleEditDraft(item)">
+          <!-- 封面图片/视频区域 -->
+          <div class="content-cover-wrapper">
+            <img v-if="item.coverUrl" :src="item.coverUrl" :alt="item.title" class="content-cover" />
+            <div v-else class="cover-placeholder">
+              <el-icon :size="48" color="#999">
+                <Picture />
+              </el-icon>
+            </div>
+
+            <!-- 视频播放按钮 -->
+            <div v-if="item.type === 'video'" class="play-overlay">
+              <el-icon :size="40" color="#fff">
+                <VideoPlay />
+              </el-icon>
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="content-info">
+            <div class="content-title">
+              {{ item.title || "未命名" }}
+            </div>
+            <div class="content-meta">
+              <span class="meta-time">{{ formatTime(item.createdTime) }}</span>
+            </div>
+          </div>
+
+          <!-- 操作按钮 -->
+          <div class="card-actions">
+            <el-button size="small" type="primary" plain @click.stop="handleEditDraft(item)">
+              <el-icon>
+                <Edit />
+              </el-icon>
+              编辑
+            </el-button>
+            <el-button size="small" type="danger" plain @click.stop="handleDeleteDraft(item)">
+              <el-icon>
+                <Delete />
+              </el-icon>
+              删除
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div v-else class="empty-section">
+      <el-empty description="暂无数据" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="draftDynamic">
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { ArrowLeft, Picture, VideoPlay, Edit, Delete, Plus } from "@element-plus/icons-vue";
+import { getUserDraftDynamics, deleteDynamicsById } from "@/api/modules/newLoginApi";
+import { deleteDraft } from "@/api/modules/dynamicManagement";
+import { useUserStore } from "@/stores/modules/user";
+
+const router = useRouter();
+const userStore = useUserStore();
+
+// 接口定义
+interface DraftItem {
+  id: number;
+  title: string;
+  coverUrl: string;
+  type: "image" | "video";
+  createdTime: string;
+  context?: string;
+  imagePath?: string;
+  addressName?: string;
+  address?: string;
+  addressProvince?: string;
+}
+
+// 响应式数据
+const draftList = ref<DraftItem[]>([]);
+const loading = ref(false);
+
+// 返回上一页
+const handleGoBack = () => {
+  router.back();
+};
+
+// 格式化时间
+const formatTime = (time: string) => {
+  if (!time) return "";
+
+  const date = new Date(time);
+  const now = new Date();
+  const diff = now.getTime() - date.getTime();
+
+  // 小于1分钟
+  if (diff < 60000) {
+    return "刚刚";
+  }
+
+  // 小于1小时
+  if (diff < 3600000) {
+    return `${Math.floor(diff / 60000)}分钟前`;
+  }
+
+  // 小于1天
+  if (diff < 86400000) {
+    return `${Math.floor(diff / 3600000)}小时前`;
+  }
+
+  // 小于7天
+  if (diff < 604800000) {
+    return `${Math.floor(diff / 86400000)}天前`;
+  }
+
+  // 显示具体日期
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, "0");
+  const day = String(date.getDate()).padStart(2, "0");
+
+  if (year === now.getFullYear()) {
+    return `${month}-${day}`;
+  }
+
+  return `${year}-${month}-${day}`;
+};
+
+// 加载草稿列表
+const loadDraftList = async () => {
+  try {
+    loading.value = true;
+
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 调用接口获取草稿列表
+    const res: any = await getUserDraftDynamics({
+      phoneId: phoneId,
+      page: 1,
+      size: 1000
+    });
+
+    if (res.code === 200) {
+      const dataList = res.data?.records || res.data?.list || [];
+
+      draftList.value = dataList.map((item: any) => {
+        const firstImage = item.imagePath ? item.imagePath.split(",")[0] : "";
+        const isVideo = firstImage.toLowerCase().endsWith(".mp4");
+
+        return {
+          id: item.id,
+          title: item.title || item.context || "未命名",
+          coverUrl: firstImage,
+          type: isVideo ? "video" : "image",
+          createdTime: item.createdTime || item.createdTime || new Date().toISOString(),
+          // 保留原始数据,用于编辑
+          ...item
+        };
+      });
+
+      console.log("加载草稿列表成功:", draftList.value);
+    }
+  } catch (error) {
+    console.error("加载草稿列表失败:", error);
+    ElMessage.error("加载草稿列表失败");
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 编辑草稿
+const handleEditDraft = (item: DraftItem) => {
+  // 跳转到发布动态页面,并传递草稿ID
+  router.push({
+    path: "/dynamicManagement/publishDynamic",
+    query: {
+      draftId: item.id,
+      mode: "edit"
+    }
+  });
+};
+
+// 删除草稿
+const handleDeleteDraft = async (item: DraftItem) => {
+  try {
+    await ElMessageBox.confirm("确定要删除这条草稿吗?删除后将无法恢复。", "删除确认", {
+      confirmButtonText: "确定删除",
+      cancelButtonText: "取消",
+      type: "warning",
+      confirmButtonClass: "el-button--danger"
+    });
+
+    // 调用删除接口
+    await deleteDynamicsById({
+      id: item.id
+    });
+
+    ElMessage.success("删除成功");
+
+    // 从列表中移除该项
+    const index = draftList.value.findIndex(draft => draft.id === item.id);
+    if (index > -1) {
+      draftList.value.splice(index, 1);
+    }
+  } catch (error: any) {
+    // 用户取消删除或删除失败
+    if (error !== "cancel") {
+      console.error("删除草稿失败:", error);
+      ElMessage.error("删除草稿失败");
+    }
+  }
+};
+
+// 创建新动态
+const handleCreateNew = () => {
+  router.push("/dynamicManagement/publishDynamic");
+};
+
+// 初始化
+onMounted(() => {
+  loadDraftList();
+});
+</script>
+
+<style scoped lang="scss">
+.draft-dynamic-container {
+  min-height: calc(100vh - 120px);
+  padding: 20px;
+  background: #ffffff;
+
+  // 头部区域
+  .header-section {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 16px;
+    margin-bottom: 24px;
+    border-bottom: 1px solid #e4e7ed;
+    .back-btn {
+      display: flex;
+      gap: 4px;
+      align-items: center;
+      padding: 8px 16px;
+      font-size: 15px;
+      color: #606266;
+      transition: all 0.3s;
+      &:hover {
+        color: #409eff;
+        background: #ecf5ff;
+      }
+    }
+    .header-title {
+      flex: 1;
+      font-size: 20px;
+      font-weight: 600;
+      color: #303133;
+      text-align: center;
+    }
+    .header-right {
+      width: 100px;
+    }
+  }
+
+  // 内容区域
+  .content-section {
+    margin-top: 20px;
+  }
+
+  // 内容网格布局
+  .content-grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 20px;
+
+    @media (width <= 1400px) {
+      grid-template-columns: repeat(3, 1fr);
+    }
+
+    @media (width <= 1024px) {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    @media (width <= 768px) {
+      grid-template-columns: repeat(1, 1fr);
+    }
+  }
+
+  // 内容卡片
+  .content-card {
+    position: relative;
+    overflow: hidden;
+    cursor: pointer;
+    background: #ffffff;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    transition: all 0.3s;
+    &:hover {
+      border-color: #409eff;
+      box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+      transform: translateY(-2px);
+      .card-actions {
+        opacity: 1;
+      }
+      .play-overlay {
+        opacity: 1;
+      }
+    }
+
+    // 封面区域
+    .content-cover-wrapper {
+      position: relative;
+      width: 100%;
+      aspect-ratio: 16 / 9;
+      overflow: hidden;
+      background: #f5f7fa;
+      .content-cover {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      .cover-placeholder {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+        background: #f5f7fa;
+      }
+
+      // 播放按钮覆盖层
+      .play-overlay {
+        position: absolute;
+        inset: 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: rgb(0 0 0 / 30%);
+        opacity: 0;
+        transition: opacity 0.3s;
+        .el-icon {
+          filter: drop-shadow(0 2px 4px rgb(0 0 0 / 20%));
+        }
+      }
+
+      // 草稿标签
+      .draft-label {
+        position: absolute;
+        top: 8px;
+        left: 8px;
+        display: flex;
+        gap: 4px;
+        align-items: center;
+        padding: 4px 10px;
+        font-size: 12px;
+        color: #ffffff;
+        background: rgb(255 152 0 / 90%);
+        backdrop-filter: blur(4px);
+        border-radius: 4px;
+        .el-icon {
+          font-size: 12px;
+        }
+      }
+    }
+
+    // 内容信息
+    .content-info {
+      padding: 12px;
+      .content-title {
+        margin-bottom: 8px;
+        overflow: hidden;
+        font-size: 15px;
+        font-weight: 500;
+        line-height: 1.4;
+        color: #303133;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+      .content-meta {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-size: 13px;
+        color: #909399;
+        .meta-time {
+          display: flex;
+          gap: 4px;
+          align-items: center;
+        }
+      }
+    }
+
+    // 操作按钮
+    .card-actions {
+      position: absolute;
+      inset: 0;
+      display: flex;
+      gap: 12px;
+      align-items: center;
+      justify-content: center;
+      background: rgb(0 0 0 / 60%);
+      opacity: 0;
+      transition: opacity 0.3s;
+    }
+  }
+
+  // 空状态
+  .empty-section {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-height: 400px;
+    padding: 80px 0;
+    :deep(.el-empty) {
+      .el-button {
+        margin-top: 16px;
+      }
+    }
+  }
+}
+</style>

+ 454 - 0
src/views/dynamicManagement/friendCoupon.vue

@@ -0,0 +1,454 @@
+<template>
+  <div class="table-box button-table friend-coupon-container">
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :request-api="getTableList"
+      :init-param="initParam"
+      :data-callback="dataCallback"
+      :key="activeName"
+    >
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <div class="table-header-content">
+          <div class="header-button">
+            <el-button type="primary" @click="openGiftDialog"> 赠送好友优惠券 </el-button>
+          </div>
+          <el-tabs v-model="activeName" class="header-tabs" @tab-click="handleTabClick">
+            <el-tab-pane label="好友赠我" name="friendMessage" />
+            <el-tab-pane label="我赠好友" name="myGift" />
+          </el-tabs>
+        </div>
+      </template>
+
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button link type="primary" @click="viewDetail(scope.row)"> 查看详情 </el-button>
+        <el-button v-if="activeName === 'myGift'" link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+      </template>
+    </ProTable>
+
+    <!-- 赠送好友优惠券对话框 -->
+    <el-dialog v-model="giftDialogVisible" title="赠送好友优惠券" width="600px" @close="closeGiftDialog">
+      <el-form ref="giftFormRef" :model="giftFormData" :rules="giftRules" label-width="120px">
+        <el-form-item label="选择好友" prop="friendId">
+          <el-select v-model="giftFormData.friendId" placeholder="请选择好友" style="width: 100%" :loading="friendListLoading">
+            <el-option v-for="friend in friendList" :key="friend.id" :label="friend.name" :value="friend.id" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="选择优惠券" prop="couponId">
+          <el-select
+            v-model="giftFormData.couponId"
+            placeholder="请选择优惠券"
+            style="width: 100%"
+            :loading="couponListLoading"
+            @focus="loadCouponList"
+            @change="handleCouponChange"
+          >
+            <el-option v-for="coupon in couponList" :key="coupon.id" :label="coupon.name" :value="coupon.id" />
+          </el-select>
+          <div v-if="giftFormData.couponId" class="coupon-info">
+            <span>请输入赠送数量</span>
+          </div>
+        </el-form-item>
+
+        <el-form-item v-if="giftFormData.couponId" label="赠送数量" prop="quantity">
+          <el-input-number v-model="giftFormData.quantity" :min="1" :max="100" placeholder="请输入数量" style="width: 100%" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeGiftDialog"> 取消 </el-button>
+          <el-button type="primary" @click="handleGiftSubmit"> 确定 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="friendCoupon">
+import { computed, onMounted, reactive, ref } from "vue";
+import { useRouter } from "vue-router";
+import type { FormInstance, FormRules } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
+import ProTable from "@/components/ProTable/index.vue";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { localGet } from "@/utils";
+import { getFriendCouponList, getMutualAttention, getCouponList, setFriendCoupon } from "@/api/modules/newLoginApi";
+
+const router = useRouter();
+
+// 当前激活的标签页
+const activeName = ref("friendMessage");
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>();
+
+// 赠送对话框
+const giftDialogVisible = ref(false);
+const giftFormRef = ref<FormInstance>();
+
+// 好友列表
+const friendList = ref<any[]>([]);
+const friendListLoading = ref(false);
+const friendListLoaded = ref(false); // 标记是否已加载
+
+// 优惠券列表
+const couponList = ref<any[]>([]);
+const couponListLoading = ref(false);
+const couponListLoaded = ref(false); // 标记是否已加载
+
+// 加载好友列表
+const loadFriendList = async () => {
+  // 如果已经加载过,则不重复加载
+  if (friendListLoaded.value || friendListLoading.value) {
+    return;
+  }
+
+  friendListLoading.value = true;
+
+  try {
+    const res: any = await getMutualAttention({
+      page: 1,
+      size: 999, // 获取所有好友
+      fansId: "store_" + localGet("geeker-user")?.userInfo?.phone
+    });
+
+    if (res.code === 200) {
+      friendList.value = res.data.records.map((item: any) => ({
+        id: item.id,
+        name: item.username
+      }));
+      friendListLoaded.value = true;
+    } else {
+      // 如果接口失败,使用模拟数据
+      friendList.value = [];
+      friendListLoaded.value = true;
+    }
+  } catch (error) {
+    console.error("加载好友列表失败:", error);
+    // 使用模拟数据作为备用
+    friendList.value = [];
+    friendListLoaded.value = true;
+  } finally {
+    friendListLoading.value = false;
+  }
+};
+
+// 加载优惠券列表
+const loadCouponList = async () => {
+  // 如果已经加载过,则不重复加载
+  if (couponListLoaded.value || couponListLoading.value) {
+    return;
+  }
+
+  couponListLoading.value = true;
+
+  try {
+    const res: any = await getCouponList({
+      storeId: localGet("createdId"),
+      status: 0
+    });
+
+    if (res.code === 200) {
+      couponList.value = res.data.map((item: any) => ({
+        id: item.id,
+        name: item.name
+      }));
+      couponListLoaded.value = true;
+    } else {
+      // 如果接口失败,使用模拟数据
+      couponList.value = [];
+      couponListLoaded.value = true;
+    }
+  } catch (error) {
+    console.error("加载优惠券列表失败:", error);
+    // 使用模拟数据作为备用
+    couponList.value = [];
+    couponListLoaded.value = true;
+  } finally {
+    couponListLoading.value = false;
+  }
+};
+
+// 赠送表单数据
+const giftFormData = reactive({
+  friendId: "",
+  couponId: "",
+  quantity: 1
+});
+
+// 表单验证规则
+const giftRules = reactive<FormRules>({
+  friendId: [{ required: true, message: "请选择好友", trigger: "change" }],
+  couponId: [{ required: true, message: "请选择优惠券", trigger: "change" }],
+  quantity: [{ required: true, message: "请输入赠送数量", trigger: "blur" }]
+});
+
+// 好友留言表格列配置
+const friendMessageColumns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "storeName",
+    label: "店铺名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "couponName",
+    label: "优惠券名称"
+  },
+  {
+    prop: "nominalValue",
+    label: "优惠券额",
+    render: (scope: any) => {
+      return `¥${scope.row.nominalValue || 0}`;
+    }
+  },
+  {
+    prop: "endDate",
+    label: "有效期至",
+    render: (scope: any) => {
+      return scope.row.endDate?.replace(/-/g, "/") || "--";
+    }
+  },
+  {
+    prop: "acName",
+    label: "赠送人"
+  },
+  {
+    prop: "couponNum",
+    label: "优惠券数量"
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 200 }
+]);
+
+// 我赠好友表格列配置
+const myGiftColumns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "storeName",
+    label: "店铺名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "couponName",
+    label: "优惠券名称"
+  },
+  {
+    prop: "nominalValue",
+    label: "优惠券额",
+    render: (scope: any) => {
+      return `¥${scope.row.nominalValue || 0}`;
+    }
+  },
+  {
+    prop: "endDate",
+    label: "有效期至",
+    render: (scope: any) => {
+      return scope.row.endDate?.replace(/-/g, "/") || "--";
+    }
+  },
+  {
+    prop: "acName",
+    label: "接收人"
+  },
+  {
+    prop: "couponNum",
+    label: "优惠券数量"
+  },
+  {
+    prop: "status",
+    label: "状态",
+    render: (scope: any) => {
+      const statusMap: Record<string, string> = {
+        "0": "未使用",
+        "1": "已使用",
+        "2": "已过期"
+      };
+      return statusMap[scope.row.status] || "--";
+    }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 200 }
+]);
+
+// 根据当前选中的tab动态返回列配置
+const columns = computed(() => {
+  return activeName.value === "friendMessage" ? friendMessageColumns : myGiftColumns;
+});
+// 初始化请求参数 - 好友赠我传 storeUserId
+const initParam = reactive({
+  storeUserId: localGet("createdId"), // 好友赠我:当前店铺ID(接收方)
+  friendStoreUserId: undefined as number | undefined, // 我赠好友:当前用户ID(赠送方)
+  type: activeName.value
+});
+
+// Tab切换处理
+const handleTabClick = () => {
+  initParam.type = activeName.value;
+
+  // 根据当前 tab 设置正确的参数
+  if (activeName.value === "myGift") {
+    // 好友赠我:传 storeUserId
+    initParam.storeUserId = localGet("createdId");
+    initParam.friendStoreUserId = undefined;
+  } else {
+    // 我赠好友:传 friendStoreUserId
+    initParam.storeUserId = undefined;
+    initParam.friendStoreUserId = localGet("geeker-user").userInfo.id;
+  }
+
+  proTable.value?.getTableList();
+};
+
+// dataCallback 是对于返回的表格数据做处理
+const dataCallback = (data: any) => {
+  return {
+    list: data || [],
+    total: data?.length || 0
+  };
+};
+
+// 获取表格列表
+const getTableList = (params: any) => {
+  const newParams = {
+    ...params,
+    type: activeName.value === "friendMessage" ? 0 : 1 // 0-好友赠我,1-我赠好友
+  };
+
+  return getFriendCouponList(newParams);
+};
+
+// 打开赠送对话框
+const openGiftDialog = () => {
+  giftDialogVisible.value = true;
+  loadFriendList();
+  loadCouponList();
+  // 点击下拉框时才会加载数据(通过 @focus 事件触发)
+};
+
+// 关闭赠送对话框
+const closeGiftDialog = () => {
+  giftDialogVisible.value = false;
+  giftFormRef.value?.resetFields();
+  Object.assign(giftFormData, {
+    friendId: "",
+    couponId: "",
+    quantity: 1
+  });
+  // 重置加载状态,下次打开时重新加载
+  friendListLoaded.value = false;
+  couponListLoaded.value = false;
+};
+
+// 优惠券改变时
+const handleCouponChange = (val: string) => {
+  giftFormData.quantity = 1;
+};
+
+// 提交赠送
+const handleGiftSubmit = async () => {
+  if (!giftFormRef.value) return;
+
+  await giftFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        // 调用赠送接口
+        const params = {
+          couponIds: [
+            {
+              couponId: giftFormData.couponId,
+              singleQty: giftFormData.quantity
+            }
+          ],
+          friendStoreUserId: String(giftFormData.friendId)
+        };
+
+        const res: any = await setFriendCoupon(params);
+
+        if (res && res.code === 200) {
+          ElMessage.success("赠送成功");
+          closeGiftDialog();
+          proTable.value?.getTableList();
+        } else {
+          ElMessage.error(res?.msg || "赠送失败");
+        }
+      } catch (error: any) {
+        console.error("赠送失败:", error);
+        ElMessage.error(error?.message || "赠送失败");
+      }
+    }
+  });
+};
+
+// 查看详情
+const viewDetail = (row: any) => {
+  router.push({
+    path: "/dynamicManagement/friendCouponDetail",
+    query: {
+      couponId: row.couponId,
+      type: activeName.value
+    }
+  });
+};
+
+// 删除行数据
+const deleteRow = (row: any) => {
+  ElMessageBox.confirm("确定要删除这条赠送记录吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(async () => {
+      try {
+        // TODO: 集成真实接口时,取消下面的注释
+        // await deleteFriendCoupon({ id: row.id });
+
+        ElMessage.success("删除成功");
+        proTable.value?.getTableList();
+      } catch (error) {
+        ElMessage.error("删除失败");
+      }
+    })
+    .catch(() => {
+      // 用户取消删除
+    });
+};
+
+// 页面加载时触发查询
+onMounted(() => {
+  proTable.value?.getTableList();
+});
+</script>
+
+<style lang="scss" scoped>
+.friend-coupon-container {
+  .table-header-content {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    .header-tabs {
+      :deep(.el-tabs__nav-wrap::after) {
+        height: 0;
+      }
+    }
+    .header-button {
+      display: flex;
+      justify-content: flex-start;
+    }
+  }
+  .coupon-info {
+    margin-top: 8px;
+    font-size: 12px;
+    color: #909399;
+  }
+  .dialog-footer {
+    display: flex;
+    gap: 10px;
+    justify-content: flex-end;
+  }
+}
+</style>

+ 324 - 0
src/views/dynamicManagement/friendCouponDetail.vue

@@ -0,0 +1,324 @@
+<template>
+  <!-- 好友优惠券 - 详情页面 -->
+  <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
+    <div class="header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="title">好友优惠券详情</h2>
+    </div>
+    <div class="content">
+      <!-- 左侧内容区域 -->
+      <div class="contentLeft">
+        <!-- 基础信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">基础信息:</h3>
+          <!-- 店铺名称 -->
+          <div class="detail-item">
+            <div class="detail-label">店铺名称</div>
+            <div class="detail-value">
+              {{ couponModel.name || "--" }}
+            </div>
+          </div>
+          <!-- 优惠券名称 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠券名称</div>
+            <div class="detail-value">
+              {{ couponModel.name || "--" }}
+            </div>
+          </div>
+          <!-- 面值 -->
+          <div class="detail-item">
+            <div class="detail-label">面值(元)</div>
+            <div class="detail-value">
+              {{ formatCurrency(couponModel.nominalValue, 2, "¥") }}
+            </div>
+          </div>
+          <!-- 有效期至 -->
+          <div class="detail-item">
+            <div class="detail-label">有效期至</div>
+            <div class="detail-value">
+              {{ couponModel.endGetDate }}
+            </div>
+          </div>
+          <!-- 优惠券数量 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠券数量</div>
+            <div class="detail-value">
+              {{ couponModel.singleQty || "--" }}
+            </div>
+          </div>
+          <!-- 最低消费金额 -->
+          <div class="detail-item">
+            <div class="detail-label">最低消费金额</div>
+            <div class="detail-value">
+              {{ formatCurrency(couponModel.minimumSpendingAmount, 2, "¥") }}
+            </div>
+          </div>
+        </div>
+        <!-- 好友信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">好友信息:</h3>
+          <!-- 好友名称 -->
+          <div class="detail-item">
+            <div class="detail-label">
+              {{ type === "friendMessage" ? "赠送人" : "接收人" }}
+            </div>
+            <div class="detail-value">
+              {{ couponModel.acName || "--" }}
+            </div>
+          </div>
+          <!-- 状态 -->
+          <div class="detail-item" v-if="type === 'myGift' && couponModel.status !== undefined">
+            <div class="detail-label">状态</div>
+            <div class="detail-value">
+              {{ getStatusText() }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧内容区域 -->
+      <div class="contentRight">
+        <!-- 优惠券详细列表 -->
+        <div
+          class="model"
+          v-if="couponModel.lifeDiscountCouponFriendRuleDetailVos && couponModel.lifeDiscountCouponFriendRuleDetailVos.length > 0"
+        >
+          <h3 style="font-weight: bold">优惠券详细列表:</h3>
+          <div v-for="(detail, index) in couponModel.lifeDiscountCouponFriendRuleDetailVos" :key="index" class="detail-card">
+            <div class="detail-item">
+              <div class="detail-label">店铺名称</div>
+              <div class="detail-value">
+                {{ detail.storeName || "--" }}
+              </div>
+            </div>
+            <div class="detail-item">
+              <div class="detail-label">优惠券名称</div>
+              <div class="detail-value">
+                {{ detail.couponName || "--" }}
+              </div>
+            </div>
+            <div class="detail-item">
+              <div class="detail-label">优惠券数量</div>
+              <div class="detail-value">
+                {{ detail.couponNum || "--" }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="friendCouponDetail">
+/**
+ * 好友优惠券 - 详情页面
+ * 功能:显示好友优惠券的详细信息
+ */
+import { ref, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import { getFriendCouponDetail } from "@/api/modules/newLoginApi";
+import { formatCurrency } from "@/utils/formatCurrency";
+import { localGet } from "@/utils";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面ID参数
+const id = ref<string>("");
+
+// 优惠券类型(好友赠我 friendMessage / 我赠好友 myGift)
+const type = ref<string>("");
+
+// ==================== 优惠券信息数据模型 ====================
+const couponModel = ref<any>({
+  // 账户名称(赠送人/接收人)
+  acName: "",
+  // 优惠券名称
+  couponName: "",
+  // 优惠券数量
+  couponNum: 0,
+  // 删除标志
+  deleteFlag: 0,
+  // 结束日期
+  endDate: "",
+  // ID
+  id: 0,
+  // 图片URL
+  imgUrl: "",
+  // 详细列表
+  lifeDiscountCouponFriendRuleDetailVos: [],
+  // 最低消费金额
+  minimumSpendingAmount: 0,
+  // 金额上限
+  moneyHigh: 0,
+  // 金额下限
+  moneyLow: 0,
+  // 面值
+  nominalValue: 0,
+  // 状态
+  status: "",
+  // 店铺ID
+  storeId: 0,
+  // 店铺名称
+  storeName: ""
+});
+
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取couponId并加载详情数据
+ */
+onMounted(async () => {
+  id.value = (route.query.couponId as string) || "";
+  type.value = (route.query.type as string) || "";
+  if (id.value) {
+    await loadDetailData();
+  } else {
+    ElMessage.warning("缺少优惠券ID参数");
+  }
+});
+
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 数据加载函数 ====================
+
+/**
+ * 加载详情数据
+ */
+const loadDetailData = async () => {
+  try {
+    // 使用 couponId 获取详情数据
+    const res: any = await getFriendCouponDetail({
+      counponId: id.value
+    });
+
+    if (res.code === 200) {
+      couponModel.value = res.data;
+    } else {
+      ElMessage.error(res.msg);
+    }
+  } catch (error) {
+    ElMessage.error("加载详情数据出错");
+  }
+};
+
+/**
+ * 获取状态文本
+ */
+const getStatusText = () => {
+  const statusMap: Record<string, string> = {
+    "0": "未使用",
+    "1": "已使用",
+    "2": "已过期"
+  };
+  return statusMap[couponModel.value.status] || "--";
+};
+</script>
+
+<style scoped lang="scss">
+/* 页面容器 */
+.table-box {
+  display: flex;
+  flex-direction: column;
+  height: auto !important;
+  min-height: 100%;
+}
+
+/* 头部区域 */
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20px 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 24px;
+  width: 98%;
+  padding: 0 12px;
+  margin: 24px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+    padding-right: 12px;
+  }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+    padding-left: 12px;
+  }
+}
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+  h3 {
+    padding-bottom: 12px;
+    margin: 0 0 20px;
+    font-size: 16px;
+    color: #303133;
+  }
+}
+
+/* 详情项样式 */
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  min-height: 32px;
+  margin-bottom: 24px;
+}
+.detail-label {
+  flex-shrink: 0;
+  min-width: 200px;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 32px;
+  color: #606266;
+}
+.detail-value {
+  flex: 1;
+  font-size: 14px;
+  line-height: 32px;
+  color: #303133;
+  word-break: break-word;
+}
+.empty-text {
+  color: #909399;
+}
+
+/* 详情卡片样式 */
+.detail-card {
+  padding: 16px;
+  margin-bottom: 16px;
+  background-color: #f5f7fa;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+}
+</style>

+ 553 - 0
src/views/dynamicManagement/friendRelation.vue

@@ -0,0 +1,553 @@
+<template>
+  <div class="table-box button-table friend-relation-container">
+    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <div class="header-button">
+          <el-button type="primary" @click="openAddDialog"> 添加活动 </el-button>
+        </div>
+      </template>
+
+      <!-- 状态列 -->
+      <template #status="scope">
+        <el-tag :type="getStatusType(scope.row.status)">
+          {{ getStatusText(scope.row.status) }}
+        </el-tag>
+      </template>
+
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
+        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+        <el-button v-if="scope.row.status === 0" link type="primary" @click="handleApprove(scope.row)"> 同意 </el-button>
+      </template>
+    </ProTable>
+
+    <!-- 添加/编辑活动对话框 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" @close="closeDialog">
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px">
+        <el-form-item label="活动名称" prop="acName">
+          <el-input v-model="formData.acName" placeholder="请输入" clearable />
+        </el-form-item>
+
+        <el-form-item label="消费门槛金额(¥)" required>
+          <div style="display: flex; gap: 10px; align-items: center; width: 100%">
+            <el-form-item prop="moneyLow" style=" flex: 1;margin-bottom: 0">
+              <el-input v-model.number="formData.moneyLow" placeholder="0.00" clearable>
+                <template #prefix> ¥ </template>
+              </el-input>
+            </el-form-item>
+            <span>~</span>
+            <el-form-item prop="moneyHigh" style=" flex: 1;margin-bottom: 0">
+              <el-input v-model.number="formData.moneyHigh" placeholder="0.00" clearable>
+                <template #prefix> ¥ </template>
+              </el-input>
+            </el-form-item>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="来源商家及优惠券">
+          <div class="merchant-coupon-list">
+            <div v-for="(item, index) in formData.details" :key="index" class="merchant-coupon-item">
+              <el-select
+                v-model="item.friendStoreUserId"
+                placeholder="选择商家"
+                style="width: 200px; margin-right: 10px"
+                @change="handleMerchantChange(index)"
+              >
+                <el-option
+                  v-for="merchant in merchantList"
+                  :key="merchant.couponId"
+                  :label="merchant.storeName"
+                  :value="merchant.couponId"
+                />
+              </el-select>
+              <el-select
+                v-model="item.couponId"
+                placeholder="选择优惠券"
+                style="width: 200px; margin-right: 10px"
+                @change="handleCouponChange(index)"
+              >
+                <el-option
+                  v-for="coupon in item.couponList"
+                  :key="coupon.couponId || coupon.id"
+                  :label="coupon.couponName"
+                  :value="coupon.couponId || coupon.id"
+                />
+              </el-select>
+              <el-input v-model="item.remainingCount" placeholder="剩余张数1000张" style="width: 180px; margin-right: 10px" />
+              <el-button type="danger" link @click="removeMerchantCoupon(index)" v-if="formData.details.length > 1">
+                删除
+              </el-button>
+            </div>
+            <el-button type="primary" link @click="addMerchantCoupon" style="margin-top: 10px">
+              <el-icon><Plus /></el-icon>
+              添加商家优惠券
+            </el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog"> 取消 </el-button>
+          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="friendRelation">
+import { computed, onMounted, reactive, ref } from "vue";
+import type { FormInstance, FormRules } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { Plus } from "@element-plus/icons-vue";
+import ProTable from "@/components/ProTable/index.vue";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { localGet } from "@/utils";
+
+import {
+  getRuleList,
+  saveFriendCouponRule,
+  getReceivedFriendCouponList,
+  delFriendCouponRule,
+  getRuleById
+} from "@/api/modules/newLoginApi";
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>();
+
+// 对话框相关
+const dialogVisible = ref(false);
+const dialogTitle = computed(() => (isEdit.value ? "编辑活动" : "添加活动"));
+const isEdit = ref(false);
+const currentEditId = ref("");
+
+// 表单引用
+const formRef = ref<FormInstance>();
+
+// 商家列表
+const merchantList = ref<any[]>([]);
+
+// 表单数据
+const formData = reactive({
+  acName: "",
+  moneyLow: undefined as number | undefined,
+  moneyHigh: undefined as number | undefined,
+  details: [] as Array<{
+    friendStoreUserId: string | number;
+    couponId: string | number;
+    couponList: any[]; // 当前商家的优惠券列表
+    remainingCount: number; // 剩余张数
+  }>
+});
+
+// 表单验证规则
+const formRules = reactive<FormRules>({
+  acName: [{ required: true, message: "请输入活动名称", trigger: "blur" }],
+  moneyLow: [{ required: true, message: "请输入最低消费金额", trigger: "blur" }],
+  moneyHigh: [{ required: true, message: "请输入最高消费金额", trigger: "blur" }]
+});
+
+// 表格列配置
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "acName",
+    label: "活动名称"
+  },
+  {
+    prop: "endDate",
+    label: "有效期至"
+  },
+  {
+    prop: "relationType",
+    label: "消费门槛",
+    render: (scope: any) => {
+      return scope.row.moneyLow + "元" + "~" + scope.row.moneyHigh + "元";
+    }
+  },
+  {
+    prop: "status",
+    label: "状态",
+    enum: [
+      { label: "启用中", value: "0" },
+      { label: "已停用", value: "1" }
+    ],
+    fieldNames: { label: "label", value: "value" },
+    render: (scope: any) => {
+      return scope.row.status == "0" ? "启用中" : "已停用";
+    }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 250 }
+]);
+
+// 初始化请求参数
+const initParam = reactive({
+  storeId: localGet("createdId") || ""
+});
+
+// 数据回调处理
+const dataCallback = (data: any) => {
+  return {
+    list: data || [],
+    total: data?.total || 0
+  };
+};
+
+// 获取表格列表
+const getTableList = (params: any) => {
+  return getRuleList(params);
+};
+
+// 获取状态文本
+const getStatusText = (status: number) => {
+  const statusMap: Record<number, string> = {
+    0: "待同意",
+    1: "已同意",
+    2: "已拒绝"
+  };
+  return statusMap[status] || "--";
+};
+
+// 获取状态类型
+const getStatusType = (status: number): "success" | "warning" | "info" | "danger" => {
+  const typeMap: Record<number, "success" | "warning" | "info" | "danger"> = {
+    0: "warning",
+    1: "success",
+    2: "info"
+  };
+  return typeMap[status] || "info";
+};
+
+// 获取商家列表(选择商家)
+const getMerchantList = async () => {
+  try {
+    const res: any = await getReceivedFriendCouponList({
+      storeId: localGet("createdId") || ""
+    });
+    if (res.code == 200) {
+      merchantList.value = res.data;
+      console.log(merchantList.value);
+    }
+  } catch (error) {
+    console.error("获取商家列表失败", error);
+  }
+};
+
+// 打开添加对话框
+const openAddDialog = async () => {
+  isEdit.value = false;
+  await getMerchantList();
+  // 初始化一个空的商家优惠券项
+  formData.details = [
+    {
+      friendStoreUserId: "",
+      couponId: "",
+      couponList: [],
+      remainingCount: 0
+    }
+  ];
+  dialogVisible.value = true;
+};
+
+// 关闭对话框
+const closeDialog = () => {
+  dialogVisible.value = false;
+  formRef.value?.resetFields();
+  Object.assign(formData, {
+    acName: "",
+    moneyLow: undefined,
+    moneyHigh: undefined,
+    details: []
+  });
+  currentEditId.value = "";
+};
+
+// 添加商家优惠券
+const addMerchantCoupon = () => {
+  formData.details.push({
+    friendStoreUserId: "",
+    couponId: "",
+    couponList: [],
+    remainingCount: 0
+  });
+};
+
+// 移除商家优惠券
+const removeMerchantCoupon = (index: number) => {
+  formData.details.splice(index, 1);
+};
+
+// 商家选择改变时,获取该商家的优惠券列表(选择优惠券)
+const handleMerchantChange = async (index: number) => {
+  const item = formData.details[index];
+  item.couponId = ""; // 重置优惠券选择
+  item.remainingCount = 0; // 重置剩余张数
+
+  if (!item.friendStoreUserId) {
+    item.couponList = [];
+    return;
+  }
+
+  // item.friendStoreUserId 存储的是商家的 couponId,需要找到对应商家的 friendStoreUserId
+  const merchant = merchantList.value.find((m: any) => m.couponId === item.friendStoreUserId);
+  if (!merchant) {
+    item.couponList = [];
+    return;
+  }
+
+  try {
+    const res: any = await getReceivedFriendCouponList({
+      storeId: localGet("createdId") || "",
+      friendStoreUserId: merchant.friendStoreUserId
+    });
+    if (res.code == 200) {
+      item.couponList = res.data;
+    } else {
+      item.couponList = [];
+    }
+  } catch (error) {
+    console.error("获取优惠券列表失败", error);
+    item.couponList = [];
+  }
+};
+
+// 优惠券选择改变时,更新剩余张数
+const handleCouponChange = (index: number) => {
+  const item = formData.details[index];
+  const coupon = item.couponList.find((c: any) => (c.couponId || c.id) === item.couponId);
+  if (coupon) {
+    item.remainingCount = coupon.couponNum || 0; // 使用 getReceivedFriendCouponList 返回的 couponNum
+  }
+};
+
+// 编辑行数据
+const editRow = async (row: any) => {
+  isEdit.value = true;
+  currentEditId.value = row.id;
+
+  try {
+    // 1. 获取商家列表
+    await getMerchantList();
+
+    // 2. 调用 getRuleById 获取活动详情
+    const res: any = await getRuleById({ id: row.id });
+
+    if (res.code == 200 && res.data) {
+      const detailData = res.data;
+      console.log("getRuleById 返回的数据:", detailData);
+
+      // 3. 处理 lifeDiscountCouponFriendRuleDetailVos 数据
+      let details: Array<{
+        friendStoreUserId: string | number;
+        couponId: string | number;
+        couponList: any[];
+        remainingCount: number;
+      }> = [];
+
+      const detailVos = detailData.lifeDiscountCouponFriendRuleDetailVos || detailData.details || [];
+
+      if (detailVos && Array.isArray(detailVos) && detailVos.length > 0) {
+        details = await Promise.all(
+          detailVos.map(async (item: any) => {
+            console.log("detail item:", item);
+
+            // 根据 couponId 在商家列表中找到对应的商家
+            const matchedMerchant = merchantList.value.find((m: any) => m.couponId === item.couponId);
+            console.log("匹配到的商家:", matchedMerchant);
+
+            // 如果没有匹配到商家,返回默认值
+            if (!matchedMerchant) {
+              return {
+                friendStoreUserId: item.couponId, // 商家选择框绑定的是 couponId
+                couponId: "",
+                couponList: [],
+                remainingCount: 0
+              };
+            }
+
+            // 获取该商家的优惠券列表
+            let couponList: any[] = [];
+            try {
+              const couponRes: any = await getReceivedFriendCouponList({
+                storeId: localGet("createdId") || "",
+                friendStoreUserId: matchedMerchant.friendStoreUserId
+              });
+              if (couponRes.code == 200) {
+                couponList = couponRes.data || [];
+              }
+            } catch (error) {
+              console.error("获取优惠券列表失败", error);
+            }
+
+            // 在优惠券列表中找到匹配的优惠券
+            let matchedCoupon = couponList.find((c: any) => c.couponId === item.couponId);
+            console.log("匹配到的优惠券:", matchedCoupon);
+
+            // 如果没有在列表中找到,使用 lifeDiscountCouponFriendRuleDetailVos 中的数据创建一个临时对象
+            if (!matchedCoupon && item.couponId) {
+              matchedCoupon = {
+                couponId: item.couponId,
+                couponName: item.couponName, // 使用 getRuleById 返回的 couponName
+                couponNum: item.couponNum || 0 // 使用 getRuleById 返回的 couponNum
+              };
+              // 将这个临时对象添加到列表中,以便下拉框可以显示
+              couponList = [matchedCoupon, ...couponList];
+            }
+
+            return {
+              friendStoreUserId: item.couponId, // 商家选择框的值是商家的 couponId
+              couponId: matchedCoupon?.couponId || item.couponId || "", // 优惠券选择框的值使用 couponId
+              couponList: couponList,
+              remainingCount: matchedCoupon?.couponNum || 0 // 使用 getReceivedFriendCouponList 返回的 couponNum
+            };
+          })
+        );
+      } else {
+        details = [
+          {
+            friendStoreUserId: "",
+            couponId: "",
+            couponList: [],
+            remainingCount: 0
+          }
+        ];
+      }
+
+      Object.assign(formData, {
+        acName: detailData.acName,
+        moneyLow: detailData.moneyLow,
+        moneyHigh: detailData.moneyHigh,
+        details: details
+      });
+
+      dialogVisible.value = true;
+    } else {
+      ElMessage.error(res.msg || "获取活动详情失败");
+    }
+  } catch (error: any) {
+    ElMessage.error(error?.msg || "获取活动详情失败");
+  }
+};
+
+// 删除行数据
+const deleteRow = (row: any) => {
+  ElMessageBox.confirm("确定要删除这个活动吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(async () => {
+      try {
+        const res: any = await delFriendCouponRule({ id: row.id });
+        if (res.code == 200) {
+          ElMessage.success("删除成功");
+          proTable.value?.getTableList();
+        } else {
+          ElMessage.error(res.msg || "删除失败");
+        }
+      } catch (error: any) {
+        ElMessage.error(error?.msg || "删除失败");
+      }
+    })
+    .catch(() => {
+      // 用户取消删除
+    });
+};
+
+// 同意好友申请(此页面可能不需要)
+const handleApprove = async (row: any) => {
+  try {
+    ElMessage.success("操作成功");
+    proTable.value?.getTableList();
+  } catch (error) {
+    ElMessage.error("操作失败");
+  }
+};
+
+// 提交表单
+const handleSubmit = async () => {
+  if (!formRef.value) return;
+
+  await formRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      // 验证是否至少添加了一个商家优惠券
+      if (!formData.details.length) {
+        ElMessage.warning("请至少添加一个商家优惠券");
+        return;
+      }
+
+      // 验证所有商家优惠券项是否都已选择
+      const hasEmpty = formData.details.some(item => !item.friendStoreUserId || !item.couponId);
+      if (hasEmpty) {
+        ElMessage.warning("请完善所有商家优惠券信息");
+        return;
+      }
+
+      try {
+        // 转换数据:item.friendStoreUserId 存储的是商家的 couponId,item.couponId 存储的是优惠券的 couponId
+        const details = formData.details.map(item => {
+          // 找到对应的商家
+          const merchant = merchantList.value.find((m: any) => m.couponId === item.friendStoreUserId);
+          // 找到对应的优惠券
+          const coupon = item.couponList.find((c: any) => (c.couponId || c.id) === item.couponId);
+
+          return {
+            couponId: item.couponId, // 直接使用选中的 couponId
+            friendStoreUserId: merchant?.friendStoreUserId || "" // 使用商家的真实 friendStoreUserId
+          };
+        });
+
+        const params = {
+          storeId: localGet("createdId") || "",
+          acName: formData.acName,
+          moneyHigh: formData.moneyHigh,
+          moneyLow: formData.moneyLow,
+          details: details
+        };
+
+        console.log("提交参数:", params);
+
+        const res: any = await saveFriendCouponRule(params);
+
+        if (res.code == 200) {
+          ElMessage.success(isEdit.value ? "编辑成功" : "添加成功");
+          closeDialog();
+          proTable.value?.getTableList();
+        } else {
+          ElMessage.error(res.msg || "操作失败");
+        }
+      } catch (error: any) {
+        ElMessage.error(error?.msg || (isEdit.value ? "编辑失败" : "添加失败"));
+      }
+    }
+  });
+};
+
+// 页面加载时触发查询
+onMounted(() => {
+  proTable.value?.getTableList();
+});
+</script>
+
+<style lang="scss" scoped>
+.friend-relation-container {
+  .header-button {
+    margin-bottom: 16px;
+  }
+  .merchant-coupon-list {
+    width: 100%;
+    .merchant-coupon-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 10px;
+    }
+  }
+  .dialog-footer {
+    display: flex;
+    gap: 10px;
+    justify-content: flex-end;
+  }
+}
+</style>

+ 2318 - 0
src/views/dynamicManagement/index.vue

@@ -0,0 +1,2318 @@
+<template>
+  <div class="dynamic-management-container">
+    <!-- 头部:Tabs和发布按钮 -->
+    <div class="header-section">
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="推荐" name="recommend" />
+        <el-tab-pane label="关注" name="follow" />
+      </el-tabs>
+      <div class="action-buttons">
+        <el-button type="primary" @click="handlePublish"> 发布动态 </el-button>
+      </div>
+    </div>
+    <!-- 动态列表(推荐和关注共用) -->
+    <div v-if="dynamicList.length > 0" class="content-section">
+      <div class="dynamic-grid">
+        <div v-for="item in paginatedList" :key="item.id" class="dynamic-card" @click="handleCardClick(item)">
+          <!-- 图片/视频区域 -->
+          <div class="dynamic-image-wrapper">
+            <!-- 视频 -->
+            <video v-if="item.isVideo && item.imageUrl" :src="item.imageUrl" class="dynamic-image" controls preload="metadata" />
+            <!-- 图片 -->
+            <img v-else-if="item.imageUrl" :src="item.imageUrl" :alt="item.title" class="dynamic-image" />
+            <!-- 占位符 -->
+            <div v-else class="image-placeholder">
+              <el-icon :size="48" color="#999">
+                <Picture />
+              </el-icon>
+            </div>
+          </div>
+
+          <!-- 动态内容 -->
+          <div class="dynamic-content">
+            <div class="dynamic-text">
+              {{ item.title }}
+            </div>
+
+            <!-- 用户信息和点赞 -->
+            <div class="dynamic-footer">
+              <div class="user-info">
+                <div class="user-avatar">
+                  <img v-if="item.userAvatar" :src="item.userAvatar" :alt="item.userName" />
+                  <el-icon v-else :size="24">
+                    <Avatar />
+                  </el-icon>
+                </div>
+                <span class="user-name">{{ item.userName }}</span>
+              </div>
+
+              <div class="like-section" @click.stop="handleLike(item)">
+                <el-icon :size="18" :color="item.isLike == '1' ? '#f56c6c' : '#999'" class="like-icon">
+                  <Star />
+                </el-icon>
+                <span class="like-count">{{ item.dianzanCount }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div v-else class="empty-section">
+      <el-empty :description="activeTab === 'follow' ? '暂无关注的动态' : '暂无动态数据'" />
+    </div>
+
+    <!-- 分页 -->
+    <div v-if="dynamicList.length > 0" class="pagination-section">
+      <el-pagination
+        v-model:current-page="pagination.page"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[10, 20, 30, 50]"
+        :total="pagination.total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 动态详情 Drawer -->
+    <el-drawer
+      v-model="detailDrawerVisible"
+      direction="rtl"
+      size="90%"
+      :show-close="false"
+      destroy-on-close
+      class="detail-drawer"
+    >
+      <template #header>
+        <div class="drawer-header">
+          <el-button class="close-btn" text @click="handleCloseDetail">
+            <el-icon :size="24">
+              <Close />
+            </el-icon>
+          </el-button>
+        </div>
+      </template>
+
+      <div v-if="currentDetail" class="detail-content">
+        <!-- 主内容区域 -->
+        <div class="detail-main">
+          <!-- 图片/视频轮播展示 -->
+          <div class="media-container">
+            <!-- 多媒体轮播 -->
+            <el-carousel
+              v-if="currentDetail.mediaList && currentDetail.mediaList.length > 0"
+              :autoplay="false"
+              :loop="false"
+              indicator-position="outside"
+              arrow="always"
+              height="100%"
+              class="media-carousel"
+              @change="handleCarouselChange"
+            >
+              <el-carousel-item v-for="(media, index) in currentDetail.mediaList" :key="index">
+                <!-- 视频 -->
+                <video
+                  v-if="media.type === 'video'"
+                  :ref="el => setVideoRef(el, index)"
+                  :src="media.url"
+                  class="detail-media detail-video"
+                  controls
+                  preload="metadata"
+                  @play="handleVideoPlay(index)"
+                />
+                <!-- 图片 -->
+                <img v-else :src="media.url" :alt="currentDetail.title" class="detail-media detail-image" />
+              </el-carousel-item>
+            </el-carousel>
+            <!-- 占位符 -->
+            <div v-else class="media-placeholder">
+              <el-icon :size="80" color="#dcdfe6">
+                <Picture />
+              </el-icon>
+            </div>
+            <!-- 媒体计数指示器 -->
+            <div v-if="currentDetail.mediaList && currentDetail.mediaList.length > 1" class="media-counter">
+              {{ currentCarouselIndex + 1 }} / {{ currentDetail.mediaList.length }}
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="detail-info">
+            <div class="author-info">
+              <div class="author-avatar">
+                <img
+                  v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
+                  :src="currentDetail.author?.avatar || currentDetail.userAvatar"
+                  :alt="currentDetail.author?.name || currentDetail.userName"
+                />
+                <el-icon v-else :size="32">
+                  <Avatar />
+                </el-icon>
+              </div>
+              <div class="author-details">
+                <div class="author-name">@{{ currentDetail.author?.name || currentDetail.userName }}</div>
+                <div class="publish-time">
+                  {{ currentDetail.createdTime }}
+                </div>
+              </div>
+            </div>
+
+            <div class="detail-description">
+              <p>{{ currentDetail.context }}</p>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右侧操作栏 -->
+        <div class="action-bar">
+          <!-- 作者头像 -->
+          <div class="action-item author-action">
+            <div class="action-avatar" @click="handleViewUserProfile">
+              <img
+                v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
+                :src="currentDetail.author?.avatar || currentDetail.userAvatar"
+                :alt="currentDetail.author?.name || currentDetail.userName"
+              />
+              <el-icon v-else :size="40" color="#fff">
+                <Avatar />
+              </el-icon>
+              <!-- 关注按钮 (定位在头像右下角) -->
+              <div v-if="currentDetail.isFollowThis == 0 && !isMyDynamic" class="follow-badge" @click.stop="handleFollowInDetail">
+                <el-icon :size="16" color="#fff">
+                  <Plus />
+                </el-icon>
+              </div>
+            </div>
+          </div>
+
+          <!-- 点赞 -->
+          <div class="action-item" @click="handleDetailLike">
+            <div class="action-icon">
+              <el-icon :size="28" :color="currentDetail.isLike == '1' ? '#f56c6c' : '#fff'">
+                <StarFilled v-if="currentDetail.isLike" />
+                <Star v-else />
+              </el-icon>
+            </div>
+            <div class="action-count">
+              {{ currentDetail.dianzanCount }}
+            </div>
+          </div>
+
+          <!-- 评论 -->
+          <div class="action-item" @click="handleShowComments">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <ChatDotRound />
+              </el-icon>
+            </div>
+            <div class="action-count">
+              {{ currentDetail.commentCount }}
+            </div>
+          </div>
+
+          <!-- 分享 -->
+          <div class="action-item" @click="handleShare">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <Share />
+              </el-icon>
+            </div>
+            <div class="action-count">分享</div>
+          </div>
+
+          <!-- 更多 -->
+          <el-popover placement="left" :width="120" trigger="click" popper-class="more-actions-popover">
+            <template #reference>
+              <div class="action-item">
+                <div class="action-icon">
+                  <el-icon :size="28" color="#fff">
+                    <MoreFilled />
+                  </el-icon>
+                </div>
+              </div>
+            </template>
+            <div class="more-actions-menu">
+              <!-- 如果是当前用户的动态,显示编辑和删除 -->
+              <template v-if="isMyDynamic">
+                <div class="menu-item" style="display: flex; align-items: center; cursor: pointer" @click="handleDeleteDynamic">
+                  <el-icon :size="18">
+                    <Delete /> </el-icon
+                  >&nbsp;&nbsp;
+                  <span>删除</span>
+                </div>
+              </template>
+              <!-- 如果不是当前用户的动态,显示举报和拉黑 -->
+              <template v-else>
+                <div
+                  class="menu-item"
+                  style="display: flex; align-items: center; padding-bottom: 10px; cursor: pointer"
+                  @click="handleReportDynamic"
+                >
+                  <el-icon :size="18">
+                    <Warning /> </el-icon
+                  >&nbsp;&nbsp;
+                  <span>举报</span>
+                </div>
+                <div class="menu-item" style="display: flex; align-items: center; cursor: pointer" @click="handleBlockUserClick">
+                  <el-icon :size="18">
+                    <CircleClose /> </el-icon
+                  >&nbsp;&nbsp;
+                  <span>拉黑</span>
+                </div>
+              </template>
+            </div>
+          </el-popover>
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- 评论侧边栏 -->
+    <el-drawer v-model="commentDrawerVisible" title="评论" direction="rtl" size="400px" destroy-on-close>
+      <!-- 评论列表 -->
+      <div class="comment-list-container">
+        <div v-if="commentListData.length > 0" class="comment-list">
+          <div v-for="comment in commentListData" :key="comment.id" class="comment-item">
+            <div class="comment-avatar">
+              <img v-if="comment.userAvatar" :src="comment.userAvatar" :alt="comment.userName" />
+              <el-icon v-else :size="32">
+                <Avatar />
+              </el-icon>
+            </div>
+            <div class="comment-content-wrapper">
+              <div class="comment-header">
+                <span class="comment-user-name">{{ comment.userName }}</span>
+              </div>
+              <div class="comment-text">
+                {{ comment.commentContent }}
+              </div>
+
+              <div class="comment-actions">
+                <span class="comment-action-item" @click="handleLikeComment(comment)">
+                  <el-icon :size="16" :color="comment.isLiked ? '#f56c6c' : '#999'">
+                    <Star />
+                  </el-icon>
+                  <span>{{ comment.likeCount || 0 }}</span>
+                </span>
+                <span class="comment-action-item" @click="handleReplyComment(comment)">
+                  <el-icon :size="16">
+                    <ChatDotRound />
+                  </el-icon>
+                  <span>回复</span>
+                </span>
+              </div>
+              <!-- 商家回复 -->
+              <div v-for="item in comment.storeComment" :key="item.id" class="store-comment-wrapper">
+                <div class="store-comment-item">
+                  <div class="store-comment-avatar">
+                    <img v-if="item.userImage" :src="item.userImage" :alt="item.userName" />
+                    <el-icon v-else :size="24">
+                      <Avatar />
+                    </el-icon>
+                  </div>
+                  <div class="store-comment-content">
+                    <div class="store-comment-header">
+                      <span class="store-comment-user-name">{{ item.userName || "商家" }}</span>
+                      <span class="store-comment-time">{{ item.createdTime || item.createDate }}</span>
+                    </div>
+                    <div class="store-comment-text">
+                      {{ item.commentContent }}
+                      <span
+                        class="comment-action-item"
+                        @click="handleLikeComment(item)"
+                        style="display: flex; align-items: center"
+                      >
+                        <el-icon :size="16" :color="item.isLiked ? '#f56c6c' : '#999'">
+                          <Star /> </el-icon
+                        >&nbsp;
+                        <span>{{ item.likeCount || 0 }}</span>
+                      </span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <el-empty v-else description="暂无评论" />
+      </div>
+
+      <!-- 评论输入框 -->
+      <div class="comment-input-wrapper">
+        <!-- 回复提示 -->
+        <div v-if="replyingComment" class="reply-hint">
+          <span class="reply-text">回复 @{{ replyingComment.userName }}</span>
+          <el-icon class="cancel-reply" @click="handleCancelReply">
+            <Close />
+          </el-icon>
+        </div>
+        <el-input
+          v-model="commentInput"
+          type="textarea"
+          :rows="3"
+          :placeholder="replyingComment ? '输入回复内容...' : '你要评论点什么呢~'"
+          maxlength="500"
+          show-word-limit
+        />
+        <el-button type="primary" :loading="commentSubmitting" @click="handleSubmitComment">
+          {{ replyingComment ? "回复" : "发送" }}
+        </el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 举报对话框 -->
+    <el-dialog v-model="reportDialogVisible" title="举报理由" width="500px" destroy-on-close @close="handleCloseReportDialog">
+      <div class="report-dialog-content">
+        <div class="report-tip">请选择最符合的原因,以便于我们进行的处理</div>
+
+        <!-- 举报原因选项 -->
+        <div class="report-reasons">
+          <el-radio-group v-model="reportForm.reason">
+            <el-radio label="用户头像"> 用户头像 </el-radio>
+            <el-radio label="名称/昵称"> 名称/昵称 </el-radio>
+            <el-radio label="违法违规"> 违法违规 </el-radio>
+            <el-radio label="低俗色情、暴力恐怖、政治谣言"> 低俗色情、暴力恐怖、政治谣言 </el-radio>
+            <el-radio label="涉嫌诈骗"> 涉嫌诈骗 </el-radio>
+            <el-radio label="人身攻击"> 人身攻击 </el-radio>
+            <el-radio label="侵犯版权"> 侵犯版权 </el-radio>
+            <el-radio label="恶意骚扰"> 恶意骚扰 </el-radio>
+            <el-radio label="虚假/过度宣传"> 虚假/过度宣传 </el-radio>
+            <el-radio label="诱导点赞分享"> 诱导点赞分享 </el-radio>
+            <el-radio label="传播人身安全"> 传播人身安全 </el-radio>
+            <el-radio label="侵权举报"> 侵权举报 </el-radio>
+            <el-radio label="其他举报"> 其他举报 </el-radio>
+          </el-radio-group>
+        </div>
+
+        <!-- 详细描述(仅"其他举报"时显示) -->
+        <div v-if="reportForm.reason === '其他举报'" class="report-description">
+          <el-input
+            v-model="reportForm.description"
+            type="textarea"
+            :rows="4"
+            placeholder="请描述任何涉及举报内容的其体情况,我们会综合一判断合举政采!(必填)"
+            maxlength="300"
+            show-word-limit
+          />
+        </div>
+
+        <!-- 上传凭证 -->
+        <div class="report-upload">
+          <div class="upload-title">上传凭证</div>
+          <el-upload
+            v-model:file-list="reportForm.fileList"
+            list-type="picture-card"
+            :limit="9"
+            :on-preview="handleReportPreview"
+            :on-remove="handleReportRemove"
+            :before-upload="beforeReportUpload"
+            :http-request="handleReportUpload"
+            accept="image/*"
+            multiple
+          >
+            <el-icon :size="24">
+              <Plus />
+            </el-icon>
+          </el-upload>
+        </div>
+
+        <!-- 同意协议 -->
+        <div class="report-agreement">
+          <el-checkbox v-model="reportForm.agreed"> 同时拉黑该用户 </el-checkbox>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="reportDialogVisible = false"> 取消 </el-button>
+          <el-button type="primary" :loading="reportSubmitting" @click="handleSubmitReport"> 提交 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 分享对话框 -->
+    <el-dialog v-model="shareDialogVisible" title="分享给好友" width="500px" destroy-on-close @close="handleCloseShareDialog">
+      <div class="share-dialog-content">
+        <!-- 好友列表 -->
+        <div class="share-friend-list">
+          <div v-if="filteredShareFriendList.length > 0">
+            <div
+              v-for="friend in filteredShareFriendList"
+              :key="friend.id"
+              class="share-friend-item"
+              @click="handleSelectFriend(friend)"
+            >
+              <div class="friend-info">
+                <div class="friend-avatar">
+                  <img v-if="friend.avatar" :src="friend.avatar" :alt="friend.name" />
+                  <el-icon v-else :size="40">
+                    <Avatar />
+                  </el-icon>
+                </div>
+                <div class="friend-name">
+                  {{ friend.name }}
+                </div>
+              </div>
+              <el-icon v-if="selectedFriends.includes(friend.id)" :size="20" color="#409eff">
+                <CircleCheck />
+              </el-icon>
+            </div>
+          </div>
+          <el-empty v-else description="暂无好友" />
+        </div>
+
+        <!-- 已选择的好友 -->
+        <div v-if="selectedFriends.length > 0" class="selected-friends">
+          <div class="selected-title">已选择 {{ selectedFriends.length }} 位好友</div>
+          <div class="selected-list">
+            <el-tag v-for="friendId in selectedFriends" :key="friendId" closable @close="handleRemoveFriend(friendId)">
+              {{ shareFriendList.find(f => f.id === friendId)?.name }}
+            </el-tag>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="shareDialogVisible = false"> 取消 </el-button>
+          <el-button
+            type="primary"
+            :loading="shareSubmitting"
+            :disabled="selectedFriends.length === 0"
+            @click="handleConfirmShare"
+          >
+            确认分享
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="dynamicManagementIndex">
+import { ref, reactive, computed, onMounted, watch } from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import {
+  Picture,
+  Avatar,
+  Star,
+  StarFilled,
+  Close,
+  ChatDotRound,
+  Share,
+  MoreFilled,
+  Edit,
+  Delete,
+  Warning,
+  CircleClose,
+  Plus,
+  Search,
+  CircleCheck
+} from "@element-plus/icons-vue";
+import {
+  getUserDynamics,
+  likeDynamic,
+  unlikeDynamic,
+  reportUserViolation,
+  blockUser,
+  getUserByPhone,
+  toggleFollowUser,
+  cancelFollowed,
+  likeDynamicNew,
+  unlikeDynamicNew,
+  likeDynamicList,
+  likeComment,
+  unlikeComment
+} from "@/api/modules/dynamicManagement";
+
+import { saveComment, commentList, getMutualAttention, addTransferCount } from "@/api/modules/newLoginApi";
+import { uploadImg } from "@/api/modules/upload";
+import { useUserStore } from "@/stores/modules/user";
+
+const router = useRouter();
+const userStore = useUserStore();
+
+// 举报原因到违规类型的映射
+const violationTypeMap: Record<string, number> = {
+  用户头像: 1,
+  "名称/昵称": 2,
+  违法违规: 3,
+  "低俗色情、暴力恐怖、政治谣言": 4,
+  涉嫌诈骗: 5,
+  人身攻击: 6,
+  侵犯版权: 7,
+  恶意骚扰: 8,
+  "虚假/过度宣传": 9,
+  诱导点赞分享: 10,
+  传播人身安全: 11,
+  侵权举报: 12,
+  其他举报: 13
+};
+
+// 接口定义
+// 媒体项类型
+interface MediaItem {
+  url: string;
+  type: "image" | "video";
+}
+
+interface DynamicItem {
+  isLike: any;
+  dianzanCount: any;
+  createdTime: any;
+  context: string;
+  id: number;
+  title: string;
+  content: string;
+  imageUrl: string;
+  userName: string;
+  userAvatar: string;
+  likeCount: number;
+  isLiked: boolean;
+  createTime: string;
+  userId?: string | number; // 发布者ID
+  phoneId?: string; // 发布者店铺ID
+  storeUserId?: string | number; // 小店用户ID(用于举报)
+  userType?: number; // 发布者用户类型:1商家,2用户
+  phone?: string; // 发布者手机号
+  isFollowed?: number; // 是否已关注:0未关注,1已关注
+  isFollowThis?: number; // 是否已关注:0未关注,1已关注(用于判断关注按钮显示)
+  isVideo?: boolean; // 是否为视频
+  mediaType?: string; // 媒体类型:image 或 video
+  mediaList?: MediaItem[]; // 媒体列表(支持多张图片和视频)
+}
+
+interface DetailItem extends DynamicItem {
+  author?: {
+    id: number;
+    name: string;
+    avatar: string;
+  };
+  context: string;
+  commentCount: number;
+  isFollowThis?: number; // 是否已关注:0未关注,1已关注(用于判断关注按钮显示)
+}
+
+// 响应式数据
+const activeTab = ref("recommend");
+const dynamicList = ref<DynamicItem[]>([]);
+const isfollowed = ref(0); // 0: 推荐, 1: 关注
+
+// 详情 Drawer 相关
+const detailDrawerVisible = ref(false);
+const currentDetail = ref<DetailItem | null>(null);
+
+// 轮播相关
+const currentCarouselIndex = ref(0);
+const videoRefs = ref<Map<number, HTMLVideoElement>>(new Map());
+
+// 设置视频引用
+const setVideoRef = (el: any, index: number) => {
+  if (el) {
+    videoRefs.value.set(index, el as HTMLVideoElement);
+  }
+};
+
+// 轮播切换时暂停所有视频
+const handleCarouselChange = (newIndex: number) => {
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  currentCarouselIndex.value = newIndex;
+};
+
+// 视频播放时暂停其他视频
+const handleVideoPlay = (currentIndex: number) => {
+  videoRefs.value.forEach((video, index) => {
+    if (index !== currentIndex && video && !video.paused) {
+      video.pause();
+    }
+  });
+};
+
+// 举报对话框相关
+const reportDialogVisible = ref(false);
+const reportSubmitting = ref(false);
+const reportForm = reactive({
+  reason: "用户头像", // 默认选择第一个选项
+  description: "",
+  fileList: [] as any[],
+  agreed: false
+});
+
+// 分享对话框相关
+interface ShareFriend {
+  id: number;
+  name: string;
+  avatar: string;
+  phoneId?: string;
+}
+
+const shareDialogVisible = ref(false);
+const shareSubmitting = ref(false);
+const shareSearch = ref("");
+const shareFriendList = ref<ShareFriend[]>([]);
+const selectedFriends = ref<number[]>([]);
+
+// 过滤后的好友列表
+const filteredShareFriendList = computed(() => {
+  if (!shareSearch.value) {
+    return shareFriendList.value;
+  }
+  const keyword = shareSearch.value.toLowerCase();
+  return shareFriendList.value.filter(friend => friend.name.toLowerCase().includes(keyword));
+});
+
+// 分页
+const pagination = reactive({
+  page: 1,
+  pageSize: 10,
+  total: 0
+});
+
+// 直接使用动态列表(后端已完成分页)
+const paginatedList = computed(() => {
+  return dynamicList.value;
+});
+
+// 判断当前详情是否是当前用户的动态
+const isMyDynamic = computed(() => {
+  const currentUserStoreId = userStore.userInfo?.storeId;
+  const dynamicStoreUserId = currentDetail.value?.storeUserId; // ✅ 添加可选链操作符
+
+  // 通过 storeId 和 storeUserId 判断是否是当前用户的动态
+  const result = currentUserStoreId == dynamicStoreUserId;
+  console.log("是否是自己发布的作品:", result);
+
+  return result;
+});
+
+// 标签切换
+const handleTabClick = (tab: any) => {
+  // 根据切换的 tab 更新 isfollowed 的值
+  // 使用传入的 tab.props.name 获取当前点击的 tab,而不是 activeTab.value
+  const tabName = tab?.props?.name || tab?.paneName || activeTab.value;
+
+  if (tabName === "recommend") {
+    isfollowed.value = 0; // 推荐
+  } else if (tabName === "follow") {
+    isfollowed.value = 1; // 关注
+  }
+
+  pagination.page = 1;
+  loadDynamicList();
+};
+
+// 发布动态
+const handlePublish = () => {
+  // 校验是否已入驻店铺
+  if (!userStore.userInfo?.storeId) {
+    ElMessage.warning("请先入驻店铺");
+    return;
+  }
+
+  router.push("/dynamicManagement/publishDynamic");
+};
+
+// 分页大小改变
+const handleSizeChange = (val: number) => {
+  pagination.pageSize = val;
+  pagination.page = 1;
+  loadDynamicList();
+};
+
+// 当前页改变
+const handleCurrentChange = (val: number) => {
+  pagination.page = val;
+  loadDynamicList();
+};
+
+// 根据 phoneId 判断用户类型
+const getUserTypeFromPhoneId = (phoneId: string | undefined): number => {
+  if (!phoneId) return 1; // 默认商家
+
+  const prefix = phoneId.split("_")[0]; // 截取 "_" 之前的文字
+  return prefix === "store" ? 1 : 2; // store = 商家(1), 其他 = 用户(2)
+};
+
+// 加载动态列表
+const loadDynamicList = async () => {
+  try {
+    // 获取店铺ID(从 userStore 中获取,如果没有则使用默认值)
+    const phoneId = userStore.userInfo?.phoneId;
+
+    const res = await getUserDynamics({
+      type: 2, // 固定值,表示动态类型
+      isfollowed: isfollowed.value, // 0 推荐, 1 关注(使用全局变量)
+      myself: 0, // 0 表示他人的动态
+      page: pagination.page,
+      size: pagination.pageSize,
+      phoneId: `store_${userStore.userInfo?.phone}`
+    });
+
+    // 处理返回的数据
+    if (res.data) {
+      // 根据实际返回的数据结构进行映射
+      const responseData = res.data as any;
+      const list = responseData.records;
+      dynamicList.value = list.map((item: any) => {
+        const phoneId = item.phoneId || item.storeId;
+        const userType = getUserTypeFromPhoneId(phoneId); // 根据 phoneId 判断用户类型
+
+        // 从 phoneId 中提取手机号("_" 之后的部分)
+        let phone = item.phone || item.userPhone || item.mobile || "";
+        if (!phone && phoneId && phoneId.includes("_")) {
+          phone = phoneId.split("_")[1]; // 截取 "_" 之后的文字作为手机号
+        }
+
+        // 输出关注状态(仅第一条)
+        if (item.id === list[0].id) {
+          console.log("接口返回的isFollowThis:", item.isFollowThis, "(0=未关注, 1=已关注)");
+        }
+
+        // 解析媒体列表(支持多张图片和视频)
+        const mediaUrl = item.imagePath || "";
+        const mediaUrls = mediaUrl
+          .split(",")
+          .map((url: string) => url.trim())
+          .filter((url: string) => url);
+        const mediaList: MediaItem[] = mediaUrls.map((url: string) => ({
+          url,
+          type: url.toLowerCase().endsWith(".mp4") ? ("video" as const) : ("image" as const)
+        }));
+
+        const firstUrl = mediaUrls[0] || "";
+        const isVideo = firstUrl.toLowerCase().endsWith(".mp4");
+        const mediaType = isVideo ? "video" : "image";
+
+        // 获取用户头像(优先使用 userImage 字段)
+        const userAvatar = item.userImage || item.userAvatar || item.avatar || item.headImg || "";
+
+        return {
+          // 保留接口返回的所有原始字段
+          ...item,
+          // 额外添加的处理字段
+          id: item.id || item.dynamicId,
+          title: item.title || item.content || item.dynamicContent || "这家店超好吃....",
+          content: item.content || item.dynamicContent || "",
+          imageUrl: firstUrl, // 使用第一个URL(兼容旧逻辑)
+          mediaList, // 完整媒体列表
+          userName: item.userName || item.nickname || item.storeName || "用户",
+          userAvatar: userAvatar,
+          likeCount: item.likeCount || item.praiseCount || 0,
+          isLiked: item.isLiked || item.isPraise || false,
+          createTime: item.createTime || item.createDate || new Date().toISOString(),
+          userId: item.userId || item.createUserId,
+          phoneId: phoneId,
+          storeUserId: item.storeUserId || item.userId || item.createUserId, // 小店用户ID
+          userType: userType, // 用户类型:1商家,2用户
+          phone: phone, // 手机号(从 phoneId 或其他字段获取)
+          isFollowed: item.isFollowThis, // 使用isFollowThis字段:0未关注(显示按钮),1已关注(隐藏按钮)
+          isFollowThis: item.isFollowThis, // 是否已关注:0未关注,1已关注(用于判断关注按钮显示)
+          isVideo: isVideo, // 是否为视频
+          mediaType: mediaType // 媒体类型
+        };
+      });
+
+      pagination.total = responseData.total || responseData.totalCount || list.length;
+    }
+  } catch (error) {
+    console.error("加载动态列表失败:", error);
+    ElMessage.error("加载动态列表失败");
+    // 失败时清空列表
+    dynamicList.value = [];
+    pagination.total = 0;
+  }
+};
+
+// 点击卡片 - 查看详情(直接使用列表数据)
+const handleCardClick = async (item: DynamicItem) => {
+  console.log("点击动态:", item);
+  console.log("isFollowThis值:", item.isFollowThis, "(0=未关注显示按钮, 1=已关注隐藏按钮)");
+  console.log("isMyDynamic:", isMyDynamic.value);
+  // 直接使用列表数据构建详情
+  currentDetail.value = {
+    ...item,
+    author: {
+      id: 0,
+      name: item.userName,
+      avatar: item.userAvatar
+    },
+    context: item.context,
+    createdTime: item.createdTime,
+    dianzanCount: item.dianzanCount,
+    isLike: item.isLike,
+
+    commentCount: 0,
+    isFollowThis: item.isFollowThis // 使用item.isFollowThis而不是item.isFollowed
+  };
+  console.log(currentDetail.value);
+  detailDrawerVisible.value = true;
+
+  // 打开抽屉时加载评论列表
+  await loadCommentList();
+};
+
+// 列表点赞/取消点赞
+const handleLike = async (item: DynamicItem) => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const params = {
+      userId: currentUserPhoneId, // 当前用户phoneId
+      huifuId: item.id, // 动态ID
+      type: 2 // 2表示点赞
+    };
+
+    // 根据当前点赞状态调用不同的接口
+    if (item.isLiked) {
+      // 已点赞,调用取消点赞接口
+      await unlikeDynamicNew(params);
+    } else {
+      // 未点赞,调用点赞接口
+      await likeDynamicNew(params);
+    }
+    await loadDynamicList();
+
+    ElMessage.success(item.isLiked ? "点赞成功" : "取消点赞");
+  } catch (error) {
+    console.error("列表点赞操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 关闭详情
+const handleCloseDetail = () => {
+  detailDrawerVisible.value = false;
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  setTimeout(() => {
+    currentDetail.value = null;
+    currentCarouselIndex.value = 0;
+    videoRefs.value.clear();
+  }, 300);
+};
+
+// 详情页点赞(表单方式提交)
+const handleDetailLike = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const params = {
+      userId: currentUserPhoneId, // 当前用户phoneId
+      huifuId: currentDetail.value.id, // 动态ID
+      type: 2 // 2表示点赞
+    };
+
+    // 根据当前点赞状态调用不同的接口
+    if (currentDetail.value.isLiked) {
+      // 已点赞,调用取消点赞接口
+
+      await unlikeDynamicNew(params);
+    } else {
+      // 未点赞,调用点赞接口
+      await likeDynamicNew(params);
+    }
+
+    // 切换点赞状态
+    currentDetail.value.isLiked = !currentDetail.value.isLiked;
+    currentDetail.value.likeCount += currentDetail.value.isLiked ? 1 : -1;
+
+    // 同步更新列表中的数据
+    const listItem = dynamicList.value.find(item => item.id === currentDetail.value?.id);
+    if (listItem) {
+      listItem.isLiked = currentDetail.value.isLiked;
+      listItem.likeCount = currentDetail.value.likeCount;
+    }
+
+    ElMessage.success(currentDetail.value.isLiked ? "点赞成功" : "取消点赞");
+  } catch (error) {
+    console.error("点赞操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 显示评论
+// 评论相关
+const commentDrawerVisible = ref(false);
+const commentListData = ref<any[]>([]);
+const commentInput = ref("");
+const commentSubmitting = ref(false);
+const currentCommentDynamicId = ref<number | string>("");
+const replyingComment = ref<any>(null); // 当前正在回复的评论
+
+const handleShowComments = () => {
+  if (!currentDetail.value) return;
+  commentDrawerVisible.value = true;
+  currentCommentDynamicId.value = currentDetail.value.id;
+};
+
+// 加载评论列表
+const loadCommentList = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    let params = {
+      businessId: String(currentDetail.value.id),
+      businessType: "2",
+      replyStatus: 0,
+      pageNum: 1,
+      pageSize: 10,
+      commentType: 0,
+      days: "",
+      phoneId: `store_${userStore.userInfo?.phone}`
+    };
+    const res: any = await commentList(params);
+    if (res.code === 200) {
+      commentListData.value = res.data.records || [];
+      // 更新评论总数
+      if (currentDetail.value) {
+        currentDetail.value.commentCount = res.data.total || 0;
+      }
+      console.log("评论列表:", commentListData.value);
+      console.log("评论总数:", res.data.total);
+    }
+  } catch (error) {
+    console.error("加载评论列表失败:", error);
+  }
+};
+
+// 提交评论
+const handleSubmitComment = async () => {
+  if (!commentInput.value.trim()) {
+    ElMessage.warning("请输入评论内容");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.error("动态信息不存在");
+    return;
+  }
+
+  try {
+    commentSubmitting.value = true;
+
+    // 判断是回复评论还是评论动态
+    const isReply = !!replyingComment.value;
+
+    const params: any = {
+      replyId: isReply ? replyingComment.value.id : "", // 回复评论时传评论ID,否则为空
+      commentContent: commentInput.value,
+      businessType: "2",
+      businessId: String(currentDetail.value.id),
+      storeId: userStore.userInfo?.storeId || userStore.userInfo?.createdId,
+      commentStar: "",
+      phoneId: `store_${userStore.userInfo?.phone}`
+    };
+
+    const res: any = await saveComment(params);
+    if (res.code === 200) {
+      ElMessage.success(isReply ? "回复成功" : "评论成功");
+      commentInput.value = "";
+      replyingComment.value = null; // 清空回复状态
+      await loadCommentList();
+    } else {
+      ElMessage.error(res.message || (isReply ? "回复失败" : "评论失败"));
+    }
+  } catch (error) {
+    console.error("提交评论失败:", error);
+    ElMessage.error(replyingComment.value ? "回复失败" : "评论失败");
+  } finally {
+    commentSubmitting.value = false;
+  }
+};
+
+// 点赞评论
+const handleLikeComment = async (comment: any) => {
+  console.log(comment);
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const params = {
+      userId: currentUserPhoneId, // 当前用户phoneId
+      huifuId: comment.id, // 动态ID
+      type: 1 // 2表示点赞
+    };
+
+    // 根据当前点赞状态调用不同的接口
+    if (comment.isLiked) {
+      // 已点赞,调用取消点赞接口
+      await unlikeDynamicNew(params);
+    } else {
+      // 未点赞,调用点赞接口
+      await likeDynamicNew(params);
+    }
+
+    // 切换点赞状态
+    comment.isLiked = !comment.isLiked;
+    comment.likeCount += comment.isLiked ? 1 : -1;
+
+    ElMessage.success(comment.isLiked ? "点赞成功" : "取消点赞");
+  } catch (error) {
+    console.error("列表点赞操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 回复评论
+const handleReplyComment = (comment: any) => {
+  replyingComment.value = comment;
+  commentInput.value = ``;
+  // 聚焦到输入框
+  setTimeout(() => {
+    const textarea = document.querySelector(".comment-input-wrapper textarea") as HTMLTextAreaElement;
+    if (textarea) {
+      textarea.focus();
+    }
+  }, 100);
+};
+
+// 取消回复
+const handleCancelReply = () => {
+  replyingComment.value = null;
+  commentInput.value = "";
+};
+
+// 分享
+const handleShare = async () => {
+  shareDialogVisible.value = true;
+  await loadShareFriendList();
+};
+
+// 加载好友列表
+const loadShareFriendList = async () => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const res: any = await getMutualAttention({
+      page: 1,
+      size: 1000,
+      fansId: fansId,
+      name: ""
+    });
+
+    if (res.code === 200) {
+      const dataList = res.data?.records || res.data?.list || res.data || [];
+      shareFriendList.value = dataList.map((item: any) => ({
+        id: item.id || item.userId,
+        name: item.userName || item.nickname || item.name || "用户",
+        avatar: item.userImage || item.avatar || item.headImg || "",
+        phoneId: item.phoneId || item.fansId || ""
+      }));
+      console.log("加载好友列表成功:", shareFriendList.value);
+    }
+  } catch (error) {
+    console.error("加载好友列表失败:", error);
+    ElMessage.error("加载好友列表失败");
+    shareFriendList.value = [];
+  }
+};
+
+// 搜索好友
+const handleShareSearch = () => {
+  // 搜索由计算属性自动处理
+};
+
+// 选择好友
+const handleSelectFriend = (friend: ShareFriend) => {
+  const index = selectedFriends.value.indexOf(friend.id);
+  if (index > -1) {
+    // 已选择,取消选择
+    selectedFriends.value.splice(index, 1);
+  } else {
+    // 未选择,添加选择
+    selectedFriends.value.push(friend.id);
+  }
+};
+
+// 移除已选择的好友
+const handleRemoveFriend = (friendId: number) => {
+  const index = selectedFriends.value.indexOf(friendId);
+  if (index > -1) {
+    selectedFriends.value.splice(index, 1);
+  }
+};
+
+// 确认分享
+const handleConfirmShare = async () => {
+  if (selectedFriends.value.length === 0) {
+    ElMessage.warning("请选择要分享的好友");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.error("动态信息不存在");
+    return;
+  }
+
+  try {
+    shareSubmitting.value = true;
+
+    // 调用 addTransferCount 接口,传递动态 id
+    const res: any = await addTransferCount({
+      id: currentDetail.value.id
+    });
+
+    if (res.code === 200) {
+      ElMessage.success(`已分享给 ${selectedFriends.value.length} 位好友`);
+      shareDialogVisible.value = false;
+
+      // 可以在这里更新动态的分享数(如果需要的话)
+      console.log("分享成功,动态ID:", currentDetail.value.id);
+    } else {
+      ElMessage.error(res.message || "分享失败");
+    }
+  } catch (error) {
+    console.error("分享失败:", error);
+    ElMessage.error("分享失败");
+  } finally {
+    shareSubmitting.value = false;
+  }
+};
+
+// 关闭分享对话框
+const handleCloseShareDialog = () => {
+  shareSearch.value = "";
+  selectedFriends.value = [];
+  shareFriendList.value = [];
+};
+
+// 查看用户主页
+const handleViewUserProfile = () => {
+  if (!currentDetail.value) return;
+
+  // 跳转到他人动态主页,传递用户信息
+  router.push({
+    path: "/dynamicManagement/userDynamic",
+    query: {
+      userId: currentDetail.value.storeUserId || currentDetail.value.userId || "",
+      phoneId: currentDetail.value.phoneId || "",
+      userName: currentDetail.value.userName || "",
+      userAvatar: currentDetail.value.userAvatar || "",
+      phone: currentDetail.value.phone || ""
+    }
+  });
+};
+
+// 详情页关注(右侧操作栏)
+const handleFollowInDetail = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    await toggleFollowUser({
+      followedId: currentDetail.value.phoneId || "",
+      fansId: currentUserPhoneId,
+      fansType: 2
+    });
+
+    // 更新关注状态
+    if (currentDetail.value) {
+      currentDetail.value.isFollowed = 1;
+      currentDetail.value.isFollowThis = 1; // 同时更新isFollowThis字段
+    }
+
+    // 同步更新列表中的状态
+    const listItem = dynamicList.value.find(item => item.id === currentDetail.value?.id);
+    if (listItem) {
+      listItem.isFollowed = 1;
+      listItem.isFollowThis = 1; // 同时更新列表项的isFollowThis状态
+    }
+
+    ElMessage.success("关注成功");
+  } catch (error) {
+    console.error("关注操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 编辑动态
+const handleEditDynamic = () => {
+  if (!currentDetail.value) return;
+  detailDrawerVisible.value = false;
+  router.push({
+    path: "/dynamicManagement/publishDynamic",
+    query: { id: currentDetail.value.id }
+  });
+};
+
+// 删除动态
+const handleDeleteDynamic = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    await ElMessageBox.confirm("确定要删除这条动态吗?删除后将无法恢复。", "删除确认", {
+      confirmButtonText: "确定删除",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+
+    // TODO: 集成真实接口
+    // await deleteDynamic({ id: currentDetail.value.id });
+
+    ElMessage.success("删除成功");
+    detailDrawerVisible.value = false;
+
+    // 从列表中移除
+    const index = dynamicList.value.findIndex(item => item.id === currentDetail.value?.id);
+    if (index > -1) {
+      dynamicList.value.splice(index, 1);
+      pagination.total--;
+    }
+  } catch {
+    // 用户取消删除
+  }
+};
+
+// 举报动态
+const handleReportDynamic = () => {
+  reportDialogVisible.value = true;
+};
+
+// 举报图片预览
+const handleReportPreview = (uploadFile: any) => {
+  console.log("预览图片", uploadFile);
+};
+
+// 移除举报图片
+const handleReportRemove = (uploadFile: any, uploadFiles: any[]) => {
+  // 已由 v-model:file-list 自动处理
+};
+
+// 举报图片上传前验证
+const beforeReportUpload = (file: File) => {
+  const isImage = file.type.startsWith("image/");
+  const isLt5M = file.size / 1024 / 1024 < 5;
+
+  if (!isImage) {
+    ElMessage.error("只能上传图片文件!");
+    return false;
+  }
+  if (!isLt5M) {
+    ElMessage.error("图片大小不能超过 5MB!");
+    return false;
+  }
+  return true;
+};
+
+// 自定义举报图片上传
+const handleReportUpload = async (options: any) => {
+  const { file, onSuccess, onError } = options;
+
+  try {
+    const uploadFormData = new FormData();
+    uploadFormData.append("file", file);
+
+    const response: any = await uploadImg(uploadFormData);
+
+    // 处理返回格式:{ code, success, data: string[], msg }
+    if (response && response.code === 200 && response.data && Array.isArray(response.data) && response.data.length > 0) {
+      // 上传成功,返回图片URL(取数组第一个元素)
+      onSuccess({
+        url: response.data[0]
+      });
+    } else {
+      ElMessage.error(response?.msg || "图片上传失败");
+      onError(new Error(response?.msg || "图片上传失败"));
+    }
+  } catch (error) {
+    console.error("图片上传失败:", error);
+    ElMessage.error("图片上传失败");
+    onError(error);
+  }
+};
+
+// 提交举报
+const handleSubmitReport = async () => {
+  // 验证表单
+  if (!reportForm.reason) {
+    ElMessage.warning("请选择举报原因");
+    return;
+  }
+
+  // 只有选择"其他举报"时才验证详细描述
+  if (reportForm.reason === "其他举报" && !reportForm.description.trim()) {
+    ElMessage.warning("请填写详细描述");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.warning("动态信息异常");
+    return;
+  }
+
+  reportSubmitting.value = true;
+
+  try {
+    // 获取违规类型编号
+    const violationType = violationTypeMap[reportForm.reason];
+
+    // 获取举报凭证图片(如果有多张,用逗号分隔)
+    const reportEvidenceImg = reportForm.fileList
+      .map((f: any) => f.url || f.response?.url)
+      .filter(Boolean)
+      .join(",");
+
+    // 获取当前用户信息
+    const currentUserId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
+    const currentUserType = userStore.userInfo?.userType || userStore.userInfo?.type || 1; // 用户类型:1商家,2用户,默认1
+
+    // 获取被举报用户类型(从 phoneId 解析)
+    const reportedUserType = currentDetail.value.userType || 1;
+
+    // 根据手机号获取被举报人ID
+    let reportedUserId = currentDetail.value.storeUserId || currentDetail.value.userId || "";
+    if (currentDetail.value.phone) {
+      try {
+        const userRes = await getUserByPhone({ phone: currentDetail.value.phone });
+        const userData = userRes.data as any;
+        if (userData && userData.id) {
+          reportedUserId = userData.id;
+          console.log("通过手机号获取到被举报人ID:", reportedUserId);
+        }
+      } catch (error) {
+        console.error("获取被举报人ID失败:", error);
+        // 如果获取失败,使用原有的 storeUserId
+      }
+    }
+
+    console.log("当前用户类型:", userStore.userInfo);
+    console.log("被举报用户类型:", reportedUserType);
+    console.log("被举报人ID:", reportedUserId);
+    console.log("动态详情:", currentDetail.value);
+
+    // 调用举报接口
+    await reportUserViolation({
+      dynamicsId: currentDetail.value.id, // 动态ID
+      reportContextType: "2", // 举报上下文类型:2表示动态
+      violationType: violationType, // 违规类型
+      otherReasonContent: reportForm.reason === "其他举报" ? reportForm.description : "", // 只有选择"其他举报"时才传详细描述
+      reportEvidenceImg: reportEvidenceImg, // 举报凭证图片
+      reportedUserId: reportedUserId, // 被举报用户ID(通过手机号获取)
+      reportedUserType: reportedUserType, // 被举报用户类型(从 phoneId 解析)
+      reportingUserId: currentUserId, // 举报人ID
+      reportingUserType: currentUserType // 举报人类型(当前登录用户类型)
+    });
+
+    // 如果同时拉黑该用户
+    if (reportForm.agreed) {
+      try {
+        // 调用拉黑接口(跳过确认对话框)
+        await handleBlockUser(true);
+        ElMessage.success("举报提交成功,已拉黑该用户");
+      } catch (blockError) {
+        console.error("拉黑失败:", blockError);
+        ElMessage.warning("举报提交成功,但拉黑失败");
+      }
+    } else {
+      ElMessage.success("举报提交成功,我们会尽快处理");
+    }
+
+    reportDialogVisible.value = false;
+  } catch (error) {
+    console.error("举报提交失败:", error);
+    ElMessage.error("举报提交失败");
+  } finally {
+    reportSubmitting.value = false;
+  }
+};
+
+// 关闭举报对话框
+const handleCloseReportDialog = () => {
+  reportForm.reason = "用户头像"; // 重置为默认选项
+  reportForm.description = "";
+  reportForm.fileList = [];
+  reportForm.agreed = false;
+};
+
+// 监听举报原因变化,如果不是"其他举报"则清空详细描述
+watch(
+  () => reportForm.reason,
+  newReason => {
+    if (newReason !== "其他举报") {
+      reportForm.description = "";
+    }
+  }
+);
+
+// 拉黑用户(点击菜单项)
+const handleBlockUserClick = () => {
+  handleBlockUser(false);
+};
+
+// 拉黑用户
+const handleBlockUser = async (skipConfirm: boolean = false) => {
+  if (!currentDetail.value) return;
+
+  try {
+    // 如果不跳过确认,显示确认对话框
+    if (!skipConfirm) {
+      await ElMessageBox.confirm("拉黑后将不再看到该用户的动态,确定要拉黑吗?", "拉黑确认", {
+        confirmButtonText: "确定拉黑",
+        cancelButtonText: "取消",
+        type: "warning"
+      });
+    }
+
+    // 获取当前用户信息
+    const currentUserId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
+    const currentUserType = userStore.userInfo?.userType || userStore.userInfo?.type || 1; // 用户类型:1商家,2用户,默认1
+
+    // 获取被拉黑用户类型(从 phoneId 解析)
+    const blockedUserType = currentDetail.value.userType || 1;
+
+    console.log("当前用户信息:", userStore.userInfo);
+    console.log("当前用户类型:", currentUserType);
+    console.log("被拉黑用户类型:", blockedUserType);
+    console.log("动态详情:", currentDetail.value);
+
+    // 调用拉黑接口
+    await blockUser({
+      blockerType: currentUserType, // 拉黑者类型(当前登录用户类型)
+      blockedType: blockedUserType, // 被拉黑者类型(从 phoneId 解析)
+      blockerId: currentUserId, // 拉黑者ID(当前登录用户)
+      blockedId: currentDetail.value.storeUserId || currentDetail.value.userId || "" // 被拉黑者ID
+    });
+
+    if (!skipConfirm) {
+      ElMessage.success("已拉黑该用户");
+    }
+
+    detailDrawerVisible.value = false;
+
+    // 从列表中移除该用户的动态
+    const index = dynamicList.value.findIndex(item => item.id === currentDetail.value?.id);
+    if (index > -1) {
+      dynamicList.value.splice(index, 1);
+      pagination.total--;
+    }
+  } catch (error) {
+    if (!skipConfirm) {
+      // 单独拉黑时,用户取消操作或接口失败
+      console.error("拉黑失败:", error);
+      if (error !== "cancel") {
+        ElMessage.error("拉黑失败");
+      }
+    } else {
+      // 举报后自动拉黑失败,抛出错误
+      throw error;
+    }
+  }
+};
+
+// 初始化
+onMounted(() => {
+  loadDynamicList();
+});
+</script>
+
+<style scoped lang="scss">
+.dynamic-management-container {
+  min-height: calc(100vh - 120px);
+  padding: 20px;
+  background: #ffffff;
+
+  // 头部区域
+  .header-section {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 20px;
+    border-bottom: 1px solid #e4e7ed;
+    :deep(.el-tabs) {
+      flex: 1;
+      margin-bottom: -1px;
+      .el-tabs__header {
+        margin-bottom: 0;
+      }
+      .el-tabs__nav-wrap::after {
+        display: none;
+      }
+    }
+    .action-buttons {
+      padding-bottom: 10px;
+      margin-left: 20px;
+    }
+  }
+
+  // 内容区域
+  .content-section {
+    margin-top: 20px;
+  }
+
+  // 动态网格布局
+  .dynamic-grid {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 20px;
+    margin-bottom: 20px;
+
+    @media (width <= 1400px) {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    @media (width <= 768px) {
+      grid-template-columns: repeat(1, 1fr);
+    }
+  }
+
+  // 动态卡片
+  .dynamic-card {
+    overflow: hidden;
+    cursor: pointer;
+    background: #ffffff;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    transition: all 0.3s;
+    &:hover {
+      box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+      transform: translateY(-2px);
+    }
+
+    // 图片区域
+    .dynamic-image-wrapper {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 220px;
+      overflow: hidden;
+      background: #f5f7fa;
+      .dynamic-image {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      .image-placeholder {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+        background: #f5f7fa;
+      }
+    }
+
+    // 动态内容
+    .dynamic-content {
+      padding: 12px 16px;
+      .dynamic-text {
+        margin-bottom: 12px;
+        overflow: hidden;
+        font-size: 14px;
+        color: #333333;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      // 底部信息
+      .dynamic-footer {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .user-info {
+          display: flex;
+          gap: 8px;
+          align-items: center;
+          .user-avatar {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 24px;
+            height: 24px;
+            overflow: hidden;
+            background: #e4e7ed;
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          .user-name {
+            font-size: 13px;
+            color: #666666;
+          }
+        }
+        .like-section {
+          display: flex;
+          gap: 4px;
+          align-items: center;
+          .like-icon {
+            cursor: pointer;
+            transition: color 0.3s;
+            &:hover {
+              color: #f56c6c;
+            }
+          }
+          .like-count {
+            font-size: 13px;
+            color: #666666;
+          }
+        }
+      }
+    }
+  }
+
+  // 空状态
+  .empty-section {
+    padding: 80px 0;
+    text-align: center;
+  }
+
+  // 分页
+  .pagination-section {
+    display: flex;
+    justify-content: center;
+    padding: 20px 0;
+    margin-top: 30px;
+  }
+
+  // 详情 Drawer
+  :deep(.detail-drawer) {
+    .el-drawer__header {
+      position: absolute;
+      top: 0;
+      right: 0;
+      left: 0;
+      z-index: 10;
+      padding: 0;
+      margin: 0;
+      background: transparent;
+    }
+    .el-drawer__body {
+      padding: 0;
+      background: #000000;
+    }
+    .drawer-header {
+      padding: 20px;
+      .close-btn {
+        padding: 8px;
+        font-size: 24px;
+        color: #ffffff;
+        background: #ffffff;
+      }
+    }
+    .detail-content {
+      position: relative;
+      display: flex;
+      width: 100%;
+      height: 100%;
+
+      // 主内容区域
+      .detail-main {
+        display: flex;
+        flex: 1;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 80px 120px 40px 40px;
+        .media-container {
+          position: relative;
+          display: flex;
+          flex: 1;
+          align-items: center;
+          justify-content: center;
+          width: 100%;
+          max-width: 800px;
+          min-height: 400px;
+          .media-carousel {
+            width: 100%;
+            height: 100%;
+            min-height: 400px;
+            max-height: 70vh;
+            :deep(.el-carousel__container) {
+              height: 100% !important;
+              min-height: 400px;
+            }
+            :deep(.el-carousel__item) {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              background: transparent;
+            }
+            :deep(.el-carousel__arrow) {
+              width: 48px;
+              height: 48px;
+              font-size: 24px;
+              color: #ffffff;
+              background-color: rgb(0 0 0 / 40%);
+              border: none;
+              &:hover {
+                background-color: rgb(0 0 0 / 60%);
+              }
+            }
+            :deep(.el-carousel__indicators) {
+              bottom: -30px;
+              .el-carousel__indicator {
+                .el-carousel__button {
+                  width: 8px;
+                  height: 8px;
+                  background-color: rgb(255 255 255 / 40%);
+                  border-radius: 50%;
+                }
+                &.is-active .el-carousel__button {
+                  background-color: #409eff;
+                }
+              }
+            }
+          }
+          .detail-media {
+            max-width: 100%;
+            max-height: 65vh;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+          .detail-image {
+            max-width: 100%;
+            max-height: 65vh;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+          .detail-video {
+            width: 100%;
+            max-height: 65vh;
+            background: #000000;
+            border-radius: 8px;
+          }
+          .media-placeholder {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 400px;
+            height: 400px;
+            background: rgb(255 255 255 / 5%);
+            border-radius: 8px;
+          }
+          .media-counter {
+            position: absolute;
+            right: 16px;
+            bottom: -24px;
+            padding: 4px 12px;
+            font-size: 14px;
+            color: #ffffff;
+            background: rgb(0 0 0 / 50%);
+            border-radius: 12px;
+          }
+        }
+        .detail-info {
+          width: 100%;
+          max-width: 800px;
+          padding: 20px 0;
+          .author-info {
+            display: flex;
+            gap: 12px;
+            align-items: center;
+            margin-bottom: 16px;
+            .author-avatar {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              width: 40px;
+              height: 40px;
+              overflow: hidden;
+              background: rgb(255 255 255 / 10%);
+              border-radius: 50%;
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .author-details {
+              flex: 1;
+              .author-name {
+                margin-bottom: 4px;
+                font-size: 16px;
+                font-weight: 500;
+                color: #ffffff;
+              }
+              .publish-time {
+                font-size: 13px;
+                color: rgb(255 255 255 / 60%);
+              }
+            }
+          }
+          .detail-description {
+            font-size: 15px;
+            line-height: 1.6;
+            color: #ffffff;
+            p {
+              margin: 0;
+            }
+          }
+        }
+      }
+
+      // 右侧操作栏
+      .action-bar {
+        position: fixed;
+        right: 40px;
+        bottom: 100px;
+        z-index: 10;
+        display: flex;
+        flex-direction: column;
+        gap: 24px;
+        .action-item {
+          display: flex;
+          flex-direction: column;
+          gap: 6px;
+          align-items: center;
+          cursor: pointer;
+          transition: transform 0.3s;
+          &:hover {
+            transform: scale(1.1);
+          }
+          &.author-action {
+            cursor: pointer;
+            &:hover {
+              transform: scale(1.1);
+            }
+          }
+          .action-avatar {
+            position: relative;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 48px;
+            height: 48px;
+            overflow: visible;
+            cursor: pointer;
+            background: rgb(255 255 255 / 20%);
+            border: 2px solid #ffffff;
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+              border-radius: 50%;
+            }
+            .follow-badge {
+              position: absolute;
+              right: -4px;
+              bottom: -4px;
+              z-index: 2;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              width: 24px;
+              height: 24px;
+              cursor: pointer;
+              background: #409eff;
+              border: 2px solid #ffffff;
+              border-radius: 50%;
+              transition: transform 0.2s;
+              &:hover {
+                transform: scale(1.15);
+              }
+            }
+          }
+          .action-icon {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 48px;
+            height: 48px;
+            background: rgb(0 0 0 / 50%);
+            backdrop-filter: blur(10px);
+            border-radius: 50%;
+            &.follow-icon {
+              background: #409eff;
+            }
+          }
+          .action-count {
+            font-size: 13px;
+            color: #ffffff;
+            text-align: center;
+            text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
+          }
+        }
+      }
+    }
+  }
+
+  // 更多操作 Popover
+  :deep(.more-actions-popover) {
+    min-width: 120px;
+    padding: 8px 0;
+    background: rgb(0 0 0 / 90%);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgb(255 255 255 / 10%);
+    .el-popper__arrow::before {
+      background: rgb(0 0 0 / 90%);
+      border: 1px solid rgb(255 255 255 / 10%);
+    }
+    .more-actions-menu {
+      .menu-item {
+        display: flex;
+        gap: 10px;
+        align-items: center;
+        padding: 10px 16px;
+        font-size: 14px;
+        line-height: 20px;
+        color: #ffffff;
+        cursor: pointer;
+        transition: all 0.3s;
+        &:hover {
+          background: rgb(255 255 255 / 10%);
+        }
+        .el-icon {
+          display: inline-flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 18px;
+          height: 18px;
+          color: #ffffff;
+          vertical-align: middle;
+        }
+        span {
+          display: inline-block;
+          line-height: 20px;
+          vertical-align: middle;
+        }
+      }
+    }
+  }
+
+  // 举报对话框
+  :deep(.el-dialog) {
+    .report-dialog-content {
+      .report-tip {
+        margin-bottom: 20px;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #606266;
+      }
+      .report-reasons {
+        margin-bottom: 20px;
+        .el-radio-group {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 12px 16px;
+          .el-radio {
+            height: auto;
+            margin-right: 0;
+            white-space: nowrap;
+            .el-radio__label {
+              font-size: 14px;
+              color: #303133;
+            }
+          }
+        }
+      }
+      .report-description {
+        margin-bottom: 20px;
+        :deep(.el-textarea__inner) {
+          font-size: 14px;
+        }
+      }
+      .report-upload {
+        margin-bottom: 20px;
+        .upload-title {
+          margin-bottom: 12px;
+          font-size: 14px;
+          font-weight: 500;
+          color: #303133;
+        }
+        :deep(.el-upload-list) {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+        }
+        :deep(.el-upload--picture-card) {
+          width: 100px;
+          height: 100px;
+          border-radius: 4px;
+        }
+        :deep(.el-upload-list--picture-card .el-upload-list__item) {
+          width: 100px;
+          height: 100px;
+          margin: 0;
+          border-radius: 4px;
+        }
+      }
+      .report-agreement {
+        .el-checkbox {
+          .el-checkbox__label {
+            font-size: 14px;
+            color: #606266;
+          }
+        }
+      }
+    }
+    .dialog-footer {
+      display: flex;
+      gap: 12px;
+      justify-content: flex-end;
+    }
+  }
+
+  // 评论侧边栏样式
+  .comment-list-container {
+    flex: 1;
+    height: calc(100vh - 200px);
+    padding: 0 20px;
+    overflow-y: auto;
+    .comment-list {
+      .comment-item {
+        display: flex;
+        gap: 12px;
+        padding: 16px 0;
+        border-bottom: 1px solid #f0f0f0;
+        &:last-child {
+          border-bottom: none;
+        }
+        .comment-avatar {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 40px;
+          height: 40px;
+          overflow: hidden;
+          background: #f5f5f5;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+        .comment-content-wrapper {
+          flex: 1;
+          min-width: 0;
+          .comment-header,
+          .store-comment-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 8px;
+            .comment-user-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: #303133;
+            }
+            .comment-time {
+              font-size: 12px;
+              color: #909399;
+            }
+          }
+          .comment-text {
+            margin-bottom: 8px;
+            font-size: 14px;
+            line-height: 1.6;
+            color: #606266;
+            word-break: break-all;
+          }
+
+          // 商家回复样式
+          .store-comment-wrapper {
+            padding: 12px;
+            margin-top: 12px;
+            background: #f5f7fa;
+            border-radius: 8px;
+            .store-comment-item {
+              display: flex;
+              gap: 10px;
+              .store-comment-avatar {
+                display: flex;
+                flex-shrink: 0;
+                align-items: center;
+                justify-content: center;
+                width: 32px;
+                height: 32px;
+                overflow: hidden;
+                background: #ffffff;
+                border-radius: 50%;
+                img {
+                  width: 100%;
+                  height: 100%;
+                  object-fit: cover;
+                }
+              }
+              .store-comment-content {
+                flex: 1;
+                min-width: 0;
+                .store-comment-header {
+                  display: flex;
+                  align-items: center;
+                  justify-content: space-between;
+                  margin-bottom: 6px;
+                  .store-comment-user-name {
+                    font-size: 13px;
+                    font-weight: 500;
+                    color: #409eff;
+                  }
+                  .store-comment-time {
+                    font-size: 11px;
+                    color: #909399;
+                  }
+                }
+                .store-comment-text {
+                  display: flex;
+                  align-items: center;
+                  justify-content: space-between;
+                  font-size: 13px;
+                  line-height: 1.5;
+                  color: #606266;
+                  word-break: break-all;
+                }
+              }
+            }
+          }
+          .comment-actions {
+            display: flex;
+            gap: 20px;
+            .comment-action-item {
+              display: flex;
+              gap: 4px;
+              align-items: center;
+              font-size: 13px;
+              color: #909399;
+              cursor: pointer;
+              transition: color 0.3s;
+              &:hover {
+                color: #409eff;
+              }
+              span {
+                font-size: 13px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  .comment-input-wrapper {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    padding: 16px 20px;
+    background: #ffffff;
+    border-top: 1px solid #e4e7ed;
+    box-shadow: 0 -2px 8px rgb(0 0 0 / 5%);
+    .reply-hint {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 8px 12px;
+      margin-bottom: 8px;
+      background: #f5f7fa;
+      border-radius: 4px;
+      .reply-text {
+        font-size: 13px;
+        color: #409eff;
+      }
+      .cancel-reply {
+        font-size: 16px;
+        color: #909399;
+        cursor: pointer;
+        transition: color 0.3s;
+        &:hover {
+          color: #f56c6c;
+        }
+      }
+    }
+    :deep(.el-textarea) {
+      margin-bottom: 12px;
+    }
+    .el-button {
+      width: 100%;
+    }
+  }
+
+  // 分享对话框
+  .share-dialog-content {
+    .search-box {
+      margin-bottom: 16px;
+    }
+    .share-friend-list {
+      max-height: 400px;
+      margin-bottom: 16px;
+      overflow-y: auto;
+      .share-friend-item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12px;
+        cursor: pointer;
+        border-radius: 8px;
+        transition: background-color 0.3s;
+        &:hover {
+          background-color: #f5f7fa;
+        }
+        .friend-info {
+          display: flex;
+          gap: 12px;
+          align-items: center;
+          .friend-avatar {
+            display: flex;
+            flex-shrink: 0;
+            align-items: center;
+            justify-content: center;
+            width: 40px;
+            height: 40px;
+            overflow: hidden;
+            background: #f5f5f5;
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          .friend-name {
+            font-size: 14px;
+            font-weight: 500;
+            color: #303133;
+          }
+        }
+      }
+    }
+    .selected-friends {
+      padding: 12px;
+      background: #f5f7fa;
+      border-radius: 8px;
+      .selected-title {
+        margin-bottom: 8px;
+        font-size: 13px;
+        color: #606266;
+      }
+      .selected-list {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        .el-tag {
+          max-width: 120px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+}
+</style>

+ 2822 - 0
src/views/dynamicManagement/myDynamic.vue

@@ -0,0 +1,2822 @@
+<template>
+  <div class="my-dynamic-container">
+    <!-- 用户信息卡片 -->
+    <div class="user-card">
+      <div class="user-header">
+        <div class="user-avatar-section">
+          <div class="user-avatar-large">
+            <img v-if="userInfo.avatar" :src="userInfo.avatar" :alt="userInfo.name" />
+            <el-icon v-else :size="60">
+              <Avatar />
+            </el-icon>
+          </div>
+          <div class="user-info-text">
+            <div class="user-name">
+              {{ userStore.userInfo.nickName }}
+            </div>
+            <div class="user-stats">
+              <span class="stat-item" @click="openRelationDialog('friend')">{{ userInfo.followCount }} 好友</span>
+              <span class="stat-divider">|</span>
+              <span class="stat-item" @click="openRelationDialog('follow')">{{ userInfo.fansCount }} 关注</span>
+              <span class="stat-divider">|</span>
+              <span class="stat-item" @click="openRelationDialog('fans')">{{ userInfo.likeCount }} 粉丝</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="user-bio">
+        {{ userStore.userInfo.accountBlurb }}
+      </div>
+    </div>
+
+    <!-- 标签页 -->
+    <div class="tabs-section">
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="动态" name="dynamic" />
+        <el-tab-pane label="赞过" name="liked" />
+      </el-tabs>
+    </div>
+
+    <!-- 内容区域 -->
+    <div v-if="contentList.length > 0 && activeTab === 'dynamic'" class="content-section">
+      <!-- 动态卡片网格 -->
+      <div class="content-grid">
+        <div class="draft-card" @click="handleDraftClick">
+          <el-icon :size="48" color="#999">
+            <Edit />
+          </el-icon>
+          <div class="draft-title">本地草稿</div>
+          <div class="draft-count">有{{ draftCount }}篇动态待发布</div>
+        </div>
+        <div v-for="item in contentList" :key="item.id" class="content-card" @click="handleCardClick(item)">
+          <!-- 封面图片/视频区域 -->
+          <div class="content-cover-wrapper">
+            <img v-if="item.coverUrl" :src="item.coverUrl" :alt="item.title" class="content-cover" />
+            <div v-else class="cover-placeholder">
+              <el-icon :size="48" color="#999">
+                <Picture />
+              </el-icon>
+            </div>
+
+            <!-- 视频播放按钮 -->
+            <div v-if="item.type === 'video'" class="play-overlay">
+              <el-icon :size="40" color="#fff">
+                <VideoPlay />
+              </el-icon>
+            </div>
+
+            <!-- 标题标签(仅第一个显示) -->
+            <div v-if="item.showLabel" class="content-label">
+              {{ item.labelText }}
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="content-footer">
+            <div class="footer-left">
+              <el-icon :size="14" color="#666">
+                <View />
+              </el-icon>
+              <span class="view-count">{{ item.liulanCount }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 赞过 -->
+    <div class="content-section" v-if="dianZanList.length > 0 && activeTab === 'liked'">
+      <div class="content-grid">
+        <div v-for="item in dianZanList" :key="item.id" class="content-card" @click="handleCardClick(item)">
+          <!-- 封面图片/视频区域 -->
+          <div class="content-cover-wrapper">
+            <img v-if="item.imagePath" :src="item.imagePath" :alt="item.title" class="content-cover" />
+            <div v-else class="cover-placeholder">
+              <el-icon :size="48" color="#999">
+                <Picture />
+              </el-icon>
+            </div>
+
+            <!-- 视频播放按钮 -->
+            <div v-if="item.type === 'video'" class="play-overlay">
+              <el-icon :size="40" color="#fff">
+                <VideoPlay />
+              </el-icon>
+            </div>
+
+            <!-- 标题标签(仅第一个显示) -->
+            <div v-if="item.showLabel" class="content-label">
+              {{ item.labelText }}
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="content-footer">
+            <div class="footer-left">
+              <el-icon :size="14" :color="item.isLike == '1' ? '#ff6700' : '#666'">
+                <StarFilled v-if="item.isLike == '1'" />
+                <Star v-else />
+              </el-icon>
+              <span class="view-count">{{ item.dianzanCount }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 动态详情 Drawer -->
+    <el-drawer
+      v-model="detailDrawerVisible"
+      direction="rtl"
+      size="90%"
+      :show-close="false"
+      destroy-on-close
+      class="detail-drawer"
+    >
+      <template #header>
+        <div class="drawer-header">
+          <el-button class="close-btn" text @click="handleCloseDetail">
+            <el-icon :size="24">
+              <Close />
+            </el-icon>
+          </el-button>
+        </div>
+      </template>
+
+      <div v-if="currentDetail" class="detail-content">
+        <!-- 主内容区域 -->
+        <div class="detail-main">
+          <!-- 图片/视频轮播展示 -->
+          <div class="media-container">
+            <!-- 多媒体轮播 -->
+            <el-carousel
+              v-if="currentDetail.mediaList && currentDetail.mediaList.length > 0"
+              :autoplay="false"
+              :loop="false"
+              indicator-position="outside"
+              arrow="always"
+              height="100%"
+              class="media-carousel"
+              @change="handleCarouselChange"
+            >
+              <el-carousel-item v-for="(media, index) in currentDetail.mediaList" :key="index">
+                <!-- 视频 -->
+                <video
+                  v-if="media.type === 'video'"
+                  :ref="el => setVideoRef(el, index)"
+                  :src="media.url"
+                  class="detail-media detail-video"
+                  controls
+                  preload="metadata"
+                  @play="handleVideoPlay(index)"
+                />
+                <!-- 图片 -->
+                <img v-else :src="media.url" :alt="currentDetail.title" class="detail-media detail-image" />
+              </el-carousel-item>
+            </el-carousel>
+            <!-- 占位符 -->
+            <div v-else class="media-placeholder">
+              <el-icon :size="80" color="#dcdfe6">
+                <Picture />
+              </el-icon>
+            </div>
+            <!-- 媒体计数指示器 -->
+            <div v-if="currentDetail.mediaList && currentDetail.mediaList.length > 1" class="media-counter">
+              {{ currentCarouselIndex + 1 }} / {{ currentDetail.mediaList.length }}
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="detail-info">
+            <div class="author-info">
+              <div class="author-avatar">
+                <img
+                  v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
+                  :src="currentDetail.author?.avatar || currentDetail.userAvatar"
+                  :alt="currentDetail.author?.name || currentDetail.userName"
+                />
+                <el-icon v-else :size="32">
+                  <Avatar />
+                </el-icon>
+              </div>
+              <div class="author-details">
+                <div class="author-name">@{{ currentDetail.author?.name || currentDetail.userName }}</div>
+                <div class="publish-time">
+                  {{ currentDetail.publishTime }}
+                </div>
+              </div>
+            </div>
+
+            <div class="detail-description">
+              <p>{{ currentDetail.description }}</p>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右侧操作栏 -->
+        <div class="action-bar">
+          <!-- 作者头像 -->
+          <div class="action-item author-action">
+            <div class="action-avatar" @click="handleViewUserProfile">
+              <img
+                v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
+                :src="currentDetail.author?.avatar || currentDetail.userAvatar"
+                :alt="currentDetail.author?.name || currentDetail.userName"
+              />
+              <el-icon v-else :size="40" color="#fff">
+                <Avatar />
+              </el-icon>
+            </div>
+          </div>
+
+          <!-- 点赞 -->
+          <div class="action-item" @click="handleDetailLike">
+            <div class="action-icon">
+              <el-icon :size="28" :color="currentDetail.isLike == '1' ? '#f56c6c' : '#fff'">
+                <StarFilled v-if="currentDetail.isLike == '1'" />
+                <Star v-else />
+              </el-icon>
+            </div>
+            <div class="action-count">
+              {{ currentDetail.dianzanCount }}
+            </div>
+          </div>
+
+          <!-- 评论 -->
+          <div class="action-item" @click="handleShowComments">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <ChatDotRound />
+              </el-icon>
+            </div>
+            <div class="action-count">
+              {{ currentDetail.commentCount }}
+            </div>
+          </div>
+
+          <!-- 分享 -->
+          <div class="action-item" @click="handleShare">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <Share />
+              </el-icon>
+            </div>
+            <div class="action-count">分享</div>
+          </div>
+
+          <!-- 更多 -->
+          <el-popover placement="left" :width="120" trigger="click" popper-class="more-actions-popover">
+            <template #reference>
+              <div class="action-item">
+                <div class="action-icon">
+                  <el-icon :size="28" color="#fff">
+                    <MoreFilled />
+                  </el-icon>
+                </div>
+              </div>
+            </template>
+            <div class="more-actions-menu">
+              <!-- 如果是当前用户的动态,显示删除 -->
+              <template v-if="isMyDynamic">
+                <!-- <div class="menu-item" @click="handleEditDynamic">
+                  <el-icon :size="18">
+                    <Edit />
+                  </el-icon>
+                  <span>编辑</span>
+                </div> -->
+                <div class="menu-item" style="display: flex; cursor: pointer" @click="handleDeleteDynamic">
+                  <el-icon :size="18">
+                    <Delete /> </el-icon
+                  >&nbsp;&nbsp;
+                  <span>删除</span>
+                </div>
+              </template>
+              <!-- 如果不是当前用户的动态,显示举报和拉黑 -->
+              <template v-else>
+                <div class="menu-item" @click="handleReportDynamic">
+                  <el-icon :size="18">
+                    <Warning />
+                  </el-icon>
+                  <span>举报</span>
+                </div>
+                <div class="menu-item" @click="handleBlockUserClick">
+                  <el-icon :size="18">
+                    <CircleClose />
+                  </el-icon>
+                  <span>拉黑</span>
+                </div>
+              </template>
+            </div>
+          </el-popover>
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- 评论侧边栏 -->
+    <el-drawer v-model="commentDrawerVisible" title="评论" direction="rtl" size="400px" destroy-on-close>
+      <!-- 评论列表 -->
+      <div class="comment-list-container">
+        <div v-if="commentListData.length > 0" class="comment-list">
+          <div v-for="comment in commentListData" :key="comment.id" class="comment-item">
+            <div class="comment-avatar">
+              <img v-if="comment.userAvatar" :src="comment.userAvatar" :alt="comment.userName" />
+              <el-icon v-else :size="32">
+                <Avatar />
+              </el-icon>
+            </div>
+            <div class="comment-content-wrapper">
+              <div class="comment-header">
+                <span class="comment-user-name">{{ comment.userName }}</span>
+              </div>
+              <div class="comment-text">
+                {{ comment.commentContent }}
+              </div>
+
+              <div class="comment-actions">
+                <span class="comment-action-item" @click="handleLikeComment(comment)">
+                  <el-icon :size="16" :color="comment.isLiked ? '#f56c6c' : '#999'">
+                    <Star />
+                  </el-icon>
+                  <span>{{ comment.likeCount || 0 }}</span>
+                </span>
+                <span class="comment-action-item" @click="handleReplyComment(comment)">
+                  <el-icon :size="16">
+                    <ChatDotRound />
+                  </el-icon>
+                  <span>回复</span>
+                </span>
+              </div>
+              <!-- 商家回复 -->
+              <div v-for="item in comment.storeComment" :key="item.id" class="store-comment-wrapper">
+                <div class="store-comment-item">
+                  <div class="store-comment-avatar">
+                    <img v-if="item.userImage" :src="item.userImage" :alt="item.userName" />
+                    <el-icon v-else :size="24">
+                      <Avatar />
+                    </el-icon>
+                  </div>
+                  <div class="store-comment-content">
+                    <div class="store-comment-header">
+                      <span class="store-comment-user-name">{{ item.userName || "商家" }}</span>
+                      <span class="store-comment-time">{{ item.createdTime || item.createDate }}</span>
+                    </div>
+                    <div class="store-comment-text">
+                      {{ item.commentContent }}
+                      <span
+                        class="comment-action-item"
+                        @click="handleLikeComment(item)"
+                        style="display: flex; align-items: center"
+                      >
+                        <el-icon :size="16" :color="item.isLiked ? '#f56c6c' : '#999'">
+                          <Star /> </el-icon
+                        >&nbsp;
+                        <span>{{ item.likeCount || 0 }}</span>
+                      </span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <el-empty v-else description="暂无评论" />
+      </div>
+
+      <!-- 评论输入框 -->
+      <div class="comment-input-wrapper">
+        <!-- 回复提示 -->
+        <div v-if="replyingComment" class="reply-hint">
+          <span class="reply-text">回复 @{{ replyingComment.userName }}</span>
+          <el-icon class="cancel-reply" @click="handleCancelReply">
+            <Close />
+          </el-icon>
+        </div>
+        <el-input
+          v-model="commentInput"
+          type="textarea"
+          :rows="3"
+          :placeholder="replyingComment ? '输入回复内容...' : '你要评论点什么呢~'"
+          maxlength="500"
+          show-word-limit
+        />
+        <el-button type="primary" :loading="commentSubmitting" @click="handleSubmitComment">
+          {{ replyingComment ? "回复" : "发送" }}
+        </el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 好友/关注/粉丝对话框 -->
+    <el-dialog
+      v-model="relationDialogVisible"
+      :title="relationDialogTitle"
+      width="600px"
+      destroy-on-close
+      @close="handleCloseRelationDialog"
+    >
+      <div class="relation-dialog-content">
+        <!-- 标签页 -->
+        <el-tabs v-model="relationActiveTab" @tab-click="handleRelationTabClick">
+          <el-tab-pane label="好友" name="friend" value="friend" />
+          <el-tab-pane label="关注" name="follow" value="follow" />
+          <el-tab-pane label="粉丝" name="fans" value="fans" />
+        </el-tabs>
+
+        <!-- 搜索框 -->
+        <div class="search-box">
+          <el-input v-model="relationSearch" placeholder="请输入手机号或昵称" clearable @input="handleRelationSearch">
+            <template #prefix>
+              <el-icon>
+                <Search />
+              </el-icon>
+            </template>
+          </el-input>
+        </div>
+
+        <!-- 用户列表 -->
+        <div class="relation-list">
+          <div v-for="user in filteredRelationList" :key="user.id" class="relation-item">
+            <div class="user-info-row">
+              <div class="user-avatar-small">
+                <img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
+                <el-icon v-else :size="40">
+                  <Avatar />
+                </el-icon>
+              </div>
+              <div class="user-details">
+                <div class="user-name-text">
+                  {{ user.name }}
+                </div>
+                <div class="user-desc">
+                  {{ user.description }}
+                </div>
+              </div>
+            </div>
+            <div class="action-button">
+              <el-button
+                v-if="user.relationStatus === 'following'"
+                type="primary"
+                plain
+                size="small"
+                @click="handleUnfollow(user)"
+              >
+                已关注
+              </el-button>
+              <el-button v-else-if="user.relationStatus === 'mutual'" type="primary" size="small" @click="handleUnfollow(user)">
+                互相关注
+              </el-button>
+              <el-button v-else type="primary" plain size="small" @click="handleFollow(user)"> 关注 </el-button>
+            </div>
+          </div>
+
+          <!-- 空状态 -->
+          <el-empty v-if="filteredRelationList.length === 0" description="暂无数据" />
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 举报对话框 -->
+    <el-dialog v-model="reportDialogVisible" title="举报理由" width="500px" destroy-on-close @close="handleCloseReportDialog">
+      <div class="report-dialog-content">
+        <div class="report-tip">请选择最符合的原因,以便于我们进行的处理</div>
+
+        <!-- 举报原因选项 -->
+        <div class="report-reasons">
+          <el-radio-group v-model="reportForm.reason">
+            <el-radio label="用户头像"> 用户头像 </el-radio>
+            <el-radio label="名称/昵称"> 名称/昵称 </el-radio>
+            <el-radio label="违法违规"> 违法违规 </el-radio>
+            <el-radio label="低俗色情、暴力恐怖、政治谣言"> 低俗色情、暴力恐怖、政治谣言 </el-radio>
+            <el-radio label="涉嫌诈骗"> 涉嫌诈骗 </el-radio>
+            <el-radio label="人身攻击"> 人身攻击 </el-radio>
+            <el-radio label="侵犯版权"> 侵犯版权 </el-radio>
+            <el-radio label="恶意骚扰"> 恶意骚扰 </el-radio>
+            <el-radio label="虚假/过度宣传"> 虚假/过度宣传 </el-radio>
+            <el-radio label="诱导点赞分享"> 诱导点赞分享 </el-radio>
+            <el-radio label="传播人身安全"> 传播人身安全 </el-radio>
+            <el-radio label="侵权举报"> 侵权举报 </el-radio>
+            <el-radio label="其他举报"> 其他举报 </el-radio>
+          </el-radio-group>
+        </div>
+
+        <!-- 详细描述 -->
+        <div class="report-description">
+          <el-input
+            v-model="reportForm.description"
+            type="textarea"
+            :rows="4"
+            placeholder="请描述任何涉及举报内容的其体情况,我们会综合一判断合举政采!(必填)"
+            maxlength="300"
+            show-word-limit
+          />
+        </div>
+
+        <!-- 上传凭证 -->
+        <div class="report-upload">
+          <div class="upload-title">上传凭证</div>
+          <el-upload
+            v-model:file-list="reportForm.fileList"
+            list-type="picture-card"
+            :limit="9"
+            :on-preview="handleReportPreview"
+            :on-remove="handleReportRemove"
+            :before-upload="beforeReportUpload"
+            :http-request="handleReportUpload"
+            accept="image/*"
+            multiple
+          >
+            <el-icon :size="24">
+              <Plus />
+            </el-icon>
+          </el-upload>
+        </div>
+
+        <!-- 同意协议 -->
+        <div class="report-agreement">
+          <el-checkbox v-model="reportForm.agreed"> 同意发票用户协议 </el-checkbox>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="reportDialogVisible = false"> 取消 </el-button>
+          <el-button type="primary" :loading="reportSubmitting" @click="handleSubmitReport"> 提交 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 分享对话框 -->
+    <el-dialog v-model="shareDialogVisible" title="分享给好友" width="500px" destroy-on-close @close="handleCloseShareDialog">
+      <div class="share-dialog-content">
+        <!-- 好友列表 -->
+        <div class="share-friend-list">
+          <div v-if="filteredShareFriendList.length > 0">
+            <div
+              v-for="friend in filteredShareFriendList"
+              :key="friend.id"
+              class="share-friend-item"
+              @click="handleSelectFriend(friend)"
+            >
+              <div class="friend-info">
+                <div class="friend-avatar">
+                  <img v-if="friend.avatar" :src="friend.avatar" :alt="friend.name" />
+                  <el-icon v-else :size="40">
+                    <Avatar />
+                  </el-icon>
+                </div>
+                <div class="friend-name">
+                  {{ friend.name }}
+                </div>
+              </div>
+              <el-icon v-if="selectedFriends.includes(friend.id)" :size="20" color="#409eff">
+                <CircleCheck />
+              </el-icon>
+            </div>
+          </div>
+          <el-empty v-else description="暂无好友" />
+        </div>
+
+        <!-- 已选择的好友 -->
+        <div v-if="selectedFriends.length > 0" class="selected-friends">
+          <div class="selected-title">已选择 {{ selectedFriends.length }} 位好友</div>
+          <div class="selected-list">
+            <el-tag v-for="friendId in selectedFriends" :key="friendId" closable @close="handleRemoveFriend(friendId)">
+              {{ shareFriendList.find(f => f.id === friendId)?.name }}
+            </el-tag>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="shareDialogVisible = false"> 取消 </el-button>
+          <el-button
+            type="primary"
+            :loading="shareSubmitting"
+            :disabled="selectedFriends.length === 0"
+            @click="handleConfirmShare"
+          >
+            确认分享
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="myDynamic">
+import { ref, reactive, computed, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import {
+  Avatar,
+  Picture,
+  VideoPlay,
+  View,
+  Search,
+  Close,
+  Star,
+  StarFilled,
+  ChatDotRound,
+  Share,
+  MoreFilled,
+  Edit,
+  Delete,
+  Warning,
+  Plus,
+  CircleCheck,
+  CircleClose
+} from "@element-plus/icons-vue";
+import {
+  deleteDynamicsById,
+  getUserDynamics,
+  getUserDraftDynamics,
+  getHomePageInfo,
+  getDianZanList,
+  saveComment,
+  commentList,
+  getMutualAttention,
+  getMyFollowed,
+  addTransferCount,
+  getMyUserFans
+} from "@/api/modules/newLoginApi";
+import { blockUser, likeDynamicNew, unlikeDynamicNew } from "@/api/modules/dynamicManagement";
+import { useUserStore } from "@/stores/modules/user";
+import FriendCoupon from "./friendCoupon.vue";
+
+const router = useRouter();
+const userStore = useUserStore();
+
+// 接口定义
+interface UserInfo {
+  name: string;
+  avatar: string;
+  bio: string;
+  followCount: number;
+  fansCount: number;
+  likeCount: number;
+}
+
+interface MediaItem {
+  url: string;
+  type: "image" | "video";
+}
+
+interface ContentItem {
+  id: number;
+  title: string;
+  coverUrl: string;
+  type: "image" | "video";
+  viewCount: number;
+  liulanCount?: number;
+  isLike?: string;
+  dianzanCount?: number;
+  showLabel?: boolean;
+  labelText?: string;
+  createTime: string;
+  mediaList?: MediaItem[];
+}
+
+interface DetailItem extends ContentItem {
+  author?: {
+    id: number;
+    name: string;
+    avatar: string;
+  };
+  userAvatar?: string;
+  userName?: string;
+  description: string;
+  publishTime: string;
+  likeCount: number;
+  commentCount: number;
+  isLiked: boolean;
+  isLike?: string; // 点赞状态:'0'未点赞,'1'已点赞
+  dianzanCount?: number; // 点赞数量
+  userId?: string | number; // 发布者ID
+  phoneId?: string; // 发布者店铺ID
+  storeUserId?: string | number; // 小店用户ID(用于举报和拉黑)
+  userType?: number; // 发布者用户类型:1商家,2用户
+}
+
+interface RelationUser {
+  id: number;
+  name: string;
+  avatar: string;
+  description: string;
+  relationStatus: "following" | "mutual" | "none"; // following: 已关注, mutual: 互相关注, none: 未关注
+  phoneId?: string; // 用户的 phoneId,用于后续操作
+}
+
+// 响应式数据
+const activeTab = ref("dynamic");
+const contentList = ref<ContentItem[]>([]);
+const draftCount = ref(0); // 草稿数量
+
+// 详情 Drawer 相关
+const detailDrawerVisible = ref(false);
+const currentDetail = ref<DetailItem | null>(null);
+
+// 视频轮播相关
+const videoRefs = ref<HTMLVideoElement[]>([]);
+const currentCarouselIndex = ref(0);
+
+// 评论相关
+const commentDrawerVisible = ref(false);
+const commentListData = ref<any[]>([]);
+const commentInput = ref("");
+const commentSubmitting = ref(false);
+const replyingComment = ref<any>(null);
+
+// 关系对话框相关
+const relationDialogVisible = ref(false);
+const relationActiveTab = ref("friend");
+const relationSearch = ref("");
+const relationList = ref<RelationUser[]>([]);
+//点击本地草稿
+const handleDraftClick = () => {
+  router.push({
+    path: "/dynamicManagement/draftDynamic"
+  });
+};
+// 举报对话框相关
+const reportDialogVisible = ref(false);
+const reportSubmitting = ref(false);
+const reportForm = reactive({
+  reason: "",
+  description: "",
+  fileList: [] as any[],
+  agreed: false
+});
+
+// 分享对话框相关
+interface ShareFriend {
+  id: number;
+  name: string;
+  avatar: string;
+  phoneId?: string;
+}
+
+const shareDialogVisible = ref(false);
+const shareSubmitting = ref(false);
+const shareSearch = ref("");
+const shareFriendList = ref<ShareFriend[]>([]);
+const selectedFriends = ref<number[]>([]);
+
+// 过滤后的好友列表
+const filteredShareFriendList = computed(() => {
+  if (!shareSearch.value) {
+    return shareFriendList.value;
+  }
+  const keyword = shareSearch.value.toLowerCase();
+  return shareFriendList.value.filter(friend => friend.name.toLowerCase().includes(keyword));
+});
+
+// 用户信息
+const userInfo = reactive<UserInfo>({
+  name: "白己的流浪主页",
+  avatar: "",
+  bio: "一家好吃的火锅店",
+  followCount: 19,
+  fansCount: 10,
+  likeCount: 10
+});
+
+// 对话框标题
+const relationDialogTitle = computed(() => {
+  const titles = {
+    friend: "好友",
+    follow: "关注",
+    fans: "粉丝"
+  };
+  return titles[relationActiveTab.value as keyof typeof titles];
+});
+
+// 过滤后的关系列表
+const filteredRelationList = computed(() => {
+  if (!relationSearch.value) {
+    return relationList.value;
+  }
+  const keyword = relationSearch.value.toLowerCase();
+  return relationList.value.filter(
+    user => user.name.toLowerCase().includes(keyword) || user.description.toLowerCase().includes(keyword)
+  );
+});
+
+// 判断当前详情是否是当前用户的动态
+const isMyDynamic = computed(() => {
+  if (!currentDetail.value) return false;
+
+  // 获取当前用户的 storeId
+  const currentUserStoreId = userStore.userInfo?.storeId;
+
+  // 获取动态的 storeUserId 或 userId
+  const dynamicStoreUserId = currentDetail.value.storeUserId || currentDetail.value.userId;
+
+  // 通过 storeId 判断是否是当前用户的动态
+  const result = currentUserStoreId == dynamicStoreUserId;
+  console.log("是否是自己发布的作品:", result, {
+    currentUserStoreId,
+    dynamicStoreUserId
+  });
+
+  return result;
+});
+
+// 标签切换
+const handleTabClick = (pane: any) => {
+  const tabName = pane.props.name;
+
+  if (tabName === "liked") {
+    getDianZan();
+  } else if (tabName === "dynamic") {
+    getDyanmicList();
+  }
+};
+
+// 点击卡片
+const handleCardClick = async (item: any) => {
+  try {
+    // 解析媒体列表(支持多张图片和视频)
+    const mediaUrl = item.imagePath || "";
+    const mediaUrls = mediaUrl
+      .split(",")
+      .map((url: string) => url.trim())
+      .filter((url: string) => url);
+    const mediaList: MediaItem[] = mediaUrls.map((url: string) => ({
+      url,
+      type: url.toLowerCase().endsWith(".mp4") ? ("video" as const) : ("image" as const)
+    }));
+
+    const firstUrl = mediaUrls[0] || "";
+    const isVideo = firstUrl.toLowerCase().endsWith(".mp4");
+
+    currentDetail.value = {
+      ...item,
+      mediaList,
+      coverUrl: firstUrl,
+      type: isVideo ? "video" : "image",
+      author: {
+        id: item.userId || 0,
+        name: item.userName || "用户",
+        avatar: item.userAvatar || item.userImage || ""
+      },
+      userAvatar: item.userAvatar || item.userImage || "",
+      userName: item.userName || "用户",
+      description: item.context || item.title || "",
+      publishTime: item.createTime || item.createdTime || "",
+      likeCount: item.likeCount || item.praiseNum || 0,
+      commentCount: item.commentCount || 0,
+      isLiked: item.isLiked || false
+    };
+
+    detailDrawerVisible.value = true;
+
+    // 重置轮播索引
+    currentCarouselIndex.value = 0;
+  } catch (error) {
+    console.error("加载详情失败:", error);
+    ElMessage.error("加载详情失败");
+  }
+};
+
+// 关闭详情
+const handleCloseDetail = () => {
+  detailDrawerVisible.value = false;
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  setTimeout(() => {
+    currentDetail.value = null;
+    videoRefs.value = [];
+  }, 300);
+};
+
+// 设置视频引用
+const setVideoRef = (el: any, index: number) => {
+  if (el) {
+    videoRefs.value[index] = el as HTMLVideoElement;
+  }
+};
+
+// 轮播切换
+const handleCarouselChange = (newIndex: number) => {
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  currentCarouselIndex.value = newIndex;
+};
+
+// 视频播放
+const handleVideoPlay = (index: number) => {
+  // 暂停其他视频
+  videoRefs.value.forEach((video, i) => {
+    if (i !== index && video && !video.paused) {
+      video.pause();
+    }
+  });
+};
+
+// 查看用户主页
+const handleViewUserProfile = () => {
+  ElMessage.info("查看用户主页功能开发中");
+};
+
+// 详情页点赞
+const handleDetailLike = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const params = {
+      userId: currentUserPhoneId, // 当前用户phoneId
+      huifuId: currentDetail.value.id, // 动态ID
+      type: 2 // 2表示点赞动态
+    };
+
+    // 根据当前点赞状态调用不同的接口
+    if (currentDetail.value.isLike == "1") {
+      // 已点赞,调用取消点赞接口
+      await unlikeDynamicNew(params);
+      currentDetail.value.isLike = "0";
+      currentDetail.value.dianzanCount = Math.max(0, (currentDetail.value.dianzanCount || 1) - 1);
+    } else {
+      // 未点赞,调用点赞接口
+      await likeDynamicNew(params);
+      currentDetail.value.isLike = "1";
+      currentDetail.value.dianzanCount = (currentDetail.value.dianzanCount || 0) + 1;
+    }
+
+    // 同步更新列表中的数据
+    const listItem = contentList.value.find(item => item.id === currentDetail.value?.id);
+    if (listItem) {
+      listItem.isLike = currentDetail.value.isLike;
+      listItem.dianzanCount = currentDetail.value.dianzanCount;
+    }
+
+    // 同步更新点赞列表中的数据
+    const dianZanItem = dianZanList.value.find(item => item.id === currentDetail.value?.id);
+    if (dianZanItem) {
+      dianZanItem.isLike = currentDetail.value.isLike;
+      dianZanItem.dianzanCount = currentDetail.value.dianzanCount;
+    }
+
+    ElMessage.success(currentDetail.value.isLike == "1" ? "点赞成功" : "取消点赞");
+  } catch (error) {
+    console.error("点赞操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 分享
+const handleShare = async () => {
+  shareDialogVisible.value = true;
+  await loadShareFriendList();
+};
+
+// 加载好友列表
+const loadShareFriendList = async () => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const res: any = await getMutualAttention({
+      page: 1,
+      size: 1000,
+      fansId: fansId,
+      name: ""
+    });
+
+    if (res.code === 200) {
+      const dataList = res.data?.records || res.data?.list || res.data || [];
+      shareFriendList.value = dataList.map((item: any) => ({
+        id: item.id || item.userId,
+        name: item.userName || item.nickname || item.name || "用户",
+        avatar: item.userImage || item.avatar || item.headImg || "",
+        phoneId: item.phoneId || item.fansId || ""
+      }));
+      console.log("加载好友列表成功:", shareFriendList.value);
+    }
+  } catch (error) {
+    console.error("加载好友列表失败:", error);
+    ElMessage.error("加载好友列表失败");
+    shareFriendList.value = [];
+  }
+};
+
+// 搜索好友
+const handleShareSearch = () => {
+  // 搜索由计算属性自动处理
+};
+
+// 选择好友
+const handleSelectFriend = (friend: ShareFriend) => {
+  const index = selectedFriends.value.indexOf(friend.id);
+  if (index > -1) {
+    // 已选择,取消选择
+    selectedFriends.value.splice(index, 1);
+  } else {
+    // 未选择,添加选择
+    selectedFriends.value.push(friend.id);
+  }
+};
+
+// 移除已选择的好友
+const handleRemoveFriend = (friendId: number) => {
+  const index = selectedFriends.value.indexOf(friendId);
+  if (index > -1) {
+    selectedFriends.value.splice(index, 1);
+  }
+};
+
+// 确认分享
+const handleConfirmShare = async () => {
+  if (selectedFriends.value.length === 0) {
+    ElMessage.warning("请选择要分享的好友");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.error("动态信息不存在");
+    return;
+  }
+
+  try {
+    shareSubmitting.value = true;
+
+    // 调用 addTransferCount 接口,传递动态 id
+    const res: any = await addTransferCount({
+      id: currentDetail.value.id
+    });
+
+    if (res.code === 200) {
+      ElMessage.success(`已分享给 ${selectedFriends.value.length} 位好友`);
+      shareDialogVisible.value = false;
+
+      // 可以在这里更新动态的分享数(如果需要的话)
+      console.log("分享成功,动态ID:", currentDetail.value.id);
+    } else {
+      ElMessage.error(res.message || "分享失败");
+    }
+  } catch (error) {
+    console.error("分享失败:", error);
+    ElMessage.error("分享失败");
+  } finally {
+    shareSubmitting.value = false;
+  }
+};
+
+// 关闭分享对话框
+const handleCloseShareDialog = () => {
+  shareSearch.value = "";
+  selectedFriends.value = [];
+  shareFriendList.value = [];
+};
+
+// 显示评论
+const handleShowComments = async () => {
+  if (!currentDetail.value) return;
+  commentDrawerVisible.value = true;
+  await loadCommentList();
+};
+
+// 加载评论列表
+const loadCommentList = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    let params = {
+      businessId: String(currentDetail.value.id),
+      businessType: "2",
+      replyStatus: 0,
+      pageNum: 1,
+      pageSize: 100,
+      commentType: 0,
+      days: "",
+      phoneId: phoneId
+    };
+    const res: any = await commentList(params);
+    if (res.code === 200) {
+      commentListData.value = res.data.records || [];
+      // 更新评论总数
+      if (currentDetail.value) {
+        currentDetail.value.commentCount = res.data.total || 0;
+      }
+      console.log("评论列表:", commentListData.value);
+      console.log("评论总数:", res.data.total);
+    }
+  } catch (error) {
+    console.error("加载评论列表失败:", error);
+  }
+};
+
+// 点赞评论
+const handleLikeComment = async (comment: any) => {
+  console.log(comment);
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const params = {
+      userId: currentUserPhoneId, // 当前用户phoneId
+      huifuId: comment.id, // 评论ID
+      type: 1 // 1表示点赞评论
+    };
+
+    // 根据当前点赞状态调用不同的接口
+    if (comment.isLiked) {
+      // 已点赞,调用取消点赞接口
+      await unlikeDynamicNew(params);
+    } else {
+      // 未点赞,调用点赞接口
+      await likeDynamicNew(params);
+    }
+
+    // 切换点赞状态
+    comment.isLiked = !comment.isLiked;
+    comment.likeCount = comment.isLiked ? (comment.likeCount || 0) + 1 : Math.max(0, (comment.likeCount || 1) - 1);
+
+    ElMessage.success(comment.isLiked ? "点赞成功" : "取消点赞");
+  } catch (error) {
+    console.error("点赞评论失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 回复评论
+const handleReplyComment = (comment: any) => {
+  replyingComment.value = comment;
+  commentInput.value = `@${comment.userName} `;
+  // 聚焦到输入框
+  setTimeout(() => {
+    const textarea = document.querySelector(".comment-input-wrapper textarea") as HTMLTextAreaElement;
+    if (textarea) {
+      textarea.focus();
+    }
+  }, 100);
+};
+
+// 取消回复
+const handleCancelReply = () => {
+  replyingComment.value = null;
+  commentInput.value = "";
+};
+
+// 提交评论
+const handleSubmitComment = async () => {
+  if (!commentInput.value.trim()) {
+    ElMessage.warning("请输入评论内容");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.error("动态信息不存在");
+    return;
+  }
+
+  try {
+    commentSubmitting.value = true;
+
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 判断是回复评论还是评论动态
+    const isReply = !!replyingComment.value;
+
+    const params: any = {
+      replyId: isReply ? replyingComment.value.id : "", // 回复评论时传评论ID,否则为空
+      commentContent: commentInput.value,
+      businessType: "2",
+      businessId: String(currentDetail.value.id),
+      storeId: userStore.userInfo?.storeId || userStore.userInfo?.createdId,
+      commentStar: "",
+      phoneId: phoneId
+    };
+
+    const res: any = await saveComment(params);
+    if (res.code === 200) {
+      ElMessage.success(isReply ? "回复成功" : "评论成功");
+      commentInput.value = "";
+      replyingComment.value = null;
+      await loadCommentList();
+    } else {
+      ElMessage.error(res.message || (isReply ? "回复失败" : "评论失败"));
+    }
+  } catch (error) {
+    console.error("提交评论失败:", error);
+    ElMessage.error(replyingComment.value ? "回复失败" : "评论失败");
+  } finally {
+    commentSubmitting.value = false;
+  }
+};
+
+// 编辑动态
+const handleEditDynamic = () => {
+  if (!currentDetail.value) return;
+
+  // 关闭详情页
+  detailDrawerVisible.value = false;
+
+  // 跳转到编辑页面
+  router.push({
+    path: "/dynamicManagement/publishDynamic",
+    query: { id: currentDetail.value.id }
+  });
+};
+
+// 删除动态
+const handleDeleteDynamic = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    await ElMessageBox.confirm("确定要删除这条动态吗?删除后将无法恢复。", "删除确认", {
+      confirmButtonText: "确定删除",
+      cancelButtonText: "取消",
+      type: "warning",
+      confirmButtonClass: "el-button--danger"
+    }).then(async () => {
+      if (!currentDetail.value) return;
+      const res: any = await deleteDynamicsById({ id: currentDetail.value.id });
+      if (res.code === 200) {
+        ElMessage.success("删除成功");
+        detailDrawerVisible.value = false;
+        const index = contentList.value.findIndex(item => item.id === currentDetail.value?.id);
+        if (index > -1) {
+          contentList.value.splice(index, 1);
+        }
+      }
+    });
+  } catch {
+    // 用户取消删除
+  }
+};
+
+// 举报动态
+const handleReportDynamic = () => {
+  reportDialogVisible.value = true;
+};
+
+// 拉黑用户(点击菜单项)
+const handleBlockUserClick = () => {
+  handleBlockUser(false);
+};
+
+// 拉黑用户
+const handleBlockUser = async (skipConfirm: boolean = false) => {
+  if (!currentDetail.value) return;
+
+  try {
+    // 如果不跳过确认,显示确认对话框
+    if (!skipConfirm) {
+      await ElMessageBox.confirm("拉黑后将不再看到该用户的动态,确定要拉黑吗?", "拉黑确认", {
+        confirmButtonText: "确定拉黑",
+        cancelButtonText: "取消",
+        type: "warning"
+      });
+    }
+
+    // 获取当前用户信息
+    const currentUserId = userStore.userInfo?.id || userStore.userInfo?.userId || userStore.userInfo?.storeId || "";
+    const currentUserType = userStore.userInfo?.userType || userStore.userInfo?.type || 1; // 用户类型:1商家,2用户,默认1
+
+    // 获取被拉黑用户类型(从 phoneId 解析)
+    const getUserTypeFromPhoneId = (phoneId: string | undefined): number => {
+      if (!phoneId) return 1; // 默认商家
+      const prefix = phoneId.split("_")[0]; // 截取 "_" 之前的文字
+      return prefix === "store" ? 1 : 2; // store = 商家(1), 其他 = 用户(2)
+    };
+
+    const blockedUserType = getUserTypeFromPhoneId(currentDetail.value.phoneId) || 1;
+
+    console.log("当前用户信息:", userStore.userInfo);
+    console.log("当前用户类型:", currentUserType);
+    console.log("被拉黑用户类型:", blockedUserType);
+    console.log("动态详情:", currentDetail.value);
+
+    // 调用拉黑接口
+    await blockUser({
+      blockerType: currentUserType, // 拉黑者类型(当前登录用户类型)
+      blockedType: blockedUserType, // 被拉黑者类型(从 phoneId 解析)
+      blockerId: currentUserId, // 拉黑者ID(当前登录用户)
+      blockedId: currentDetail.value.storeUserId || currentDetail.value.userId || "" // 被拉黑者ID
+    });
+
+    if (!skipConfirm) {
+      ElMessage.success("已拉黑该用户");
+    }
+
+    detailDrawerVisible.value = false;
+
+    // 从列表中移除该用户的动态
+    const index = contentList.value.findIndex(item => item.id === currentDetail.value?.id);
+    if (index > -1) {
+      contentList.value.splice(index, 1);
+    }
+  } catch (error) {
+    if (!skipConfirm) {
+      // 单独拉黑时,用户取消操作或接口失败
+      console.error("拉黑失败:", error);
+      if (error !== "cancel") {
+        ElMessage.error("拉黑失败");
+      }
+    } else {
+      // 举报后自动拉黑失败,抛出错误
+      throw error;
+    }
+  }
+};
+
+// 举报图片预览
+const handleReportPreview = (uploadFile: any) => {
+  // 可以添加图片预览功能
+  console.log("预览图片", uploadFile);
+};
+
+// 移除举报图片
+const handleReportRemove = (uploadFile: any, uploadFiles: any[]) => {
+  // 已由 v-model:file-list 自动处理
+};
+
+// 举报图片上传前验证
+const beforeReportUpload = (file: File) => {
+  const isImage = file.type.startsWith("image/");
+  const isLt5M = file.size / 1024 / 1024 < 5;
+
+  if (!isImage) {
+    ElMessage.error("只能上传图片文件!");
+    return false;
+  }
+  if (!isLt5M) {
+    ElMessage.error("图片大小不能超过 5MB!");
+    return false;
+  }
+  return true;
+};
+
+// 自定义举报图片上传
+const handleReportUpload = async (options: any) => {
+  const { file } = options;
+
+  try {
+    // TODO: 集成真实上传接口时,取消下面的注释
+    // const uploadFormData = new FormData();
+    // uploadFormData.append('file', file);
+    // const res = await uploadDynamicImage(uploadFormData);
+    // // 上传成功后更新文件列表
+    // return res;
+
+    // 临时方案:使用 FileReader 模拟上传
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = e => {
+        resolve({
+          url: e.target?.result
+        });
+      };
+      reader.onerror = reject;
+      reader.readAsDataURL(file);
+    });
+  } catch (error) {
+    ElMessage.error("图片上传失败");
+    throw error;
+  }
+};
+
+// 提交举报
+const handleSubmitReport = async () => {
+  // 验证表单
+  if (!reportForm.reason) {
+    ElMessage.warning("请选择举报原因");
+    return;
+  }
+
+  if (!reportForm.description.trim()) {
+    ElMessage.warning("请填写详细描述");
+    return;
+  }
+
+  if (!reportForm.agreed) {
+    ElMessage.warning("请同意发票用户协议");
+    return;
+  }
+
+  reportSubmitting.value = true;
+
+  try {
+    // TODO: 集成真实接口时,取消下面的注释
+    // await reportDynamic({
+    //   dynamicId: currentDetail.value?.id,
+    //   reason: reportForm.reason,
+    //   description: reportForm.description,
+    //   images: reportForm.fileList.map(f => f.url)
+    // });
+
+    // 模拟提交延迟
+    await new Promise(resolve => setTimeout(resolve, 1000));
+
+    ElMessage.success("举报提交成功,我们会尽快处理");
+    reportDialogVisible.value = false;
+  } catch (error) {
+    ElMessage.error("举报提交失败");
+  } finally {
+    reportSubmitting.value = false;
+  }
+};
+
+// 关闭举报对话框
+const handleCloseReportDialog = () => {
+  reportForm.reason = "";
+  reportForm.description = "";
+  reportForm.fileList = [];
+  reportForm.agreed = false;
+};
+
+// 加载用户主页信息
+const loadUserInfo = async () => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 调用接口获取主页信息
+    const res: any = await getHomePageInfo({
+      phoneId: phoneId
+    });
+
+    if (res.code === 200 && res.data) {
+      userInfo.followCount = res.data.friendNum;
+      userInfo.fansCount = res.data.followNum;
+      userInfo.likeCount = res.data.fansNum;
+
+      console.log("加载用户信息成功:", userInfo);
+    }
+  } catch (error) {
+    console.error("加载用户信息失败:", error);
+  }
+};
+
+// 加载草稿数量
+const loadDraftCount = async () => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 调用接口获取草稿列表
+    const res: any = await getUserDraftDynamics({
+      phoneId: phoneId,
+      page: 1,
+      size: 1000
+    });
+
+    if (res.code === 200) {
+      const draftList = res.data?.records || res.data?.list || [];
+      draftCount.value = draftList.length;
+      console.log("加载草稿数量成功:", draftCount.value);
+    }
+  } catch (error) {
+    console.error("加载草稿数量失败:", error);
+  }
+};
+
+// 加载内容列表
+const loadContentList = async () => {
+  // 根据当前 tab 调用不同的接口
+  if (activeTab.value === "dynamic") {
+    getDyanmicList();
+  } else if (activeTab.value === "liked") {
+    getDianZan();
+  }
+};
+const dianZanList = ref<any[]>([]);
+const getDianZan = async () => {
+  const phone = userStore.userInfo?.phone || "";
+  const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+  const res: any = await getDianZanList({
+    phoneId: phoneId
+  });
+  if (res.code === 200) {
+    dianZanList.value = res.data;
+  }
+};
+const getDyanmicList = async () => {
+  const phone = userStore.userInfo?.phone || "";
+  const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+  const res: any = await getUserDynamics({
+    phoneId: phoneId,
+    type: 2, // 2表示动态类型
+    myself: 1, // 1表示自己的动态
+    page: 1,
+    size: 1000
+  });
+  if (res.code === 200) {
+    const dataList = res.data?.records || res.data?.list || [];
+    contentList.value = dataList.map((item: any) => ({
+      id: item.id,
+      title: item.title || item.context || "",
+      coverUrl: item.imagePath ? item.imagePath.split(",")[0] : "", // 取第一张图片作为封面
+      type: item.imagePath && item.imagePath.toLowerCase().includes(".mp4") ? "video" : "image",
+      viewCount: item.viewCount || item.browseNum || 0,
+      showLabel: false,
+      labelText: "",
+      createTime: item.createTime || item.createdTime || new Date().toISOString(),
+      // 保留原始数据,用于详情展示
+      ...item
+    }));
+  }
+};
+
+// 打开关系对话框
+const openRelationDialog = (type: "friend" | "follow" | "fans") => {
+  relationActiveTab.value = type;
+  if (relationActiveTab.value == "friend") {
+    handleFriendList();
+  } else if (relationActiveTab.value == "follow") {
+    handleFollowList();
+  } else {
+    handleFansList();
+  }
+  relationDialogVisible.value = true;
+};
+
+// 关系标签切换
+const handleRelationTabClick = (tab: any) => {
+  relationSearch.value = "";
+
+  // 从 tab 对象中获取当前点击的标签名称
+  const tabName = tab?.props?.name || tab?.paneName;
+
+  console.log("切换标签:", tabName);
+
+  if (tabName === "friend") {
+    handleFriendList();
+  } else if (tabName === "follow") {
+    handleFollowList();
+  } else if (tabName === "fans") {
+    handleFansList();
+  }
+};
+//好友列表
+const handleFriendList = async () => {
+  const phone = userStore.userInfo?.phone || "";
+  const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
+  const res: any = await getMutualAttention({
+    page: 1,
+    size: 1000,
+    fansId: fansId,
+    name: relationSearch.value || ""
+  });
+  if (res.code === 200) {
+    const dataList = res.data?.records || res.data?.list || res.data || [];
+    relationList.value = dataList.map((item: any) => ({
+      id: item.id || item.userId,
+      name: item.userName || item.nickname || item.name || "用户",
+      avatar: item.userImage || item.avatar || item.headImg || "",
+      description: item.description || item.bio || item.signature || "欢迎来这里",
+      relationStatus: "mutual" as const, // 好友列表都是互相关注
+      phoneId: item.phoneId || item.fansId || "" // 保存 phoneId 用于后续操作
+    }));
+  }
+};
+//关注列表
+const handleFollowList = async () => {
+  const phone = userStore.userInfo?.phone || "";
+  const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
+  const res: any = await getMyFollowed({
+    page: 1,
+    size: 1000,
+    fansId: fansId,
+    name: relationSearch.value || ""
+  });
+  if (res.code === 200) {
+    const dataList = res.data?.records || res.data?.list || res.data || [];
+    relationList.value = dataList.map((item: any) => ({
+      id: item.id || item.userId,
+      name: item.userName || item.nickname || item.name || "用户",
+      avatar: item.userImage || item.avatar || item.headImg || "",
+      description: item.description || item.bio || item.signature || "欢迎来这里",
+      relationStatus: "following" as const, // 关注列表都是已关注状态
+      phoneId: item.phoneId || item.followedId || "" // 保存 phoneId 用于后续操作
+    }));
+  }
+};
+//粉丝列表
+const handleFansList = async () => {
+  const phone = userStore.userInfo?.phone || "";
+  const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
+  const res: any = await getMyUserFans({
+    page: 1,
+    size: 1000,
+    fansId: fansId,
+    name: relationSearch.value || ""
+  });
+  if (res.code === 200) {
+    const dataList = res.data?.records || res.data?.list || res.data || [];
+    relationList.value = dataList.map((item: any) => ({
+      id: item.id || item.userId,
+      name: item.userName || item.nickname || item.name || "用户",
+      avatar: item.userImage || item.avatar || item.headImg || "",
+      description: item.description || item.bio || item.signature || "欢迎来这里",
+      // 粉丝列表需要判断是否互相关注
+      relationStatus: item.isFollowThis === 1 ? ("mutual" as const) : ("none" as const),
+      phoneId: item.phoneId || item.fansId || "" // 保存 phoneId 用于后续操作
+    }));
+  }
+};
+
+// 搜索关系列表
+const handleRelationSearch = () => {
+  if (relationActiveTab.value === "friend") {
+    handleFriendList();
+  } else if (relationActiveTab.value === "follow") {
+    handleFollowList();
+  } else if (relationActiveTab.value === "fans") {
+    handleFansList();
+  }
+};
+
+// 关注用户
+const handleFollow = async (user: RelationUser) => {
+  try {
+    // TODO: 集成真实接口时,取消下面的注释
+    // await followUser({ userId: user.id });
+
+    user.relationStatus = "following";
+    ElMessage.success("关注成功");
+
+    // 更新用户统计信息
+    if (relationActiveTab.value === "fans") {
+      userInfo.followCount++;
+    }
+  } catch (error) {
+    ElMessage.error("关注失败");
+  }
+};
+
+// 取消关注用户
+const handleUnfollow = async (user: RelationUser) => {
+  try {
+    await ElMessageBox.confirm("确定要取消关注吗?", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+
+    // TODO: 集成真实接口时,取消下面的注释
+    // await unfollowUser({ userId: user.id });
+
+    user.relationStatus = "none";
+    ElMessage.success("已取消关注");
+
+    // 更新用户统计信息
+    if (relationActiveTab.value === "follow") {
+      userInfo.followCount--;
+    }
+  } catch {
+    // 用户取消操作
+  }
+};
+
+// 关闭关系对话框
+const handleCloseRelationDialog = () => {
+  relationSearch.value = "";
+  relationList.value = [];
+};
+
+// 初始化
+onMounted(() => {
+  loadUserInfo();
+  getDyanmicList();
+  loadDraftCount();
+
+  // 为统计数据添加点击事件监听
+  // 这部分通过模板中的 @click 直接处理
+});
+</script>
+
+<style scoped lang="scss">
+// 详情 Drawer
+:deep(.detail-drawer) {
+  .el-drawer__header {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    z-index: 10;
+    padding: 0;
+    margin: 0;
+    background: transparent;
+  }
+  .el-drawer__body {
+    padding: 0;
+    background: #000000;
+  }
+  .drawer-header {
+    padding: 20px;
+    .close-btn {
+      padding: 8px;
+      font-size: 24px;
+      color: #ffffff;
+      background: #ffffff;
+    }
+  }
+  .detail-content {
+    position: relative;
+    display: flex;
+    width: 100%;
+    height: 100%;
+
+    // 主内容区域
+    .detail-main {
+      display: flex;
+      flex: 1;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 80px 120px 40px 40px;
+      .media-container {
+        position: relative;
+        display: flex;
+        flex: 1;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        max-width: 800px;
+        min-height: 400px;
+        .media-carousel {
+          width: 100%;
+          height: 100%;
+          min-height: 400px;
+          max-height: 70vh;
+          :deep(.el-carousel__container) {
+            height: 100% !important;
+            min-height: 400px;
+          }
+          :deep(.el-carousel__item) {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background: transparent;
+          }
+          :deep(.el-carousel__arrow) {
+            width: 48px;
+            height: 48px;
+            font-size: 24px;
+            color: #ffffff;
+            background-color: rgb(0 0 0 / 40%);
+            border: none;
+            &:hover {
+              background-color: rgb(0 0 0 / 60%);
+            }
+          }
+          :deep(.el-carousel__indicators) {
+            bottom: -30px;
+            .el-carousel__indicator {
+              .el-carousel__button {
+                width: 8px;
+                height: 8px;
+                background-color: rgb(255 255 255 / 40%);
+                border-radius: 50%;
+              }
+              &.is-active .el-carousel__button {
+                background-color: #409eff;
+              }
+            }
+          }
+        }
+        .detail-media {
+          max-width: 100%;
+          max-height: 65vh;
+          object-fit: contain;
+          border-radius: 8px;
+        }
+        .detail-image {
+          max-width: 100%;
+          max-height: 65vh;
+          object-fit: contain;
+          border-radius: 8px;
+        }
+        .detail-video {
+          width: 100%;
+          max-height: 65vh;
+          background: #000000;
+          border-radius: 8px;
+        }
+        .media-placeholder {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 400px;
+          height: 400px;
+          background: rgb(255 255 255 / 5%);
+          border-radius: 8px;
+        }
+        .media-counter {
+          position: absolute;
+          right: 16px;
+          bottom: -24px;
+          padding: 4px 12px;
+          font-size: 14px;
+          color: #ffffff;
+          background: rgb(0 0 0 / 50%);
+          border-radius: 12px;
+        }
+      }
+      .detail-info {
+        width: 100%;
+        max-width: 800px;
+        padding: 20px 0;
+        .author-info {
+          display: flex;
+          gap: 12px;
+          align-items: center;
+          margin-bottom: 16px;
+          .author-avatar {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 40px;
+            height: 40px;
+            overflow: hidden;
+            background: rgb(255 255 255 / 10%);
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          .author-details {
+            flex: 1;
+            .author-name {
+              margin-bottom: 4px;
+              font-size: 16px;
+              font-weight: 500;
+              color: #ffffff;
+            }
+            .publish-time {
+              font-size: 13px;
+              color: rgb(255 255 255 / 60%);
+            }
+          }
+        }
+        .detail-description {
+          font-size: 15px;
+          line-height: 1.6;
+          color: #ffffff;
+          p {
+            margin: 0;
+          }
+        }
+      }
+    }
+
+    // 右侧操作栏
+    .action-bar {
+      position: fixed;
+      right: 40px;
+      bottom: 100px;
+      z-index: 10;
+      display: flex;
+      flex-direction: column;
+      gap: 24px;
+      .action-item {
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+        align-items: center;
+        cursor: pointer;
+        transition: transform 0.3s;
+        &:hover {
+          transform: scale(1.1);
+        }
+        &.author-action {
+          cursor: pointer;
+          &:hover {
+            transform: scale(1.1);
+          }
+        }
+        .action-avatar {
+          position: relative;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 48px;
+          height: 48px;
+          overflow: visible;
+          cursor: pointer;
+          background: rgb(255 255 255 / 20%);
+          border: 2px solid #ffffff;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            border-radius: 50%;
+          }
+        }
+        .action-icon {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 48px;
+          height: 48px;
+          background: rgb(0 0 0 / 50%);
+          backdrop-filter: blur(10px);
+          border-radius: 50%;
+          &.follow-icon {
+            background: #409eff;
+          }
+        }
+        .action-count {
+          font-size: 13px;
+          color: #ffffff;
+          text-align: center;
+          text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
+        }
+      }
+    }
+  }
+}
+
+// 更多操作 Popover
+:deep(.more-actions-popover) {
+  min-width: 120px;
+  padding: 8px 0;
+  background: rgb(0 0 0 / 90%);
+  backdrop-filter: blur(10px);
+  border: 1px solid rgb(255 255 255 / 20%);
+  .more-actions-menu {
+    .menu-item {
+      display: flex;
+      gap: 8px;
+      align-items: center;
+      padding: 10px 16px;
+      font-size: 14px;
+      color: #ffffff;
+      cursor: pointer;
+      transition: background 0.3s;
+      .el-icon {
+        font-size: 18px;
+      }
+    }
+  }
+}
+.my-dynamic-container {
+  min-height: calc(100vh - 120px);
+  padding: 20px;
+  background: #ffffff;
+
+  // 用户信息卡片
+  .user-card {
+    padding: 24px;
+    margin-bottom: 20px;
+    background: #ffffff;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    .user-header {
+      margin-bottom: 16px;
+      .user-avatar-section {
+        display: flex;
+        gap: 16px;
+        align-items: center;
+        .user-avatar-large {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 80px;
+          height: 80px;
+          overflow: hidden;
+          background: #f5f7fa;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+        .user-info-text {
+          flex: 1;
+          .user-name {
+            margin-bottom: 8px;
+            font-size: 20px;
+            font-weight: 600;
+            color: #303133;
+          }
+          .user-stats {
+            display: flex;
+            gap: 8px;
+            align-items: center;
+            font-size: 14px;
+            color: #606266;
+            .stat-item {
+              cursor: pointer;
+              transition: color 0.3s;
+              &:hover {
+                color: #409eff;
+              }
+            }
+            .stat-divider {
+              color: #dcdfe6;
+            }
+          }
+        }
+      }
+    }
+    .user-bio {
+      font-size: 14px;
+      line-height: 1.6;
+      color: #606266;
+    }
+  }
+
+  // 标签页区域
+  .tabs-section {
+    margin-bottom: 20px;
+    border-bottom: 1px solid #e4e7ed;
+    :deep(.el-tabs) {
+      .el-tabs__header {
+        margin-bottom: 0;
+      }
+      .el-tabs__nav-wrap::after {
+        display: none;
+      }
+    }
+  }
+
+  // 内容区域
+  .content-section {
+    margin-top: 20px;
+  }
+
+  // 内容网格布局
+  .content-grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 16px;
+    margin-bottom: 20px;
+    .draft-card {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      height: 255px;
+      padding: 20px;
+      text-align: center;
+      cursor: pointer;
+      background: #f5f7fa;
+      border: 1px dashed #dcdfe6;
+      border-radius: 8px;
+      transition: all 0.3s;
+      &:hover {
+        background: #ecf5ff;
+        border-color: #409eff;
+        .el-icon {
+          color: #409eff !important;
+        }
+      }
+      .draft-title {
+        margin-top: 12px;
+        font-size: 16px;
+        font-weight: 500;
+        color: #303133;
+      }
+      .draft-count {
+        margin-top: 8px;
+        font-size: 14px;
+        color: #909399;
+      }
+    }
+
+    @media (width <= 1400px) {
+      grid-template-columns: repeat(3, 1fr);
+    }
+
+    @media (width <= 1024px) {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    @media (width <= 768px) {
+      grid-template-columns: repeat(1, 1fr);
+    }
+  }
+
+  // 内容卡片
+  .content-card {
+    cursor: pointer;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    transition: all 0.3s;
+    &:hover {
+      box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+      transform: translateY(-2px);
+      .content-cover-wrapper {
+        .play-overlay {
+          opacity: 1;
+        }
+      }
+    }
+
+    // 封面区域
+    .content-cover-wrapper {
+      position: relative;
+      width: 100%;
+      aspect-ratio: 16 / 9;
+      overflow: hidden;
+      background: #f5f7fa;
+      border-top-left-radius: 8px;
+      border-top-right-radius: 8px;
+      .content-cover {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      .cover-placeholder {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+        background: #f5f7fa;
+      }
+
+      // 播放按钮覆盖层
+      .play-overlay {
+        position: absolute;
+        inset: 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: rgb(0 0 0 / 30%);
+        opacity: 0;
+        transition: opacity 0.3s;
+        .el-icon {
+          filter: drop-shadow(0 2px 4px rgb(0 0 0 / 20%));
+        }
+      }
+
+      // 标题标签
+      .content-label {
+        position: absolute;
+        top: 8px;
+        left: 8px;
+        padding: 4px 12px;
+        font-size: 12px;
+        color: #ffffff;
+        background: rgb(0 0 0 / 60%);
+        backdrop-filter: blur(4px);
+        border-radius: 4px;
+      }
+    }
+
+    // 底部信息
+    .content-footer {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 0 4px;
+      .footer-left {
+        display: flex;
+        gap: 4px;
+        align-items: center;
+        padding-top: 5px;
+      }
+      .footer-right {
+        display: flex;
+        align-items: center;
+      }
+      .view-count {
+        font-size: 13px;
+        color: #666666;
+      }
+    }
+  }
+
+  // 空状态
+  .empty-section {
+    padding: 80px 0;
+    text-align: center;
+  }
+
+  // 详情 Drawer
+  :deep(.detail-drawer) {
+    .el-drawer__header {
+      position: absolute;
+      top: 0;
+      right: 0;
+      left: 0;
+      z-index: 10;
+      padding: 0;
+      margin: 0;
+      background: transparent;
+    }
+    .el-drawer__body {
+      padding: 0;
+      background: #000000;
+    }
+    .drawer-header {
+      padding: 20px;
+      .close-btn {
+        padding: 8px;
+        font-size: 24px;
+        color: #ffffff;
+        &:hover {
+          background: rgb(255 255 255 / 10%);
+        }
+      }
+    }
+    .detail-content {
+      position: relative;
+      display: flex;
+      width: 100%;
+      height: 100%;
+
+      // 主内容区域
+      .detail-main {
+        display: flex;
+        flex: 1;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 80px 120px 40px 40px;
+        .media-container {
+          display: flex;
+          flex: 1;
+          align-items: center;
+          justify-content: center;
+          width: 100%;
+          max-width: 800px;
+          .detail-media {
+            max-width: 100%;
+            max-height: 100%;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+          .media-placeholder {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 400px;
+            height: 400px;
+            background: rgb(255 255 255 / 5%);
+            border-radius: 8px;
+          }
+        }
+        .detail-info {
+          width: 100%;
+          max-width: 800px;
+          padding: 20px 0;
+          .author-info {
+            display: flex;
+            gap: 12px;
+            align-items: center;
+            margin-bottom: 16px;
+            .author-avatar {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              width: 40px;
+              height: 40px;
+              overflow: hidden;
+              background: rgb(255 255 255 / 10%);
+              border-radius: 50%;
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .author-details {
+              flex: 1;
+              .author-name {
+                margin-bottom: 4px;
+                font-size: 16px;
+                font-weight: 500;
+                color: #ffffff;
+              }
+              .publish-time {
+                font-size: 13px;
+                color: rgb(255 255 255 / 60%);
+              }
+            }
+          }
+          .detail-description {
+            font-size: 15px;
+            line-height: 1.6;
+            color: #ffffff;
+            p {
+              display: inline;
+              margin: 0;
+            }
+            .expand-btn {
+              margin-left: 4px;
+              color: rgb(255 255 255 / 60%);
+              cursor: pointer;
+              &:hover {
+                color: #ffffff;
+              }
+            }
+          }
+        }
+      }
+
+      // 右侧操作栏
+      .action-bar {
+        position: fixed;
+        right: 40px;
+        bottom: 100px;
+        z-index: 10;
+        display: flex;
+        flex-direction: column;
+        gap: 24px;
+        .action-item {
+          display: flex;
+          flex-direction: column;
+          gap: 6px;
+          align-items: center;
+          cursor: pointer;
+          transition: transform 0.3s;
+          &:hover {
+            transform: scale(1.1);
+          }
+          &.author-action {
+            cursor: default;
+            &:hover {
+              transform: none;
+            }
+          }
+          .action-avatar {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 48px;
+            height: 48px;
+            overflow: hidden;
+            background: rgb(255 255 255 / 20%);
+            border: 2px solid #ffffff;
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          .action-icon {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 48px;
+            height: 48px;
+            background: rgb(0 0 0 / 50%);
+            backdrop-filter: blur(10px);
+            border-radius: 50%;
+          }
+          .action-count {
+            font-size: 13px;
+            color: #ffffff;
+            text-align: center;
+            text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
+          }
+        }
+      }
+    }
+  }
+
+  // 更多操作 Popover
+  :deep(.more-actions-popover) {
+    min-width: 120px;
+    padding: 8px 0;
+    background: rgb(0 0 0 / 90%);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgb(255 255 255 / 10%);
+    .el-popper__arrow::before {
+      background: rgb(0 0 0 / 90%);
+      border: 1px solid rgb(255 255 255 / 10%);
+    }
+    .more-actions-menu {
+      .menu-item {
+        display: flex;
+        gap: 12px;
+        align-items: center;
+        padding: 12px 16px;
+        font-size: 14px;
+        color: #ffffff;
+        cursor: pointer;
+        transition: all 0.3s;
+        .el-icon {
+          color: #ffffff;
+        }
+        span {
+          flex: 1;
+        }
+      }
+    }
+  }
+
+  // 对话框样式(关系对话框 + 举报对话框)
+  :deep(.el-dialog) {
+    // 关系对话框
+    .relation-dialog-content {
+      .el-tabs {
+        margin-bottom: 16px;
+        .el-tabs__header {
+          margin-bottom: 16px;
+        }
+      }
+      .search-box {
+        margin-bottom: 16px;
+      }
+      .relation-list {
+        max-height: 400px;
+        overflow-y: auto;
+        .relation-item {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 12px 0;
+          border-bottom: 1px solid #f5f7fa;
+          &:last-child {
+            border-bottom: none;
+          }
+          .user-info-row {
+            display: flex;
+            flex: 1;
+            gap: 12px;
+            align-items: center;
+            .user-avatar-small {
+              display: flex;
+              flex-shrink: 0;
+              align-items: center;
+              justify-content: center;
+              width: 48px;
+              height: 48px;
+              overflow: hidden;
+              background: #f5f7fa;
+              border-radius: 50%;
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .user-details {
+              flex: 1;
+              min-width: 0;
+              .user-name-text {
+                margin-bottom: 4px;
+                font-size: 15px;
+                font-weight: 500;
+                color: #303133;
+              }
+              .user-desc {
+                overflow: hidden;
+                font-size: 13px;
+                color: #909399;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+            }
+          }
+          .action-button {
+            margin-left: 12px;
+            .el-button {
+              min-width: 80px;
+            }
+          }
+        }
+      }
+    }
+
+    // 举报对话框
+    .report-dialog-content {
+      .report-tip {
+        margin-bottom: 20px;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #606266;
+      }
+      .report-reasons {
+        margin-bottom: 20px;
+        .el-radio-group {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 12px 16px;
+          .el-radio {
+            height: auto;
+            margin-right: 0;
+            white-space: nowrap;
+            .el-radio__label {
+              font-size: 14px;
+              color: #303133;
+            }
+          }
+        }
+      }
+      .report-description {
+        margin-bottom: 20px;
+        :deep(.el-textarea__inner) {
+          font-size: 14px;
+        }
+      }
+      .report-upload {
+        margin-bottom: 20px;
+        .upload-title {
+          margin-bottom: 12px;
+          font-size: 14px;
+          font-weight: 500;
+          color: #303133;
+        }
+        :deep(.el-upload-list) {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+        }
+        :deep(.el-upload--picture-card) {
+          width: 100px;
+          height: 100px;
+          border-radius: 4px;
+        }
+        :deep(.el-upload-list--picture-card .el-upload-list__item) {
+          width: 100px;
+          height: 100px;
+          margin: 0;
+          border-radius: 4px;
+        }
+      }
+      .report-agreement {
+        .el-checkbox {
+          .el-checkbox__label {
+            font-size: 14px;
+            color: #606266;
+          }
+        }
+      }
+    }
+    .dialog-footer {
+      display: flex;
+      gap: 12px;
+      justify-content: flex-end;
+    }
+  }
+
+  // 评论侧边栏样式
+  .comment-list-container {
+    flex: 1;
+    height: calc(100vh - 200px);
+    padding: 0 20px;
+    overflow-y: auto;
+    .comment-list {
+      .comment-item {
+        display: flex;
+        gap: 12px;
+        padding: 16px 0;
+        border-bottom: 1px solid #f0f0f0;
+        &:last-child {
+          border-bottom: none;
+        }
+        .comment-avatar {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 40px;
+          height: 40px;
+          overflow: hidden;
+          background: #f5f5f5;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+        .comment-content-wrapper {
+          flex: 1;
+          min-width: 0;
+          .comment-header,
+          .store-comment-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 8px;
+            .comment-user-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: #303133;
+            }
+            .comment-time {
+              font-size: 12px;
+              color: #909399;
+            }
+          }
+          .comment-text {
+            margin-bottom: 8px;
+            font-size: 14px;
+            line-height: 1.6;
+            color: #606266;
+            word-break: break-all;
+          }
+
+          // 商家回复样式
+          .store-comment-wrapper {
+            padding: 12px;
+            margin-top: 12px;
+            background: #f5f7fa;
+            border-radius: 8px;
+            .store-comment-item {
+              display: flex;
+              gap: 10px;
+              .store-comment-avatar {
+                display: flex;
+                flex-shrink: 0;
+                align-items: center;
+                justify-content: center;
+                width: 32px;
+                height: 32px;
+                overflow: hidden;
+                background: #ffffff;
+                border-radius: 50%;
+                img {
+                  width: 100%;
+                  height: 100%;
+                  object-fit: cover;
+                }
+              }
+              .store-comment-content {
+                flex: 1;
+                min-width: 0;
+                .store-comment-header {
+                  display: flex;
+                  align-items: center;
+                  justify-content: space-between;
+                  margin-bottom: 6px;
+                  .store-comment-user-name {
+                    font-size: 13px;
+                    font-weight: 500;
+                    color: #409eff;
+                  }
+                  .store-comment-time {
+                    font-size: 11px;
+                    color: #909399;
+                  }
+                }
+                .store-comment-text {
+                  display: flex;
+                  align-items: center;
+                  justify-content: space-between;
+                  font-size: 13px;
+                  line-height: 1.5;
+                  color: #606266;
+                  word-break: break-all;
+                }
+              }
+            }
+          }
+          .comment-actions {
+            display: flex;
+            gap: 20px;
+            .comment-action-item {
+              display: flex;
+              gap: 4px;
+              align-items: center;
+              font-size: 13px;
+              color: #909399;
+              cursor: pointer;
+              transition: color 0.3s;
+              &:hover {
+                color: #409eff;
+              }
+              span {
+                font-size: 13px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  .comment-input-wrapper {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    padding: 16px 20px;
+    background: #ffffff;
+    border-top: 1px solid #e4e7ed;
+    box-shadow: 0 -2px 8px rgb(0 0 0 / 5%);
+    .reply-hint {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 8px 12px;
+      margin-bottom: 8px;
+      background: #f5f7fa;
+      border-radius: 4px;
+      .reply-text {
+        font-size: 13px;
+        color: #409eff;
+      }
+      .cancel-reply {
+        font-size: 16px;
+        color: #909399;
+        cursor: pointer;
+        transition: color 0.3s;
+        &:hover {
+          color: #f56c6c;
+        }
+      }
+    }
+    :deep(.el-textarea) {
+      margin-bottom: 12px;
+    }
+    .el-button {
+      width: 100%;
+    }
+  }
+
+  // 分享对话框
+  .share-dialog-content {
+    .search-box {
+      margin-bottom: 16px;
+    }
+    .share-friend-list {
+      max-height: 400px;
+      margin-bottom: 16px;
+      overflow-y: auto;
+      .share-friend-item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12px;
+        cursor: pointer;
+        border-radius: 8px;
+        transition: background-color 0.3s;
+        &:hover {
+          background-color: #f5f7fa;
+        }
+        .friend-info {
+          display: flex;
+          gap: 12px;
+          align-items: center;
+          .friend-avatar {
+            display: flex;
+            flex-shrink: 0;
+            align-items: center;
+            justify-content: center;
+            width: 40px;
+            height: 40px;
+            overflow: hidden;
+            background: #f5f5f5;
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          .friend-name {
+            font-size: 14px;
+            font-weight: 500;
+            color: #303133;
+          }
+        }
+      }
+    }
+    .selected-friends {
+      padding: 12px;
+      background: #f5f7fa;
+      border-radius: 8px;
+      .selected-title {
+        margin-bottom: 8px;
+        font-size: 13px;
+        color: #606266;
+      }
+      .selected-list {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        .el-tag {
+          max-width: 120px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+}
+</style>

+ 985 - 0
src/views/dynamicManagement/publishDynamic.vue

@@ -0,0 +1,985 @@
+<template>
+  <div class="publish-dynamic-container">
+    <!-- 头部导航 -->
+    <div class="header-section">
+      <el-button @click="handleGoBack" class="back-btn"> 返回 </el-button>
+      <div class="header-title">动态发布</div>
+    </div>
+    <!-- 表单内容 -->
+    <div class="form-container">
+      <el-form ref="formRef" :model="formData" :rules="rules" label-position="top">
+        <!-- 图片/视频上传 -->
+        <el-form-item label="图片/视频" prop="images">
+          <div class="upload-section">
+            <el-upload
+              v-model:file-list="fileList"
+              list-type="picture-card"
+              :limit="20"
+              :on-preview="handlePicturePreview"
+              :on-remove="handleRemoveImage"
+              :on-change="handleFileChange"
+              :before-upload="beforeImageUpload"
+              :http-request="handleImageUpload"
+              accept="image/*,video/mp4,video/*"
+              multiple
+              class="dynamic-upload"
+            >
+              <template #file="{ file }">
+                <div class="upload-file-preview">
+                  <!-- 视频缩略图 -->
+                  <video v-if="isVideoFile(file)" :src="file.url" class="upload-video-thumb" />
+                  <!-- 图片缩略图 -->
+                  <img v-else :src="file.url" class="upload-image-thumb" />
+                  <!-- 操作按钮 -->
+                  <div class="upload-actions">
+                    <span class="upload-action-item" @click.stop="handlePicturePreview(file)">
+                      <el-icon :size="20"><ZoomIn /></el-icon>
+                    </span>
+                    <span class="upload-action-item" @click.stop="handleRemoveFile(file)">
+                      <el-icon :size="20"><Delete /></el-icon>
+                    </span>
+                  </div>
+                </div>
+              </template>
+              <div class="upload-trigger">
+                <el-icon :size="32" color="#999">
+                  <Plus />
+                </el-icon>
+                <div class="upload-count">({{ fileList.length }}/20)</div>
+              </div>
+            </el-upload>
+          </div>
+        </el-form-item>
+
+        <!-- 标题 -->
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="formData.title" placeholder="请输入标题" maxlength="50" show-word-limit size="large" />
+        </el-form-item>
+
+        <!-- 正文 -->
+        <el-form-item label="正文" prop="content">
+          <el-input
+            v-model="formData.content"
+            type="textarea"
+            placeholder="请输入正文"
+            :rows="8"
+            maxlength="500"
+            show-word-limit
+          />
+        </el-form-item>
+
+        <!-- 位置 -->
+        <el-form-item label="位置" prop="location">
+          <el-select
+            v-model="formData.storePosition"
+            filterable
+            placeholder="请输入地址进行查询"
+            remote
+            reserve-keyword
+            :remote-method="getLonAndLat"
+            @change="selectAddress"
+          >
+            <el-option v-for="item in addressList" :key="item.id" :label="item.name" :value="item.location">
+              <span style="float: left">{{ item.name }}</span>
+              <span style="float: right; font-size: 13px; color: var(--el-text-color-secondary)">{{ item.district }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div class="footer-actions">
+        <el-button size="large" class="draft-btn" @click="handleSaveDraft"> 保存草稿 </el-button>
+        <el-button type="primary" size="large" class="publish-btn" :loading="publishing" @click="handlePublish"> 发布 </el-button>
+      </div>
+    </div>
+
+    <!-- 图片/视频预览对话框 -->
+    <el-dialog v-model="previewDialogVisible" width="800px" append-to-body>
+      <!-- 视频预览 -->
+      <video v-if="previewIsVideo" :src="previewImageUrl" controls style="width: 100%; max-height: 70vh" />
+      <!-- 图片预览 -->
+      <img v-else :src="previewImageUrl" alt="预览图片" style="width: 100%" />
+    </el-dialog>
+
+    <!-- 位置选择对话框 -->
+    <el-dialog v-model="locationDialogVisible" title="选择位置" width="500px" append-to-body>
+      <el-input v-model="locationSearch" placeholder="搜索位置" clearable>
+        <template #prefix>
+          <el-icon>
+            <Search />
+          </el-icon>
+        </template>
+      </el-input>
+      <div class="location-list">
+        <div v-for="location in locationList" :key="location.id" class="location-item" @click="handleChooseLocation(location)">
+          <el-icon>
+            <Location />
+          </el-icon>
+          <span>{{ location.name }}</span>
+        </div>
+        <el-empty v-if="locationList.length === 0" description="暂无位置信息" />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="publishDynamic">
+import { ref, reactive, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { ArrowLeft, Plus, Location, Search, ZoomIn, Delete } from "@element-plus/icons-vue";
+import type { FormInstance, FormRules, UploadUserFile, UploadFile } from "element-plus";
+// import { publishDynamic, saveDraft, uploadDynamicImage } from "@/api/modules/dynamicManagement";
+import { addOrUpdateDynamic } from "@/api/modules/dynamicManagement";
+import { uploadImg } from "@/api/modules/upload";
+import { useUserStore } from "@/stores/modules/user";
+import { getUserDraftDynamics, getInputPrompt } from "@/api/modules/newLoginApi";
+
+const router = useRouter();
+const route = useRoute();
+const userStore = useUserStore();
+
+// 接口定义
+interface FormData {
+  title: string;
+  content: string;
+  images: string[];
+  location: string;
+  locationId?: string;
+  address?: string; // 经纬度
+  addressProvince?: string; // 省市
+  storePosition: string;
+  queryAddress: string;
+  storePositionLongitude: string;
+  storePositionLatitude: string;
+}
+
+interface LocationItem {
+  id: string;
+  name: string;
+  address: string;
+  latitude?: string; // 纬度
+  longitude?: string; // 经度
+  province?: string; // 省市
+}
+
+// 响应式数据
+const formRef = ref<FormInstance>();
+const fileList = ref<UploadUserFile[]>([]);
+const previewDialogVisible = ref(false);
+const previewImageUrl = ref("");
+const previewIsVideo = ref(false); // 预览的是否为视频
+const locationDialogVisible = ref(false);
+const locationSearch = ref("");
+const locationList = ref<LocationItem[]>([]);
+const publishing = ref(false);
+const pendingUploadFiles = ref<UploadFile[]>([]);
+const uploading = ref(false);
+const currentDraftId = ref<number | string | null>(null); // 当前编辑的草稿ID
+const isEditMode = ref(false); // 是否为编辑模式
+
+// 表单数据
+const formData = reactive<FormData>({
+  title: "",
+  content: "",
+  images: [],
+  location: "",
+  address: "", // 经纬度
+  addressProvince: "", // 省市
+  storePosition: "",
+  queryAddress: "",
+  storePositionLongitude: "",
+  storePositionLatitude: ""
+});
+// 经纬度查询
+const addressMap = new Map();
+const addressList = ref<any[]>([]);
+const getLonAndLat = async (keyword: string) => {
+  if (keyword) {
+    console.log("地址查询", keyword);
+    let param = {
+      addressName: keyword
+    };
+    let res: any = await getInputPrompt(param as any);
+    if (res.code == "200") {
+      res.data.tips.forEach((item: any) => {
+        addressMap.set(item.location, item.name);
+      });
+      addressList.value = res?.data?.tips || [];
+      console.log("res", res);
+    } else {
+      ElMessage.error("新增失败!");
+    }
+  } else {
+    addressList.value = [];
+  }
+};
+const selectAddress = async (param: any) => {
+  if (!formData.storePosition || typeof formData.storePosition !== "string") {
+    ElMessage.warning("地址格式不正确,请重新选择");
+    return;
+  }
+  if (!formData.storePosition.includes(",")) {
+    ElMessage.warning("地址格式不正确,缺少经纬度信息");
+    return;
+  }
+  // 安全地分割地址字符串
+  let locationList = formData.storePosition.split(",");
+  // 查找对应的地址名称
+  addressList.value.forEach((item: any) => {
+    console.log(item);
+    if (item.location == formData.storePosition) {
+      formData.queryAddress = item.name;
+      formData.addressProvince = item.district;
+    }
+  });
+  formData.storePositionLongitude = locationList[0]?.trim();
+  formData.storePositionLatitude = locationList[1]?.trim();
+};
+
+// 表单验证规则
+const rules = reactive<FormRules<FormData>>({
+  title: [{ required: true, message: "请输入标题", trigger: "blur" }],
+  content: [{ required: true, message: "请输入正文", trigger: "blur" }]
+});
+
+// 返回上一页
+const handleGoBack = async () => {
+  // 检查是否有未保存的内容
+  if (formData.title || formData.content || fileList.value.length > 0) {
+    try {
+      await ElMessageBox.confirm("当前有未保存的内容,确定要离开吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      });
+      router.back();
+    } catch {
+      // 用户取消
+    }
+  } else {
+    router.back();
+  }
+};
+
+// 文件改变
+const handleFileChange = (uploadFile: UploadFile, uploadFiles: UploadFile[]) => {
+  // 验证文件
+  if (uploadFile.raw) {
+    const isImage = uploadFile.raw.type.startsWith("image/");
+    const isVideo = uploadFile.raw.type.startsWith("video/");
+    const isValidType = isImage || isVideo;
+
+    if (!isValidType) {
+      const index = uploadFiles.findIndex(f => f.uid === uploadFile.uid);
+      if (index > -1) {
+        uploadFiles.splice(index, 1);
+      }
+      ElMessage.warning("只能上传图片或视频文件");
+      return;
+    }
+
+    // 根据文件类型设置不同的大小限制
+    const maxSize = isVideo ? 100 : 20;
+    const isLtMaxSize = uploadFile.raw.size / 1024 / 1024 < maxSize;
+    if (!isLtMaxSize) {
+      const index = uploadFiles.findIndex(f => f.uid === uploadFile.uid);
+      if (index > -1) {
+        uploadFiles.splice(index, 1);
+      }
+      ElMessage.warning(`${isVideo ? "视频" : "图片"}大小不能超过 ${maxSize}MB`);
+      return;
+    }
+  }
+
+  // 为视频文件创建临时预览URL
+  if (uploadFile.raw && uploadFile.raw.type.startsWith("video/") && !uploadFile.url) {
+    uploadFile.url = URL.createObjectURL(uploadFile.raw);
+    console.log("为视频创建临时URL:", uploadFile.url);
+  }
+
+  // 添加到待上传队列
+  const existingIndex = fileList.value.findIndex(f => f.uid === uploadFile.uid);
+  if (existingIndex === -1 && uploadFile.status) {
+    fileList.value.push(uploadFile as UploadUserFile);
+  }
+
+  const readyFiles = fileList.value.filter(file => file.status === "ready");
+  if (readyFiles.length) {
+    readyFiles.forEach(file => {
+      if (!pendingUploadFiles.value.some(item => item.uid === file.uid)) {
+        pendingUploadFiles.value.push(file as UploadFile);
+      }
+    });
+  }
+  processUploadQueue();
+};
+
+// 处理上传队列
+const processUploadQueue = async () => {
+  if (uploading.value || pendingUploadFiles.value.length === 0) {
+    return;
+  }
+  const file = pendingUploadFiles.value.shift();
+  if (file) {
+    await uploadSingleFile(file);
+    processUploadQueue();
+  }
+};
+
+// 单个文件上传
+const uploadSingleFile = async (file: UploadFile) => {
+  if (!file.raw) {
+    return;
+  }
+
+  const rawFile = file.raw as File;
+  const uploadFormData = new FormData();
+  uploadFormData.append("file", rawFile);
+
+  file.status = "uploading";
+  file.percentage = 0;
+  uploading.value = true;
+
+  try {
+    console.log("开始上传文件:", rawFile.name, "类型:", rawFile.type);
+
+    // 图片和视频都使用同一个上传接口
+    const result: any = await uploadImg(uploadFormData);
+
+    console.log("上传接口返回:", result);
+
+    if (result?.code === 200 && result.data) {
+      let fileUrl = "";
+
+      // 处理不同的返回格式
+      if (Array.isArray(result.data) && result.data.length > 0) {
+        fileUrl = result.data[0]; // 数组格式
+      } else if (typeof result.data === "string") {
+        fileUrl = result.data; // 字符串格式
+      } else if (result.data.url) {
+        fileUrl = result.data.url; // 对象格式
+      }
+
+      if (fileUrl) {
+        file.status = "success";
+        file.percentage = 100;
+        file.url = fileUrl;
+        file.response = { url: fileUrl };
+
+        console.log("上传成功,文件URL:", fileUrl);
+
+        // 添加到 formData.images
+        if (!formData.images.includes(fileUrl)) {
+          formData.images.push(fileUrl);
+          console.log("添加到 images 数组,当前长度:", formData.images.length);
+        }
+      } else {
+        throw new Error("上传接口返回数据格式错误");
+      }
+    } else {
+      throw new Error(result?.msg || "文件上传失败");
+    }
+  } catch (error: any) {
+    console.error("上传失败:", error);
+    file.status = "fail";
+    if (file.url && file.url.startsWith("blob:")) {
+      URL.revokeObjectURL(file.url);
+    }
+    const index = fileList.value.findIndex(f => f.uid === file.uid);
+    if (index > -1) {
+      fileList.value.splice(index, 1);
+    }
+    ElMessage.error(error?.message || "文件上传失败");
+  } finally {
+    uploading.value = false;
+    fileList.value = [...fileList.value];
+  }
+};
+
+// 图片上传前验证
+const beforeImageUpload = (file: File) => {
+  const isImage = file.type.startsWith("image/");
+  const isVideo = file.type.startsWith("video/");
+  const isValidType = isImage || isVideo;
+
+  // 图片和视频使用不同的大小限制
+  const maxSize = isVideo ? 100 : 20; // 视频100MB,图片20MB
+  const isLtMaxSize = file.size / 1024 / 1024 < maxSize;
+
+  if (!isValidType) {
+    ElMessage.error("只能上传图片或视频文件!");
+    return false;
+  }
+  if (!isLtMaxSize) {
+    ElMessage.error(`${isVideo ? "视频" : "图片"}大小不能超过 ${maxSize}MB!`);
+    return false;
+  }
+  return true;
+};
+
+// 自定义上传
+const handleImageUpload = async (options: any) => {
+  // 上传逻辑已在 uploadSingleFile 中处理
+  return;
+};
+
+// 判断文件是否为视频
+const isVideoFile = (file: any) => {
+  const fileName = file.name || file.url || "";
+  return fileName.toLowerCase().endsWith(".mp4") || file.raw?.type?.startsWith("video/") || false;
+};
+
+// 图片/视频预览
+const handlePicturePreview = (uploadFile: UploadUserFile) => {
+  previewImageUrl.value = uploadFile.url!;
+  // 判断是否为视频文件
+  previewIsVideo.value = isVideoFile(uploadFile);
+  previewDialogVisible.value = true;
+};
+
+// 移除图片/视频(组件回调)
+const handleRemoveImage = (uploadFile: UploadUserFile, uploadFiles: UploadUserFile[]) => {
+  console.log("组件回调删除文件:", uploadFile);
+
+  // 从 formData.images 中移除对应的 URL
+  const fileUrl = uploadFile.url || (uploadFile.response as any)?.url;
+  if (fileUrl) {
+    // 清理 blob URL
+    if (fileUrl.startsWith("blob:")) {
+      URL.revokeObjectURL(fileUrl);
+    }
+
+    const index = formData.images.findIndex(url => url === fileUrl);
+    if (index > -1) {
+      formData.images.splice(index, 1);
+    }
+  }
+};
+
+// 手动移除文件(点击自定义删除按钮)
+const handleRemoveFile = (file: UploadUserFile) => {
+  console.log("删除文件:", file);
+
+  // 从 formData.images 中移除对应的 URL
+  const fileUrl = file.url || (file.response as any)?.url;
+  console.log("文件URL:", fileUrl);
+
+  if (fileUrl) {
+    // 清理 blob URL
+    if (fileUrl.startsWith("blob:")) {
+      URL.revokeObjectURL(fileUrl);
+      console.log("清理临时URL");
+    }
+
+    const index = formData.images.findIndex(url => url === fileUrl);
+    console.log("在 images 数组中的索引:", index);
+    if (index > -1) {
+      formData.images.splice(index, 1);
+    }
+  }
+
+  // 从 fileList 中移除
+  const fileIndex = fileList.value.findIndex(f => f.uid === file.uid);
+  console.log("在 fileList 中的索引:", fileIndex);
+  if (fileIndex > -1) {
+    fileList.value.splice(fileIndex, 1);
+  }
+
+  console.log("删除后 fileList 长度:", fileList.value.length);
+  console.log("删除后 images 长度:", formData.images.length);
+};
+
+// 选择位置
+const handleChooseLocation = (location: LocationItem) => {
+  formData.location = location.name;
+  formData.locationId = location.id;
+
+  // 保存经纬度和省市信息(如果有)
+  if (location.longitude && location.latitude) {
+    formData.address = `${location.longitude},${location.latitude}`;
+  }
+  if (location.province) {
+    formData.addressProvince = location.province;
+  }
+
+  console.log("选择位置:", {
+    name: formData.location,
+    address: formData.address,
+    province: formData.addressProvince
+  });
+
+  locationDialogVisible.value = false;
+};
+
+// 保存草稿
+const handleSaveDraft = async () => {
+  if (!formData.title && !formData.content && fileList.value.length === 0) {
+    ElMessage.warning("请至少填写标题或正文");
+    return;
+  }
+
+  // 检查是否有图片/视频正在上传
+  if (uploading.value || pendingUploadFiles.value.length > 0) {
+    ElMessage.warning("图片/视频正在上传中,请稍候...");
+    return;
+  }
+
+  try {
+    // 获取当前用户信息
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+    const createId = userStore.userInfo?.id || userStore.userInfo?.userId || 0;
+
+    // 调用保存草稿接口
+    const params: any = {
+      title: formData.title || "未命名",
+      context: formData.content || "", // 正文
+      imagePath: formData.images.join(","), // 多个图片/视频用逗号分隔
+      phoneId: currentUserPhoneId, // 店铺ID
+      createId: createId, // 创建者ID
+      type: "2", // 动态类型
+      draft: 1, // ✅ 1表示草稿
+      addressName: formData.queryAddress, // 地址名称
+      address: formData.storePositionLongitude + "," + formData.storePositionLatitude, // 经纬度
+      addressProvince: formData.addressProvince || "" // 省市
+    };
+
+    // 如果是编辑模式,传递ID
+    if (isEditMode.value && currentDraftId.value) {
+      params.id = currentDraftId.value;
+    }
+
+    await addOrUpdateDynamic(params);
+
+    ElMessage.success("草稿保存成功");
+    router.back();
+  } catch (error) {
+    console.error("保存草稿失败:", error);
+    ElMessage.error("保存草稿失败");
+  }
+};
+
+// 发布动态
+const handlePublish = async () => {
+  if (!formRef.value) return;
+
+  await formRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      // 检查是否有图片正在上传
+      if (uploading.value || pendingUploadFiles.value.length > 0) {
+        ElMessage.warning("图片/视频正在上传中,请稍候...");
+        return;
+      }
+
+      // 检查是否有上传的文件
+      if (formData.images.length === 0) {
+        ElMessage.warning("请至少上传一张图片或一个视频");
+        return;
+      }
+
+      publishing.value = true;
+
+      try {
+        // 获取当前用户信息
+        const phone = userStore.userInfo?.phone || "";
+        const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+        const createId = userStore.userInfo?.id || userStore.userInfo?.userId || 0;
+
+        // 调用发布动态接口
+        const params: any = {
+          title: formData.title,
+          context: formData.content, // 正文
+          imagePath: formData.images.join(","), // 多个图片/视频用逗号分隔
+          phoneId: currentUserPhoneId, // 店铺ID
+          createId: createId, // 创建者ID
+          type: "2", // 动态类型
+          draft: 0, // 0表示发布
+          addressName: formData.queryAddress, // 地址名称
+          address: formData.storePositionLongitude + "," + formData.storePositionLatitude, // 经纬度
+          addressProvince: formData.addressProvince || "" // 省市
+        };
+
+        // 如果是编辑模式,传递ID
+        if (isEditMode.value && currentDraftId.value) {
+          params.id = currentDraftId.value;
+        }
+
+        await addOrUpdateDynamic(params);
+
+        ElMessage.success("发布成功");
+        router.back();
+      } catch (error) {
+        console.error("发布失败:", error);
+        ElMessage.error("发布失败");
+      } finally {
+        publishing.value = false;
+      }
+    }
+  });
+};
+
+// 加载草稿数据
+const loadDraftData = async (draftId: string | number) => {
+  try {
+    console.log("加载草稿数据,ID:", draftId);
+
+    // 获取当前用户的手机号
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 调用接口获取草稿列表
+    const res: any = await getUserDraftDynamics({
+      phoneId: phoneId,
+      page: 1,
+      size: 1000
+    });
+
+    if (res.code === 200) {
+      const dataList = res.data?.records || res.data?.list || [];
+
+      // 查找指定的草稿
+      const draft = dataList.find((item: any) => item.id == draftId);
+
+      if (draft) {
+        console.log("找到草稿数据:", draft);
+
+        // 回显标题
+        formData.title = draft.title || "";
+
+        // 回显正文
+        formData.content = draft.context || "";
+
+        // 回显位置
+        formData.location = draft.addressName || "";
+        formData.address = draft.address || "";
+        formData.addressProvince = draft.addressProvince || "";
+        formData.storePosition = draft.address || ""; // 回显到下拉框(经纬度格式)
+        formData.queryAddress = draft.addressName || ""; // 回显地址名称
+        formData.storePositionLongitude = draft.address ? draft.address.split(",")[0]?.trim() : "";
+        formData.storePositionLatitude = draft.address ? draft.address.split(",")[1]?.trim() : "";
+
+        // 如果有地址,需要将其添加到 addressList 中以便下拉框显示
+        if (draft.address && draft.addressName) {
+          addressList.value = [
+            {
+              id: draft.id || Date.now(),
+              name: draft.addressName,
+              location: draft.address,
+              district: draft.addressProvince || ""
+            }
+          ];
+        }
+
+        // 回显图片/视频
+        if (draft.imagePath) {
+          const imageUrls = draft.imagePath.split(",").filter((url: string) => url.trim());
+          formData.images = imageUrls;
+
+          // 构建文件列表用于显示
+          fileList.value = imageUrls.map((url: string, index: number) => {
+            const isVideo = url.toLowerCase().endsWith(".mp4");
+            return {
+              uid: Date.now() + index,
+              name: isVideo ? `video_${index + 1}.mp4` : `image_${index + 1}.jpg`,
+              status: "success" as const,
+              url: url,
+              response: { url: url }
+            };
+          });
+
+          console.log("回显图片/视频列表:", fileList.value);
+        }
+
+        ElMessage.success("草稿数据加载成功");
+      } else {
+        ElMessage.warning("未找到指定的草稿");
+      }
+    }
+  } catch (error) {
+    console.error("加载草稿数据失败:", error);
+    ElMessage.error("加载草稿数据失败");
+  }
+};
+
+// 初始化
+onMounted(() => {
+  // 检查是否为编辑草稿模式
+  const draftId = route.query.draftId;
+  const mode = route.query.mode;
+
+  if (draftId && mode === "edit") {
+    console.log("进入编辑草稿模式, draftId:", draftId);
+    isEditMode.value = true;
+    currentDraftId.value = draftId as string | number;
+    loadDraftData(draftId as string | number);
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.header-section {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 0;
+  border-bottom: 1px solid #e4e7ed;
+  .back-btn {
+    display: flex;
+    gap: 4px;
+    align-items: center;
+    padding: 8px 16px;
+    margin-left: 20px;
+    font-size: 15px;
+    color: #606266;
+    transition: all 0.3s;
+    &:hover {
+      color: #409eff;
+      background: #ecf5ff;
+    }
+  }
+  .header-title {
+    flex: 1;
+    font-size: 20px;
+    font-weight: 600;
+    color: #303133;
+    text-align: center;
+  }
+  .header-right {
+    width: 100px;
+  }
+}
+.publish-dynamic-container {
+  display: flex;
+  flex-direction: column;
+  background: #ffffff;
+
+  // 头部导航
+  .header-bar {
+    position: sticky;
+    top: 0;
+    z-index: 100;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 60px;
+    .header-left {
+      flex: 1;
+      .return-btn {
+        background: #ffffff;
+        border: 1px solid #dcdfe6;
+      }
+      :deep(.el-button) {
+        padding: 8px 0;
+        font-size: 16px;
+        color: #606266;
+        &:hover {
+          color: #409eff;
+        }
+        .el-icon {
+          margin-right: 4px;
+        }
+      }
+    }
+    .header-title {
+      flex: 1;
+      font-size: 18px;
+      font-weight: 600;
+      color: #303133;
+      text-align: center;
+    }
+    .header-right {
+      flex: 1;
+    }
+  }
+
+  // 表单容器
+  .form-container {
+    flex: 1;
+    width: 100%;
+    max-width: 800px;
+    padding: 30px 20px;
+    margin: 0 auto;
+    :deep(.el-form) {
+      .el-form-item {
+        margin-bottom: 15px;
+        .el-form-item__label {
+          padding-bottom: 5px;
+          font-size: 16px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+    }
+
+    // 图片上传区域
+    .upload-section {
+      :deep(.el-upload-list) {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+      }
+      :deep(.el-upload--picture-card) {
+        width: 138px;
+        height: 138px;
+        border: 1px dashed #dcdfe6;
+        border-radius: 8px;
+        transition: all 0.3s;
+        &:hover {
+          border-color: #409eff;
+        }
+        .upload-trigger {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          height: 100%;
+          .upload-count {
+            margin-top: 8px;
+            font-size: 14px;
+            color: #909399;
+          }
+        }
+      }
+      :deep(.el-upload-list--picture-card .el-upload-list__item) {
+        width: 138px;
+        height: 138px;
+        margin: 0;
+        border-radius: 8px;
+      }
+
+      // 自定义文件预览
+      .upload-file-preview {
+        position: relative;
+        width: 100%;
+        height: 100%;
+        overflow: hidden;
+        border-radius: 8px;
+        .upload-video-thumb,
+        .upload-image-thumb {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+        .upload-actions {
+          position: absolute;
+          top: 0;
+          left: 0;
+          z-index: 10;
+          display: flex;
+          gap: 8px;
+          align-items: center;
+          justify-content: center;
+          width: 100%;
+          height: 100%;
+          background: rgb(0 0 0 / 50%);
+          opacity: 0;
+          transition: opacity 0.3s;
+          .upload-action-item {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 36px;
+            height: 36px;
+            color: #ffffff;
+            cursor: pointer;
+            background: rgb(0 0 0 / 50%);
+            border-radius: 50%;
+            transition: transform 0.2s;
+            &:hover {
+              transform: scale(1.1);
+            }
+          }
+        }
+        &:hover .upload-actions {
+          opacity: 1;
+        }
+      }
+    }
+
+    // 输入框样式
+    :deep(.el-input__inner),
+    :deep(.el-textarea__inner) {
+      border-radius: 8px;
+    }
+    :deep(.el-textarea__inner) {
+      padding: 12px 15px;
+      font-size: 15px;
+      line-height: 1.6;
+    }
+  }
+
+  // 底部操作按钮
+  .footer-actions {
+    display: flex;
+    justify-content: center;
+    .el-button {
+      min-width: 150px;
+      height: 45px;
+      margin-top: 20px;
+      font-size: 16px;
+      border-radius: 8px;
+    }
+    .draft-btn {
+      color: #606266;
+      background: #f5f7fa;
+      border-color: #dcdfe6;
+      &:hover {
+        color: #409eff;
+        background: #ecf5ff;
+        border-color: #409eff;
+      }
+    }
+    .publish-btn {
+      background: #409eff;
+      border-color: #409eff;
+    }
+  }
+
+  // 位置选择对话框
+  :deep(.el-dialog) {
+    border-radius: 12px;
+    .location-list {
+      max-height: 400px;
+      margin-top: 16px;
+      overflow-y: auto;
+      .location-item {
+        display: flex;
+        gap: 12px;
+        align-items: center;
+        padding: 12px 16px;
+        cursor: pointer;
+        border-radius: 8px;
+        transition: all 0.3s;
+        &:hover {
+          background: #f5f7fa;
+        }
+        .el-icon {
+          font-size: 18px;
+          color: #909399;
+        }
+        span {
+          font-size: 15px;
+          color: #303133;
+        }
+      }
+    }
+  }
+}
+
+// 响应式适配
+@media (width <= 768px) {
+  .publish-dynamic-container {
+    .form-container {
+      padding: 20px 16px 100px;
+    }
+    .footer-actions {
+      padding: 12px 16px;
+      .el-button {
+        min-width: 140px;
+        height: 44px;
+        font-size: 15px;
+      }
+    }
+  }
+}
+</style>

+ 689 - 0
src/views/dynamicManagement/reviewAppeal.vue

@@ -0,0 +1,689 @@
+<template>
+  <div class="review-appeal-container">
+    <!-- 顶部统计数据 -->
+    <div class="statistics-section">
+      <div class="statistics-cards">
+        <div class="stat-card">
+          <div class="stat-label">评价总数</div>
+          <div class="stat-value">
+            {{ total }}
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-label">新增差评数</div>
+          <div class="stat-value">
+            {{ statistics.badTextReviews }}
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-label">新增好评数</div>
+          <div class="stat-value">
+            {{ statistics.badImageReviews }}
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-label">差评中评数</div>
+          <div class="stat-value">
+            {{ statistics.neutralReviews }}
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-label">评论回复率</div>
+          <div class="stat-value highlight">
+            {{ statistics.abnormalRate }}
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 评价列表区域 -->
+    <div class="review-list-section">
+      <div class="section-header">
+        <div class="section-title">
+          评价列表 <el-button type="primary" style="float: right" @click="goToAppealHistory"> 申诉历史 </el-button>
+        </div>
+        <div class="time-filter">
+          <span>评论时间:</span>
+          <el-select v-model="timeFilter" placeholder="请选择" class="time-filter-select" @change="handleTimeFilterChange">
+            <el-option label="全部" value="" />
+            <el-option label="30天" value="30" />
+          </el-select>
+        </div>
+        <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+          <el-tab-pane :label="`全部 (${tabCounts.all})`" name="0" />
+          <el-tab-pane :label="`待回复差评 (${tabCounts.pending})`" name="pending" />
+          <el-tab-pane :label="`差评 (${tabCounts.bad})`" name="3" />
+          <el-tab-pane :label="`好评 (${tabCounts.good})`" name="1" />
+          <el-tab-pane :label="`中评 (${tabCounts.neutral})`" name="2" />
+        </el-tabs>
+      </div>
+
+      <!-- 评论卡片列表 -->
+      <div v-if="reviewList.length > 0" class="review-cards">
+        <div v-for="review in reviewList" :key="review.id" class="review-card">
+          <div class="review-header">
+            <div class="user-info">
+              <el-avatar :src="review.userAvatar" :size="40">
+                <el-icon><User /></el-icon>
+              </el-avatar>
+              <div class="user-details">
+                <div class="user-name">
+                  {{ review.isAnonymous == 1 || !review.userName ? "匿名用户" : review.userName }}
+                </div>
+                <el-rate v-model="review.score" disabled />
+              </div>
+            </div>
+            <div class="review-time">
+              {{ review.createdTime.replace(/-/g, "/") }}
+            </div>
+          </div>
+
+          <div class="review-content">
+            {{ review.commentContent }}
+          </div>
+
+          <div v-for="(itm, idx) in review.storeComment" :key="idx">
+            <div class="sjhf">商家回复: {{ itm.commentContent }}</div>
+          </div>
+
+          <div v-if="review.images && review.images.length > 0" class="review-images">
+            <el-image
+              v-for="(img, index) in review.images"
+              :key="index"
+              :src="img"
+              :preview-src-list="review.images"
+              fit="cover"
+              class="review-image"
+            />
+          </div>
+
+          <div class="review-footer">
+            <el-button type="warning" link v-if="review.appealStatus == 0"> 审核中 </el-button>
+            <el-button
+              type="danger"
+              link
+              v-if="review.appealStatus != 0"
+              :disabled="review.appealFlag == 1 && review.appealStatus != 1 ? true : false"
+              @click="delReviewAppeal(review)"
+            >
+              申诉删除
+            </el-button>
+
+            <el-button type="primary" link @click="openReplayDialog(review)"> 回复 </el-button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 空状态 -->
+      <el-empty v-else description="暂无评论数据" />
+
+      <!-- 分页 -->
+      <div v-if="total > 0" class="pagination-wrapper">
+        <el-pagination
+          v-model:current-page="pageNum"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 30, 50]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          background
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+
+    <!-- 申诉提交对话框 -->
+    <el-dialog v-model="appealDialogVisible" title="申诉删除" width="600px" @close="closeAppealDialog">
+      <el-form ref="appealFormRef" :model="appealFormData" :rules="appealFormRules" label-width="100px">
+        <el-form-item label="申诉原因" prop="reason">
+          <el-input
+            v-model="appealFormData.reason"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入申诉原因"
+            maxlength="300"
+            show-word-limit
+          />
+        </el-form-item>
+
+        <el-form-item label="申诉凭证" prop="images">
+          <el-upload
+            v-model:file-list="appealFormData.fileList"
+            list-type="picture-card"
+            :limit="6"
+            :http-request="handleUpload"
+            :on-preview="handlePreview"
+            :on-remove="handleRemove"
+            :on-success="handleUploadSuccess"
+            accept="image/*"
+          >
+            <el-icon><Plus /></el-icon>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="closeAppealDialog"> 取消 </el-button>
+        <el-button type="primary" @click="submitAppeal"> 确定 </el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 回复对话框 -->
+    <el-dialog v-model="replyDialogVisible" title="回复评价" width="600px" @close="closeReplyDialog">
+      <el-form ref="replyFormRef" :model="replyFormData" :rules="replyFormRules" label-width="100px">
+        <el-form-item label="回复内容" prop="content">
+          <el-input
+            v-model="replyFormData.content"
+            type="textarea"
+            :rows="6"
+            placeholder="请输入回复内容"
+            maxlength="500"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="closeReplyDialog"> 取消 </el-button>
+        <el-button type="primary" @click="submitReply"> 提交回复 </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="reviewAppeal">
+import { ref, reactive, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
+import { User, Plus } from "@element-plus/icons-vue";
+import type { FormInstance, FormRules, UploadUserFile } from "element-plus";
+import { getList, addAppealNew, saveComment, uploadImg } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils";
+import { useUserStore } from "@/stores/modules/user";
+
+const router = useRouter();
+const userStore = useUserStore();
+
+// 店铺名称
+const storeName = ref("重庆老火锅");
+
+// 统计数据
+const statistics = reactive({
+  totalReviews: 0,
+  badTextReviews: 0,
+  badImageReviews: 0,
+  neutralReviews: 0,
+  abnormalRate: "0%"
+});
+
+// 标签页计数
+const tabCounts = reactive({
+  all: 22,
+  pending: 2,
+  bad: 5,
+  good: 10,
+  neutral: 7
+});
+
+// 当前激活的标签
+const activeTab = ref("0");
+
+// 评论时间筛选
+const timeFilter = ref("");
+
+// 评论列表
+const reviewList = ref<any[]>([]);
+
+// 分页参数
+const pageNum = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+
+// 申诉提交对话框
+const appealDialogVisible = ref(false);
+const appealFormRef = ref<FormInstance>();
+const currentReviewId = ref("");
+
+const appealFormData = reactive({
+  reason: "",
+  images: [] as string[],
+  files: [] as File[], // 保存原始的 File 对象
+  fileList: [] as UploadUserFile[]
+});
+
+const appealFormRules = reactive<FormRules>({
+  reason: [{ required: true, message: "请输入申诉原因", trigger: "blur" }],
+  images: [{ required: true, message: "请上传申诉凭证", trigger: "blur" }]
+});
+
+// 回复对话框
+const replyDialogVisible = ref(false);
+const replyFormRef = ref<FormInstance>();
+const currentReplyReview = ref<any>(null);
+
+const replyFormData = reactive({
+  content: ""
+});
+
+const replyFormRules = reactive<FormRules>({
+  content: [{ required: true, message: "请输入回复内容", trigger: "blur" }]
+});
+
+// 标签页切换
+const handleTabClick = (tab: any) => {
+  // 获取点击的标签页的 name 值
+  const tabName = tab.paneName || tab.props?.name || activeTab.value;
+  loadReviewList(tabName, true);
+};
+
+// 跳转到申诉历史
+const goToAppealHistory = () => {
+  router.push("/dynamicManagement/reviewAppealHistory");
+};
+
+// 评论时间筛选变化
+const handleTimeFilterChange = () => {
+  loadReviewList(activeTab.value, true);
+};
+
+// 加载统计数据(只在初始化时调用一次)
+const loadStatistics = async () => {
+  try {
+    const baseParams = {
+      pageNum: 1,
+      pageSize: 1,
+      phoneId: `store_${localGet("geeker-user").userInfo.phone}`,
+      businessType: 5,
+      days: timeFilter.value,
+      replyStatus: 0,
+      storeId: localGet("createdId"),
+      userType: 0
+    };
+
+    // 并行请求各个评论等级的数量
+    const [allRes, goodRes, neutralRes, badRes, pendingRes]: any[] = await Promise.all([
+      getList({ ...baseParams, commentLevel: 0 }), // 全部
+      getList({ ...baseParams, commentLevel: 1 }), // 好评
+      getList({ ...baseParams, commentLevel: 2 }), // 中评
+      getList({ ...baseParams, commentLevel: 3 }), // 差评
+      getList({ ...baseParams, commentLevel: 3, replyStatus: 2 }) // 待回复差评
+    ]);
+
+    // 更新标签页计数
+    tabCounts.all = allRes?.code === 200 ? allRes.data?.total || 0 : 0;
+    tabCounts.good = goodRes?.code === 200 ? goodRes.data?.total || 0 : 0;
+    tabCounts.neutral = neutralRes?.code === 200 ? neutralRes.data?.total || 0 : 0;
+    tabCounts.bad = badRes?.code === 200 ? badRes.data?.total || 0 : 0;
+    tabCounts.pending = pendingRes?.code === 200 ? pendingRes.data?.total || 0 : 0;
+
+    // 更新统计数据
+    statistics.totalReviews = tabCounts.all;
+    statistics.badTextReviews = tabCounts.bad; // 新增差评数
+    statistics.badImageReviews = tabCounts.good; // 新增好评数
+    statistics.neutralReviews = tabCounts.neutral; // 中评数
+
+    // 计算评论回复率 = (已回复评论数 ÷ 总评论数) × 100%
+    const totalCount = tabCounts.all;
+    const repliedCount = totalCount - tabCounts.pending; // 已回复 = 总数 - 未回复
+    if (totalCount > 0) {
+      const rate = (repliedCount / totalCount) * 100;
+      statistics.abnormalRate = rate.toFixed(2) + "%";
+    } else {
+      statistics.abnormalRate = "0%";
+    }
+  } catch (error) {
+    console.error("获取统计数据失败", error);
+  }
+};
+
+// 加载评论列表
+const loadReviewList = async (commentLevel?: string | number, resetPage = false) => {
+  try {
+    // 如果需要重置页码
+    if (resetPage) {
+      pageNum.value = 1;
+    }
+
+    // 如果没有传入参数,使用当前激活的标签页
+    const level = commentLevel !== undefined ? commentLevel : activeTab.value;
+
+    // 判断是否是待回复差评
+    const isPending = level === "pending";
+
+    const params: any = {
+      pageNum: pageNum.value,
+      pageSize: pageSize.value,
+      phoneId: `store_${localGet("geeker-user").userInfo.phone}`,
+      businessType: 5, //业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价, 6:订单评论的评论)
+      commentLevel: isPending ? 3 : level, // 待回复差评传3,其他按原值
+      days: timeFilter.value, // 评论时间筛选
+      replyStatus: isPending ? 2 : 0, // 待回复差评传2,其他传0
+      storeId: localGet("createdId"),
+      userType: 0
+    };
+
+    const res: any = await getList(params);
+
+    if (res.code === 200) {
+      reviewList.value = res.data.records || [];
+      total.value = res.data.total || 0;
+    } else {
+      reviewList.value = [];
+      total.value = 0;
+      ElMessage.error(res.msg || "获取评论列表失败");
+    }
+  } catch (error: any) {
+    console.error("获取评论列表失败", error);
+    reviewList.value = [];
+    total.value = 0;
+    ElMessage.error(error?.msg || "获取评论列表失败");
+  }
+};
+
+// 分页大小变化
+const handleSizeChange = (val: number) => {
+  pageSize.value = val;
+  pageNum.value = 1;
+  loadReviewList();
+};
+
+// 页码变化
+const handleCurrentChange = (val: number) => {
+  pageNum.value = val;
+  loadReviewList();
+};
+
+// 申诉删除
+const delReviewAppeal = (review: any) => {
+  currentReviewId.value = review.id;
+  appealDialogVisible.value = true;
+};
+//回复评论
+// 打开回复对话框
+const openReplayDialog = (review: any) => {
+  console.log("打开回复对话框:", review);
+  currentReplyReview.value = review;
+  replyDialogVisible.value = true;
+};
+
+// 关闭回复对话框
+const closeReplyDialog = () => {
+  replyDialogVisible.value = false;
+  replyFormRef.value?.resetFields();
+  Object.assign(replyFormData, {
+    content: ""
+  });
+  currentReplyReview.value = null;
+};
+
+// 提交回复
+const submitReply = async () => {
+  if (!replyFormRef.value) return;
+
+  await replyFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        // 获取当前用户的手机号,并在前面拼接 "store_"
+        const phone = userStore.userInfo?.phone || "";
+        const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+        // 调用 saveComment 接口
+        const params = {
+          businessId: currentReplyReview.value.id, // 评论ID
+          businessType: "1", // 业务类型:1表示订单评价的回复
+          userId: userStore.userInfo?.userId || userStore.userInfo?.id || "",
+          storeId: userStore.userInfo?.storeId || localGet("createdId") || "",
+          commentContent: replyFormData.content, // 回复内容
+          phoneId: phoneId, // 店铺phoneId
+          replyId: currentReplyReview.value.id // 被回复的评论ID
+        };
+
+        console.log("提交回复参数:", params);
+
+        const res: any = await saveComment(params);
+
+        if (res.code === 200) {
+          ElMessage.success("回复提交成功");
+          closeReplyDialog();
+          loadReviewList();
+        } else {
+          ElMessage.error(res.message || "回复提交失败");
+        }
+      } catch (error) {
+        console.error("回复提交失败:", error);
+        ElMessage.error("回复提交失败");
+      }
+    }
+  });
+};
+
+// 关闭申诉对话框
+const closeAppealDialog = () => {
+  appealDialogVisible.value = false;
+  appealFormRef.value?.resetFields();
+  Object.assign(appealFormData, {
+    reason: "",
+    images: [],
+    files: [],
+    fileList: []
+  });
+};
+
+// 提交申诉
+const submitAppeal = async () => {
+  if (!appealFormRef.value) return;
+
+  await appealFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        // 使用 FormData 发送图片
+        const formData = new FormData();
+        formData.append("appealReason", appealFormData.reason);
+        formData.append("storeId", localGet("geeker-user").userInfo.storeId);
+        formData.append("commentId", currentReviewId.value);
+
+        // 添加图片文件,使用 file_0, file_1, file_2 等格式
+        appealFormData.files.forEach((file, index) => {
+          formData.append(`file_${index}`, file);
+        });
+
+        const res: any = await addAppealNew(formData);
+        if (res.code === 200) {
+          ElMessage.success("申诉提交成功");
+          // 更新评价的申诉状态,使按钮置灰
+          const review = reviewList.value.find((r: any) => r.id === currentReviewId.value);
+          if (review) {
+            review.isAppealed = true;
+          }
+        }
+
+        closeAppealDialog();
+      } catch (error) {
+        ElMessage.error("申诉提交失败");
+      }
+    }
+  });
+};
+
+// 图片预览
+const handlePreview = (file: UploadUserFile) => {
+  console.log("preview", file);
+};
+
+// 移除图片
+const handleRemove = (file: UploadUserFile, fileList: UploadUserFile[]) => {
+  console.log("remove", file);
+  // 找到对应的索引并删除
+  const index = appealFormData.fileList.findIndex(f => f.uid === file.uid);
+  if (index !== -1) {
+    appealFormData.images.splice(index, 1);
+    appealFormData.files.splice(index, 1);
+  }
+  appealFormData.fileList = fileList;
+};
+
+// 自定义上传方法
+const handleUpload = async (options: any) => {
+  const { file, onSuccess, onError } = options;
+
+  try {
+    // 保存原始的 File 对象用于提交时上传
+    appealFormData.files.push(file);
+
+    // 创建本地预览URL
+    const previewUrl = URL.createObjectURL(file);
+    onSuccess({ url: previewUrl });
+
+    // 将预览URL添加到images数组
+    appealFormData.images.push(previewUrl);
+    console.log("图片已添加:", file.name);
+  } catch (error: any) {
+    onError(error);
+    ElMessage.error(error?.msg || "添加图片失败");
+  }
+};
+
+// 上传成功回调
+const handleUploadSuccess = (response: any, file: any) => {
+  console.log("上传成功回调:", response, file);
+};
+
+// 初始化
+onMounted(() => {
+  loadStatistics(); // 加载统计数据
+  loadReviewList(); // 加载评论列表
+});
+</script>
+
+<style lang="scss" scoped>
+.review-appeal-container {
+  min-height: calc(100vh - 120px);
+  background: #f5f7fa;
+
+  // 统计数据区域
+  .statistics-section {
+    padding: 20px;
+    margin-bottom: 20px;
+    background: #ffffff;
+    border-radius: 8px;
+    .statistics-cards {
+      display: flex;
+      gap: 20px;
+      margin-bottom: 16px;
+      .stat-card {
+        flex: 1;
+        padding: 16px;
+        text-align: center;
+        background: #f5f7fa;
+        border-radius: 4px;
+        .stat-label {
+          margin-bottom: 8px;
+          font-size: 14px;
+          color: #909399;
+        }
+        .stat-value {
+          font-size: 24px;
+          font-weight: 600;
+          color: #303133;
+          &.highlight {
+            color: #f56c6c;
+          }
+        }
+      }
+    }
+  }
+
+  // 评价列表区域
+  .review-list-section {
+    padding: 20px;
+    background: #ffffff;
+    border-radius: 8px;
+    .section-header {
+      margin-bottom: 20px;
+      .section-title {
+        margin-bottom: 16px;
+        font-size: 16px;
+        font-weight: 600;
+        color: #303133;
+      }
+      .time-filter {
+        margin-bottom: 10px;
+        font-size: 14px;
+        .time-filter-select {
+          width: 120px;
+        }
+      }
+    }
+
+    // 评论卡片
+    .review-cards {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+      .review-card {
+        padding: 16px;
+        border: 1px solid #e4e7ed;
+        border-radius: 8px;
+        .review-header {
+          display: flex;
+          justify-content: space-between;
+          margin-bottom: 12px;
+          .user-info {
+            display: flex;
+            gap: 12px;
+            align-items: center;
+            .user-details {
+              .user-name {
+                margin-bottom: 4px;
+                font-size: 14px;
+                font-weight: 600;
+                color: #303133;
+              }
+            }
+          }
+          .review-time {
+            font-size: 13px;
+            color: #909399;
+          }
+        }
+        .review-content {
+          margin-bottom: 12px;
+          font-size: 14px;
+          line-height: 1.6;
+          color: #606266;
+        }
+        .sjhf {
+          padding-bottom: 10px;
+          font-size: 14px;
+          color: #606266;
+        }
+        .review-images {
+          display: flex;
+          gap: 8px;
+          margin-bottom: 12px;
+          .review-image {
+            width: 80px;
+            height: 80px;
+            border-radius: 4px;
+          }
+        }
+        .review-footer {
+          display: flex;
+          gap: 16px;
+          padding-top: 12px;
+          border-top: 1px solid #e4e7ed;
+        }
+      }
+    }
+
+    // 分页
+    .pagination-wrapper {
+      display: flex;
+      justify-content: flex-end;
+      padding-top: 20px;
+      margin-top: 20px;
+      border-top: 1px solid #e4e7ed;
+    }
+  }
+}
+</style>

+ 361 - 0
src/views/dynamicManagement/reviewAppealDetail.vue

@@ -0,0 +1,361 @@
+<template>
+  <div class="review-appeal-detail-container">
+    <!-- 返回按钮 -->
+    <div class="page-header">
+      <el-button type="primary" @click="goBack"> 返回 </el-button>
+    </div>
+
+    <!-- 标题和状态 -->
+    <div class="detail-header">
+      <h2 class="page-title">申诉详情</h2>
+      <div class="status-section">
+        <el-icon class="status-icon" :size="48">
+          <Clock />
+        </el-icon>
+        <div class="status-text">
+          {{ getStatusText(detailData.appealStatus) }}
+        </div>
+        <div class="status-desc">您反馈的评价内容及账号行为正处于审核阶段,请您耐心等待</div>
+      </div>
+    </div>
+
+    <!-- 处理进度 -->
+    <div class="progress-section">
+      <h3 class="section-title">处理进度</h3>
+      <el-timeline>
+        <el-timeline-item v-for="(step, index) in progressSteps" :key="index" :timestamp="step.timestamp" placement="top">
+          {{ step.content }}
+        </el-timeline-item>
+      </el-timeline>
+    </div>
+
+    <!-- 申诉详情 -->
+    <div class="appeal-detail-section">
+      <h3 class="section-title">申诉详情</h3>
+
+      <!-- 顾客评价 -->
+      <div class="review-card">
+        <div class="card-label">顾客评价</div>
+        <div class="review-header">
+          <div class="user-info">
+            <el-avatar :src="detailData.userAvatar" :size="40">
+              <el-icon><User /></el-icon>
+            </el-avatar>
+            <div class="user-details">
+              <div class="user-name">
+                {{ detailData.isAnonymous == 1 || !detailData.userName ? "匿名用户" : detailData.userName }}
+              </div>
+            </div>
+          </div>
+          <div class="review-time">
+            {{ detailData.commentTime }}
+          </div>
+        </div>
+
+        <div class="review-content">
+          {{ detailData.commentContent }}
+        </div>
+
+        <div v-if="detailData.commentImages && detailData.commentImages.length > 0" class="review-images">
+          <el-image
+            v-for="(img, index) in detailData.commentImages"
+            :key="index"
+            :src="img"
+            :preview-src-list="detailData.commentImages"
+            fit="cover"
+            class="review-image"
+          />
+        </div>
+      </div>
+
+      <!-- 申诉信息 -->
+      <div class="appeal-info-card">
+        <div class="card-label">申诉信息</div>
+        <div class="info-item">
+          <span class="info-label">申诉账号</span>
+          <span class="info-value">{{ detailData.storePhone }}</span>
+        </div>
+        <div class="info-item">
+          <span class="info-label">申诉时间</span>
+          <span class="info-value">{{ detailData.appealTime }}</span>
+        </div>
+        <div class="info-item">
+          <span class="info-label">申诉原因</span>
+          <span class="info-value">{{ detailData.appealReason }}</span>
+        </div>
+        <div class="info-item">
+          <span class="info-label">申诉图片</span>
+          <div v-if="detailData.imgList && detailData.imgList.length > 0" class="appeal-images">
+            <el-image
+              v-for="(img, index) in detailData.imgList"
+              :key="index"
+              :src="img"
+              :preview-src-list="detailData.imgList"
+              fit="cover"
+              class="appeal-image"
+            />
+          </div>
+          <span v-else class="info-value">--</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="reviewAppealDetail">
+import { ref, reactive, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { Clock, User } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+import { getAppealDetail } from "@/api/modules/newLoginApi";
+import { de } from "element-plus/es/locale";
+
+const router = useRouter();
+const route = useRoute();
+
+// 详情数据
+const detailData = reactive({
+  appealStatus: 0, // 0-待审核, 1-已驳回, 2-已通过
+  userName: "",
+  userAvatar: "",
+  isAnonymous: 0,
+  commentTime: "",
+  commentContent: "",
+  commentImages: [] as string[],
+  appealAccount: "",
+  appealTime: "",
+  appealReason: "",
+  storePhone: "",
+  imgList: [] as string[]
+});
+
+// 处理进度步骤
+const progressSteps = ref<Array<{ content: string; timestamp: string }>>([
+  {
+    content: "商家提交申诉",
+    timestamp: "2025/06/10 12:00:00"
+  },
+  {
+    content: "通过系统初审,已为您安排专人审核",
+    timestamp: "2025/06/10 12:00:00"
+  },
+  {
+    content: "申诉成功",
+    timestamp: "2025/06/10 12:00:00"
+  }
+]);
+
+// 返回
+const goBack = () => {
+  router.back();
+};
+
+// 获取状态文本
+const getStatusText = (status: number) => {
+  const statusMap: Record<number, string> = {
+    0: "待审核",
+    1: "已驳回",
+    2: "已通过"
+  };
+  return statusMap[status] || "待审核";
+};
+
+// 加载申诉详情
+const loadAppealDetail = async () => {
+  try {
+    const appealId = route.query.id;
+    if (!appealId) {
+      ElMessage.error("缺少申诉ID");
+      return;
+    }
+
+    const res: any = await getAppealDetail({ id: appealId });
+    console.log("申诉详情:", res);
+
+    if (res.code === 200 || res.code == 200) {
+      const data = res.data;
+      Object.assign(detailData, {
+        appealStatus: data.appealStatus ?? 0,
+        userName: data.userName || "",
+        userAvatar: data.userAvatar || "",
+        isAnonymous: data.isAnonymous ?? 0,
+        commentTime: data.commentTime || data.createdTime || "",
+        commentContent: data.commentContent || "",
+        commentImages: data.commentImages || [],
+        appealAccount: data.appealAccount || data.phone || "",
+        appealTime: data.appealTime || data.createdTime || "",
+        appealReason: data.appealReason || "",
+        storePhone: data.storePhone || "",
+        imgList: data.imgList || []
+      });
+
+      // 更新处理进度
+      if (data.progressSteps && Array.isArray(data.progressSteps)) {
+        progressSteps.value = data.progressSteps;
+      }
+    } else {
+      ElMessage.error(res.msg || "获取申诉详情失败");
+    }
+  } catch (error: any) {
+    console.error("获取申诉详情失败", error);
+    ElMessage.error(error?.msg || "获取申诉详情失败");
+  }
+};
+
+// 初始化
+onMounted(() => {
+  loadAppealDetail();
+});
+</script>
+
+<style lang="scss" scoped>
+.review-appeal-detail-container {
+  min-height: calc(100vh - 120px);
+  padding: 20px;
+  background: #f5f7fa;
+  .page-header {
+    margin-bottom: 20px;
+  }
+
+  // 标题和状态
+  .detail-header {
+    padding: 24px;
+    margin-bottom: 20px;
+    text-align: center;
+    background: #ffffff;
+    border-radius: 8px;
+    .page-title {
+      margin: 0 0 24px;
+      font-size: 20px;
+      font-weight: 600;
+      color: #303133;
+    }
+    .status-section {
+      .status-icon {
+        margin-bottom: 12px;
+        color: #e6a23c;
+      }
+      .status-text {
+        margin-bottom: 8px;
+        font-size: 18px;
+        font-weight: 600;
+        color: #303133;
+      }
+      .status-desc {
+        font-size: 14px;
+        color: #909399;
+      }
+    }
+  }
+
+  // 处理进度
+  .progress-section {
+    padding: 24px;
+    margin-bottom: 20px;
+    background: #ffffff;
+    border-radius: 8px;
+    .section-title {
+      margin: 0 0 20px;
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+
+  // 申诉详情
+  .appeal-detail-section {
+    padding: 24px;
+    background: #ffffff;
+    border-radius: 8px;
+    .section-title {
+      margin: 0 0 20px;
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+    .card-label {
+      margin-bottom: 16px;
+      font-size: 14px;
+      font-weight: 600;
+      color: #606266;
+    }
+
+    // 顾客评价卡片
+    .review-card {
+      padding: 16px;
+      margin-bottom: 24px;
+      background: #f5f7fa;
+      border-radius: 8px;
+      .review-header {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 12px;
+        .user-info {
+          display: flex;
+          gap: 12px;
+          align-items: center;
+          .user-details {
+            .user-name {
+              font-size: 14px;
+              font-weight: 600;
+              color: #303133;
+            }
+          }
+        }
+        .review-time {
+          font-size: 13px;
+          color: #909399;
+        }
+      }
+      .review-content {
+        margin-bottom: 12px;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #606266;
+      }
+      .review-images {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        .review-image {
+          width: 100px;
+          height: 100px;
+          border-radius: 4px;
+        }
+      }
+    }
+
+    // 申诉信息卡片
+    .appeal-info-card {
+      .info-item {
+        display: flex;
+        margin-bottom: 16px;
+        &:last-child {
+          margin-bottom: 0;
+        }
+        .info-label {
+          flex-shrink: 0;
+          width: 100px;
+          font-size: 14px;
+          color: #909399;
+        }
+        .info-value {
+          flex: 1;
+          font-size: 14px;
+          color: #606266;
+        }
+        .appeal-images {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+          .appeal-image {
+            width: 100px;
+            height: 100px;
+            border-radius: 4px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 293 - 0
src/views/dynamicManagement/reviewAppealHistory.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="review-appeal-history-container">
+    <!-- 返回按钮 -->
+    <div class="page-header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="page-title">申诉历史</h2>
+    </div>
+
+    <!-- 标签页 -->
+    <div class="tabs-section">
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="全部" name="all" />
+        <el-tab-pane label="审核中" name="pending" />
+        <el-tab-pane label="已驳回" name="rejected" />
+        <el-tab-pane label="已通过" name="approved" />
+      </el-tabs>
+    </div>
+
+    <!-- 申诉历史列表 -->
+    <div v-if="appealHistoryList.length > 0" class="appeal-history-list">
+      <div v-for="item in appealHistoryList" :key="item.id" class="appeal-history-card">
+        <div class="appeal-header">
+          <div class="user-info">
+            <el-avatar :src="item.userAvatar" :size="40">
+              <el-icon><User /></el-icon>
+            </el-avatar>
+            <div class="user-details">
+              <div class="user-name">
+                {{ item.isAnonymous == 1 || !item.userName ? "匿名用户" : item.userName }}
+              </div>
+              <div class="appeal-time">
+                {{ item.createdTime }}
+              </div>
+            </div>
+          </div>
+          <el-tag :type="getAppealStatusType(item.status)" size="large">
+            {{ getAppealStatusText(item.appealStatus) }}
+          </el-tag>
+        </div>
+
+        <div class="appeal-content">
+          {{ item.reviewContent }}
+        </div>
+
+        <div class="appeal-footer">
+          <div class="appeal-info">
+            <div class="info-item">
+              <span class="info-label">申诉状态:</span>
+              <span>{{ item.appealReason }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">申诉编号:</span>
+              <span>{{ item.appealNo || "--" }}</span>
+            </div>
+          </div>
+          <el-button type="primary" @click="viewDetail(item)"> 查看详情 </el-button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <el-empty v-else description="暂无申诉记录" />
+
+    <!-- 分页 -->
+    <div v-if="appealHistoryList.length > 0" class="pagination-section">
+      <el-pagination
+        v-model:current-page="pagination.page"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[10, 20, 30, 50, 100]"
+        :total="pagination.total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="reviewAppealHistory">
+import { ref, reactive, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { ArrowLeft, User } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+import { getAppealHistory } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils";
+
+const router = useRouter();
+
+// 当前激活的标签
+const activeTab = ref("all");
+
+// 申诉历史列表
+const appealHistoryList = ref<any[]>([]);
+
+// 分页
+const pagination = reactive({
+  page: 1,
+  pageSize: 50,
+  total: 0
+});
+
+// 返回
+const goBack = () => {
+  router.back();
+};
+
+// 标签页切换
+const handleTabClick = () => {
+  pagination.page = 1;
+  loadAppealHistory();
+};
+
+// 分页大小改变
+const handleSizeChange = (val: number) => {
+  pagination.pageSize = val;
+  pagination.page = 1;
+  loadAppealHistory();
+};
+
+// 当前页改变
+const handleCurrentChange = (val: number) => {
+  pagination.page = val;
+  loadAppealHistory();
+};
+
+// 加载申诉历史
+const loadAppealHistory = async () => {
+  try {
+    // 根据标签页设置 appealStatus
+    let appealStatus: string | number = "";
+    if (activeTab.value === "pending") {
+      appealStatus = 0; // 审核中
+    } else if (activeTab.value === "rejected") {
+      appealStatus = 1; // 已驳回
+    } else if (activeTab.value === "approved") {
+      appealStatus = 2; // 已通过
+    } else {
+      appealStatus = ""; // 全部:空字符串
+    }
+
+    const params: any = {
+      storeId: localGet("createdId") || "",
+      appealStatus: appealStatus,
+      pageNum: pagination.page,
+      pageSize: pagination.pageSize
+    };
+
+    console.log("请求参数:", params);
+    const res: any = await getAppealHistory(params);
+    console.log("接口返回:", res);
+
+    if (res.code === 200 || res.code == 200) {
+      appealHistoryList.value = res.data?.records || res.data || [];
+      pagination.total = res.data?.total || 0;
+    } else {
+      appealHistoryList.value = [];
+      pagination.total = 0;
+      ElMessage.error(res.msg || "获取申诉历史失败");
+    }
+  } catch (error: any) {
+    console.error("获取申诉历史失败", error);
+    appealHistoryList.value = [];
+    pagination.total = 0;
+    ElMessage.error(error?.msg || "获取申诉历史失败");
+  }
+};
+
+// 查看详情
+const viewDetail = (item: any) => {
+  router.push({
+    path: "/dynamicManagement/reviewAppealDetail",
+    query: { id: item.id }
+  });
+};
+
+// 获取申诉状态类型
+const getAppealStatusType = (status: number): "success" | "warning" | "info" | "danger" => {
+  const typeMap: Record<number, "success" | "warning" | "info" | "danger"> = {
+    0: "warning", // 审核中
+    1: "danger", // 已驳回
+    2: "success" // 已通过
+  };
+  return typeMap[status] || "info";
+};
+
+// 获取申诉状态文本
+const getAppealStatusText = (status: number) => {
+  const textMap: Record<number, string> = {
+    0: "审核中",
+    1: "已驳回",
+    2: "已通过"
+  };
+  return textMap[status] || "";
+};
+
+// 初始化
+onMounted(() => {
+  loadAppealHistory();
+});
+</script>
+
+<style lang="scss" scoped>
+.review-appeal-history-container {
+  min-height: calc(100vh - 120px);
+  padding: 20px;
+  background: #ffffff;
+  .page-header {
+    display: flex;
+    gap: 16px;
+    align-items: center;
+    margin-bottom: 24px;
+    .page-title {
+      width: 100%;
+      margin: 0;
+      font-size: 20px;
+      font-weight: 600;
+      color: #303133;
+      text-align: center;
+    }
+  }
+  .tabs-section {
+    margin-bottom: 24px;
+  }
+  .appeal-history-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    margin-bottom: 24px;
+    .appeal-history-card {
+      padding: 20px;
+      border: 1px solid #e4e7ed;
+      border-radius: 8px;
+      transition: all 0.3s;
+      &:hover {
+        box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+      }
+      .appeal-header {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 16px;
+        .user-info {
+          display: flex;
+          gap: 12px;
+          align-items: center;
+          .user-details {
+            .user-name {
+              margin-bottom: 4px;
+              font-size: 15px;
+              font-weight: 600;
+              color: #303133;
+            }
+            .appeal-time {
+              font-size: 13px;
+              color: #909399;
+            }
+          }
+        }
+      }
+      .appeal-content {
+        margin-bottom: 16px;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #606266;
+      }
+      .appeal-footer {
+        display: flex;
+        align-items: flex-end;
+        justify-content: space-between;
+        padding-top: 16px;
+        border-top: 1px solid #e4e7ed;
+        .appeal-info {
+          flex: 1;
+          .info-item {
+            margin-bottom: 8px;
+            font-size: 14px;
+            &:last-child {
+              margin-bottom: 0;
+            }
+            .info-label {
+              color: #909399;
+            }
+          }
+        }
+      }
+    }
+  }
+  .pagination-section {
+    display: flex;
+    justify-content: center;
+    padding: 20px 0;
+  }
+}
+</style>

+ 2117 - 0
src/views/dynamicManagement/userDynamic.vue

@@ -0,0 +1,2117 @@
+<template>
+  <div class="user-dynamic-container">
+    <!-- 用户信息卡片 -->
+    <div class="user-card">
+      <div class="user-header">
+        <div class="user-avatar-section">
+          <div class="user-avatar-large">
+            <img v-if="userInfo.avatar" :src="userInfo.avatar" :alt="userInfo.name" />
+            <el-icon v-else :size="60">
+              <Avatar />
+            </el-icon>
+          </div>
+          <div class="user-info-text">
+            <div class="user-name">
+              {{ userInfo.name }}
+            </div>
+            <div class="user-stats">
+              <span class="stat-item">{{ userInfo.followCount || 0 }} 关注</span>
+              <span class="stat-divider">|</span>
+              <span class="stat-item">{{ userInfo.fansCount || 0 }} 粉丝</span>
+              <span class="stat-divider">|</span>
+              <span class="stat-item">{{ userInfo.likeCount || 0 }} 获赞</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右侧操作按钮(只在他人主页显示) -->
+        <div v-if="!isMyPage" class="user-actions">
+          <el-button type="primary" @click="handleFollow">
+            {{ isFollowed ? "已关注" : "关注" }}
+          </el-button>
+          <el-dropdown trigger="click" @command="handleCommand">
+            <el-button>
+              <el-icon>
+                <MoreFilled />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item command="report"> 举报 </el-dropdown-item>
+                <el-dropdown-item command="block"> 拉黑 </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </div>
+
+      <div class="user-bio">
+        {{ userInfo.jianjie }}
+      </div>
+    </div>
+
+    <!-- 标签页 -->
+    <div class="tabs-section">
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="动态" name="dynamic" />
+      </el-tabs>
+    </div>
+
+    <!-- 内容区域 -->
+    <div v-if="contentList.length > 0" class="content-section">
+      <!-- 动态卡片网格 -->
+      <div class="content-grid">
+        <div v-for="item in paginatedList" :key="item.id" class="content-card" @click="handleCardClick(item)">
+          {{ item }}
+          <!-- 封面图片区域 -->
+          <div class="content-cover-wrapper">
+            <img v-if="item.coverUrl" :src="item.coverUrl" :alt="item.title" class="content-cover" />
+            <div v-else class="cover-placeholder">
+              <el-icon :size="48" color="#999">
+                <Picture />
+              </el-icon>
+            </div>
+          </div>
+
+          <!-- 观看次数 -->
+          <div class="content-footer">
+            <el-icon :size="14" color="#666">
+              <View />
+            </el-icon>
+            <span class="view-count">{{ item.viewCount || 0 }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div v-else class="empty-section">
+      <el-empty description="暂无动态" />
+    </div>
+
+    <!-- 分页 -->
+    <div v-if="contentList.length > 0" class="pagination-section">
+      <el-pagination
+        v-model:current-page="pagination.page"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[12, 24, 36, 48]"
+        :total="pagination.total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 动态详情 Drawer(复用动态广场的样式) -->
+    <el-drawer
+      v-model="detailDrawerVisible"
+      direction="rtl"
+      size="90%"
+      :show-close="false"
+      destroy-on-close
+      class="detail-drawer"
+    >
+      <template #header>
+        <div class="drawer-header">
+          <el-button class="close-btn" text @click="handleCloseDetail">
+            <el-icon :size="24">
+              <Close />
+            </el-icon>
+          </el-button>
+        </div>
+      </template>
+
+      <div v-if="currentDetail" class="detail-content">
+        <!-- 主内容区域 -->
+        <div class="detail-main">
+          <!-- 图片/视频轮播展示 -->
+          <div class="media-container">
+            <!-- 多媒体轮播 -->
+            <el-carousel
+              v-if="currentDetail.mediaList && currentDetail.mediaList.length > 0"
+              :autoplay="false"
+              :loop="false"
+              indicator-position="outside"
+              arrow="always"
+              height="100%"
+              class="media-carousel"
+              @change="handleCarouselChange"
+            >
+              <el-carousel-item v-for="(media, index) in currentDetail.mediaList" :key="index">
+                <!-- 视频 -->
+                <video
+                  v-if="media.type === 'video'"
+                  :ref="el => setVideoRef(el, index)"
+                  :src="media.url"
+                  class="detail-media detail-video"
+                  controls
+                  preload="metadata"
+                  @play="handleVideoPlay(index)"
+                />
+                <!-- 图片 -->
+                <img v-else :src="media.url" :alt="currentDetail.title" class="detail-media detail-image" />
+              </el-carousel-item>
+            </el-carousel>
+            <!-- 占位符 -->
+            <div v-else class="media-placeholder">
+              <el-icon :size="80" color="#dcdfe6">
+                <Picture />
+              </el-icon>
+            </div>
+            <!-- 媒体计数指示器 -->
+            <div v-if="currentDetail.mediaList && currentDetail.mediaList.length > 1" class="media-counter">
+              {{ currentCarouselIndex + 1 }} / {{ currentDetail.mediaList.length }}
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="detail-info">
+            <div class="author-info">
+              <div class="author-avatar">
+                <img v-if="userInfo.avatar" :src="userInfo.avatar" :alt="userInfo.name" />
+                <el-icon v-else :size="32">
+                  <Avatar />
+                </el-icon>
+              </div>
+              <div class="author-details">
+                <div class="author-name">@{{ userInfo.name }}</div>
+                <div class="publish-time">
+                  {{ currentDetail.createTime }}
+                </div>
+              </div>
+            </div>
+
+            <div class="detail-description">
+              <p>{{ currentDetail.title }}</p>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右侧操作栏 -->
+        <div class="action-bar">
+          <!-- 作者头像 -->
+          <div class="action-item author-action">
+            <div class="action-avatar">
+              <img v-if="userInfo.avatar" :src="userInfo.avatar" :alt="userInfo.name" />
+              <el-icon v-else :size="40" color="#fff">
+                <Avatar />
+              </el-icon>
+              <!-- 关注按钮 (定位在头像右下角) -->
+              <div v-if="!isFollowed && !isMyPage" class="follow-badge" @click.stop="handleFollowInDetail">
+                <el-icon :size="16" color="#fff">
+                  <Plus />
+                </el-icon>
+              </div>
+            </div>
+          </div>
+
+          <!-- 点赞 -->
+          <div class="action-item" @click="handleDetailLike">
+            <div class="action-icon">
+              <el-icon :size="28" :color="currentDetail.isLiked ? '#f56c6c' : '#fff'">
+                <StarFilled v-if="currentDetail.isLiked" />
+                <Star v-else />
+              </el-icon>
+            </div>
+            <div class="action-count">
+              {{ currentDetail.likeCount }}
+            </div>
+          </div>
+
+          <!-- 评论 -->
+          <div class="action-item" @click="handleShowComments">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <ChatDotRound />
+              </el-icon>
+            </div>
+            <div class="action-count">
+              {{ currentDetail.commentCount || 0 }}
+            </div>
+          </div>
+
+          <!-- 分享 -->
+          <div class="action-item" @click="handleShare">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <Share />
+              </el-icon>
+            </div>
+            <div class="action-count">分享</div>
+          </div>
+
+          <!-- 更多(仅在他人主页显示举报和拉黑) -->
+          <el-popover v-if="!isMyPage" placement="left" :width="120" trigger="click" popper-class="more-actions-popover">
+            <template #reference>
+              <div class="action-item">
+                <div class="action-icon">
+                  <el-icon :size="28" color="#fff">
+                    <MoreFilled />
+                  </el-icon>
+                </div>
+              </div>
+            </template>
+            <div class="more-actions-menu">
+              <div
+                class="menu-item"
+                style="display: flex; align-items: center; padding-bottom: 10px; cursor: pointer"
+                @click="handleReportDynamic"
+              >
+                <el-icon :size="18">
+                  <Warning /> </el-icon
+                >&nbsp;&nbsp;
+                <span>举报</span>
+              </div>
+              <div
+                class="menu-item"
+                style="display: flex; align-items: center; cursor: pointer"
+                @click="handleBlockUserFromDrawer"
+              >
+                <el-icon :size="18">
+                  <CircleClose /> </el-icon
+                >&nbsp;&nbsp;
+                <span>拉黑</span>
+              </div>
+            </div>
+          </el-popover>
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- 评论侧边栏 -->
+    <el-drawer v-model="commentDrawerVisible" title="评论" direction="rtl" size="400px" destroy-on-close>
+      <!-- 评论列表 -->
+      <div class="comment-list-container">
+        <div v-if="commentListData.length > 0" class="comment-list">
+          <div v-for="comment in commentListData" :key="comment.id" class="comment-item">
+            <div class="comment-avatar">
+              <img v-if="comment.userAvatar" :src="comment.userAvatar" :alt="comment.userName" />
+              <el-icon v-else :size="32">
+                <Avatar />
+              </el-icon>
+            </div>
+            <div class="comment-content-wrapper">
+              <div class="comment-header">
+                <span class="comment-user-name">{{ comment.userName }}</span>
+              </div>
+              <div class="comment-text">
+                {{ comment.commentContent }}
+              </div>
+
+              <div class="comment-actions">
+                <span class="comment-action-item" @click="handleLikeComment(comment)">
+                  <el-icon :size="16" :color="comment.isLiked ? '#f56c6c' : '#999'">
+                    <Star />
+                  </el-icon>
+                  <span>{{ comment.likeCount || 0 }}</span>
+                </span>
+                <span class="comment-action-item" @click="handleReplyComment(comment)">
+                  <el-icon :size="16">
+                    <ChatDotRound />
+                  </el-icon>
+                  <span>回复</span>
+                </span>
+              </div>
+              <!-- 商家回复 -->
+              <div v-for="item in comment.storeComment" :key="item.id" class="store-comment-wrapper">
+                <div class="store-comment-item">
+                  <div class="store-comment-avatar">
+                    <img v-if="item.userImage" :src="item.userImage" :alt="item.userName" />
+                    <el-icon v-else :size="24">
+                      <Avatar />
+                    </el-icon>
+                  </div>
+                  <div class="store-comment-content">
+                    <div class="store-comment-header">
+                      <span class="store-comment-user-name">{{ item.userName || "商家" }}</span>
+                      <span class="store-comment-time">{{ item.createdTime || item.createDate }}</span>
+                    </div>
+                    <div class="store-comment-text">
+                      {{ item.commentContent }}
+                      <span class="comment-action-item" @click="handleLikeComment(comment)">
+                        <el-icon :size="16" :color="comment.isLiked ? '#f56c6c' : '#999'">
+                          <Star />
+                        </el-icon>
+                        <span>{{ comment.likeCount || 0 }}</span>
+                      </span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <el-empty v-else description="暂无评论" />
+      </div>
+
+      <!-- 评论输入框 -->
+      <div class="comment-input-wrapper">
+        <!-- 回复提示 -->
+        <div v-if="replyingComment" class="reply-hint">
+          <span class="reply-text">回复 @{{ replyingComment.userName }}</span>
+          <el-icon class="cancel-reply" @click="handleCancelReply">
+            <Close />
+          </el-icon>
+        </div>
+        <el-input
+          v-model="commentInput"
+          type="textarea"
+          :rows="3"
+          :placeholder="replyingComment ? '输入回复内容...' : '你要评论点什么呢~'"
+          maxlength="500"
+          show-word-limit
+        />
+        <el-button type="primary" :loading="commentSubmitting" @click="handleSubmitComment">
+          {{ replyingComment ? "回复" : "发送" }}
+        </el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 分享对话框 -->
+    <el-dialog v-model="shareDialogVisible" title="分享给好友" width="500px" destroy-on-close @close="handleCloseShareDialog">
+      <div class="share-dialog-content">
+        <!-- 好友列表 -->
+        <div class="share-friend-list">
+          <div v-if="filteredShareFriendList.length > 0">
+            <div
+              v-for="friend in filteredShareFriendList"
+              :key="friend.id"
+              class="share-friend-item"
+              @click="handleSelectFriend(friend)"
+            >
+              <div class="friend-info">
+                <div class="friend-avatar">
+                  <img v-if="friend.avatar" :src="friend.avatar" :alt="friend.name" />
+                  <el-icon v-else :size="40">
+                    <Avatar />
+                  </el-icon>
+                </div>
+                <div class="friend-name">
+                  {{ friend.name }}
+                </div>
+              </div>
+              <el-icon v-if="selectedFriends.includes(friend.id)" :size="20" color="#409eff">
+                <CircleCheck />
+              </el-icon>
+            </div>
+          </div>
+          <el-empty v-else description="暂无好友" />
+        </div>
+
+        <!-- 已选择的好友 -->
+        <div v-if="selectedFriends.length > 0" class="selected-friends">
+          <div class="selected-title">已选择 {{ selectedFriends.length }} 位好友</div>
+          <div class="selected-list">
+            <el-tag v-for="friendId in selectedFriends" :key="friendId" closable @close="handleRemoveFriend(friendId)">
+              {{ shareFriendList.find(f => f.id === friendId)?.name }}
+            </el-tag>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="shareDialogVisible = false"> 取消 </el-button>
+          <el-button
+            type="primary"
+            :loading="shareSubmitting"
+            :disabled="selectedFriends.length === 0"
+            @click="handleConfirmShare"
+          >
+            确认分享
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 举报对话框 -->
+    <el-dialog v-model="reportDialogVisible" title="举报理由" width="500px" destroy-on-close @close="handleCloseReportDialog">
+      <div class="report-dialog-content">
+        <div class="report-tip">请选择最符合的原因,以便于我们进行的处理</div>
+
+        <!-- 举报原因选项 -->
+        <div class="report-reasons">
+          <el-radio-group v-model="reportForm.reason">
+            <el-radio label="用户头像"> 用户头像 </el-radio>
+            <el-radio label="名称/昵称"> 名称/昵称 </el-radio>
+            <el-radio label="违法违规"> 违法违规 </el-radio>
+            <el-radio label="低俗色情、暴力恐怖、政治谣言"> 低俗色情、暴力恐怖、政治谣言 </el-radio>
+            <el-radio label="涉嫌诈骗"> 涉嫌诈骗 </el-radio>
+            <el-radio label="人身攻击"> 人身攻击 </el-radio>
+            <el-radio label="侵犯版权"> 侵犯版权 </el-radio>
+            <el-radio label="恶意骚扰"> 恶意骚扰 </el-radio>
+            <el-radio label="虚假/过度宣传"> 虚假/过度宣传 </el-radio>
+            <el-radio label="诱导点赞分享"> 诱导点赞分享 </el-radio>
+            <el-radio label="传播人身安全"> 传播人身安全 </el-radio>
+            <el-radio label="侵权举报"> 侵权举报 </el-radio>
+            <el-radio label="其他举报"> 其他举报 </el-radio>
+          </el-radio-group>
+        </div>
+
+        <!-- 详细描述(仅"其他举报"时显示) -->
+        <div v-if="reportForm.reason === '其他举报'" class="report-description">
+          <el-input
+            v-model="reportForm.description"
+            type="textarea"
+            :rows="4"
+            placeholder="请描述任何涉及举报内容的其体情况,我们会综合一判断合举政采!(必填)"
+            maxlength="300"
+            show-word-limit
+          />
+        </div>
+
+        <!-- 上传凭证 -->
+        <div class="report-upload">
+          <div class="upload-title">上传凭证</div>
+          <el-upload
+            v-model:file-list="reportForm.fileList"
+            list-type="picture-card"
+            :limit="9"
+            :on-preview="handleReportPreview"
+            :on-remove="handleReportRemove"
+            :before-upload="beforeReportUpload"
+            :http-request="handleReportUpload"
+            accept="image/*"
+            multiple
+          >
+            <el-icon :size="24">
+              <Plus />
+            </el-icon>
+          </el-upload>
+        </div>
+
+        <!-- 同意协议 -->
+        <div class="report-agreement">
+          <el-checkbox v-model="reportForm.agreed"> 同时拉黑该用户 </el-checkbox>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="reportDialogVisible = false"> 取消 </el-button>
+          <el-button type="primary" :loading="reportSubmitting" @click="handleSubmitReport"> 提交 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="userDynamic">
+import { ref, reactive, computed, onMounted, watch } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import {
+  Avatar,
+  Picture,
+  View,
+  Close,
+  Star,
+  StarFilled,
+  Share,
+  MoreFilled,
+  Plus,
+  Warning,
+  CircleClose,
+  ChatDotRound,
+  CircleCheck
+} from "@element-plus/icons-vue";
+import {
+  getUserDynamicsList,
+  toggleFollowUser,
+  cancelFollowed,
+  reportUserViolation,
+  blockUser,
+  getUserByPhone
+} from "@/api/modules/dynamicManagement";
+import { uploadImg } from "@/api/modules/upload";
+import { useUserStore } from "@/stores/modules/user";
+import { saveComment, commentList, getMutualAttention, addTransferCount } from "@/api/modules/newLoginApi";
+
+const route = useRoute();
+const router = useRouter();
+const userStore = useUserStore();
+
+// 从路由参数获取用户信息
+const targetUserId = ref((route.query.userId as string) || "");
+const targetPhoneId = ref((route.query.phoneId as string) || "");
+const targetUserName = ref((route.query.userName as string) || "");
+const targetUserAvatar = ref((route.query.userAvatar as string) || "");
+const targetUserPhone = ref((route.query.phone as string) || "");
+
+// 接口定义
+interface MediaItem {
+  url: string;
+  type: "image" | "video";
+}
+
+interface ContentItem {
+  id: number;
+  title: string;
+  coverUrl: string;
+  viewCount: number;
+  likeCount: number;
+  isLiked: boolean;
+  createTime: string;
+  commentCount?: number;
+  mediaList?: MediaItem[];
+}
+
+interface ShareFriend {
+  id: number;
+  name: string;
+  avatar: string;
+  phoneId?: string;
+}
+
+// 响应式数据
+const activeTab = ref("dynamic");
+const contentList = ref<ContentItem[]>([]);
+const isFollowed = ref(false); // 是否已关注
+
+// 详情 Drawer 相关
+const detailDrawerVisible = ref(false);
+const currentDetail = ref<ContentItem | null>(null);
+
+// 视频轮播相关
+const videoRefs = ref<HTMLVideoElement[]>([]);
+const currentCarouselIndex = ref(0);
+
+// 举报对话框相关
+const reportDialogVisible = ref(false);
+const reportSubmitting = ref(false);
+const reportForm = reactive({
+  reason: "用户头像", // 默认选择第一个选项
+  description: "",
+  fileList: [] as any[],
+  agreed: false
+});
+
+// 评论相关
+const commentDrawerVisible = ref(false);
+const commentListData = ref<any[]>([]);
+const commentInput = ref("");
+const commentSubmitting = ref(false);
+const replyingComment = ref<any>(null);
+
+// 分享对话框相关
+const shareDialogVisible = ref(false);
+const shareSubmitting = ref(false);
+const shareSearch = ref("");
+const shareFriendList = ref<ShareFriend[]>([]);
+const selectedFriends = ref<number[]>([]);
+
+// 过滤后的好友列表
+const filteredShareFriendList = computed(() => {
+  if (!shareSearch.value) {
+    return shareFriendList.value;
+  }
+  const keyword = shareSearch.value.toLowerCase();
+  return shareFriendList.value.filter(friend => friend.name.toLowerCase().includes(keyword));
+});
+
+// 举报原因到违规类型的映射
+const violationTypeMap: Record<string, number> = {
+  用户头像: 1,
+  "名称/昵称": 2,
+  违法违规: 3,
+  "低俗色情、暴力恐怖、政治谣言": 4,
+  涉嫌诈骗: 5,
+  人身攻击: 6,
+  侵犯版权: 7,
+  恶意骚扰: 8,
+  "虚假/过度宣传": 9,
+  诱导点赞分享: 10,
+  传播人身安全: 11,
+  侵权举报: 12,
+  其他举报: 13
+};
+
+// 用户信息
+const userInfo = reactive({
+  name: targetUserName.value || "用户主页",
+  avatar: targetUserAvatar.value,
+  jianjie: "",
+  followCount: 0, // 关注数
+  fansCount: 0, // 粉丝数
+  likeCount: 0 // 获赞数
+});
+
+// 判断是否是当前用户自己的主页
+const isMyPage = computed(() => {
+  const currentUserStoreId = userStore.userInfo?.storeId;
+  const targetStoreId = targetUserId.value; // 目标用户的storeUserId
+
+  console.log("===== isMyPage 判断 =====");
+  console.log("当前用户storeId:", currentUserStoreId);
+  console.log("目标用户storeUserId:", targetStoreId);
+
+  // 通过 storeId 和 storeUserId 判断是否是当前用户的主页
+  const result = currentUserStoreId == targetStoreId;
+  console.log("是否是自己的主页:", result);
+
+  return result;
+});
+
+// 分页
+const pagination = reactive({
+  page: 1,
+  pageSize: 12,
+  total: 0
+});
+
+// 计算分页后的列表
+const paginatedList = computed(() => {
+  return contentList.value;
+});
+
+// 标签切换
+const handleTabClick = () => {
+  pagination.page = 1;
+  loadContentList();
+};
+
+// 分页大小改变
+const handleSizeChange = (val: number) => {
+  pagination.pageSize = val;
+  pagination.page = 1;
+  loadContentList();
+};
+
+// 当前页改变
+const handleCurrentChange = (val: number) => {
+  pagination.page = val;
+  loadContentList();
+};
+
+// 点击卡片
+const handleCardClick = async (item: any) => {
+  try {
+    // 解析媒体列表(支持多张图片和视频)
+    const mediaUrl = item.imagePath || item.coverUrl || "";
+    const mediaUrls = mediaUrl
+      .split(",")
+      .map((url: string) => url.trim())
+      .filter((url: string) => url);
+    const mediaList: MediaItem[] = mediaUrls.map((url: string) => ({
+      url,
+      type: url.toLowerCase().endsWith(".mp4") ? ("video" as const) : ("image" as const)
+    }));
+
+    const firstUrl = mediaUrls[0] || "";
+    const isVideo = firstUrl.toLowerCase().endsWith(".mp4");
+
+    currentDetail.value = {
+      ...item,
+      mediaList,
+      coverUrl: firstUrl,
+      type: isVideo ? "video" : "image"
+    };
+
+    detailDrawerVisible.value = true;
+
+    // 重置轮播索引
+    currentCarouselIndex.value = 0;
+  } catch (error) {
+    console.error("加载详情失败:", error);
+    ElMessage.error("加载详情失败");
+  }
+};
+
+// 关闭详情
+const handleCloseDetail = () => {
+  detailDrawerVisible.value = false;
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  setTimeout(() => {
+    currentDetail.value = null;
+    videoRefs.value = [];
+  }, 300);
+};
+
+// 设置视频引用
+const setVideoRef = (el: any, index: number) => {
+  if (el) {
+    videoRefs.value[index] = el as HTMLVideoElement;
+  }
+};
+
+// 轮播切换
+const handleCarouselChange = (newIndex: number) => {
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  currentCarouselIndex.value = newIndex;
+};
+
+// 视频播放
+const handleVideoPlay = (index: number) => {
+  // 暂停其他视频
+  videoRefs.value.forEach((video, i) => {
+    if (i !== index && video && !video.paused) {
+      video.pause();
+    }
+  });
+};
+
+// 详情页点赞
+const handleDetailLike = () => {
+  if (!currentDetail.value) return;
+
+  currentDetail.value.isLiked = !currentDetail.value.isLiked;
+  currentDetail.value.likeCount += currentDetail.value.isLiked ? 1 : -1;
+
+  ElMessage.success(currentDetail.value.isLiked ? "点赞成功" : "取消点赞");
+};
+
+// 显示评论
+const handleShowComments = async () => {
+  if (!currentDetail.value) return;
+  commentDrawerVisible.value = true;
+  await loadCommentList();
+};
+
+// 加载评论列表
+const loadCommentList = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    let params = {
+      businessId: String(currentDetail.value.id),
+      businessType: "2",
+      replyStatus: 0,
+      pageNum: 1,
+      pageSize: 100,
+      commentType: 0,
+      days: "",
+      phoneId: phoneId
+    };
+    const res: any = await commentList(params);
+    if (res.code === 200) {
+      commentListData.value = res.data.records || [];
+      // 更新评论总数
+      if (currentDetail.value) {
+        currentDetail.value.commentCount = res.data.total || 0;
+      }
+      console.log("评论列表:", commentListData.value);
+      console.log("评论总数:", res.data.total);
+    }
+  } catch (error) {
+    console.error("加载评论列表失败:", error);
+  }
+};
+
+// 点赞评论
+const handleLikeComment = async (comment: any) => {
+  try {
+    comment.isLiked = !comment.isLiked;
+    comment.likeCount = comment.isLiked ? (comment.likeCount || 0) + 1 : (comment.likeCount || 1) - 1;
+  } catch (error) {
+    console.error("点赞评论失败:", error);
+  }
+};
+
+// 回复评论
+const handleReplyComment = (comment: any) => {
+  replyingComment.value = comment;
+  commentInput.value = ``;
+  // 聚焦到输入框
+  setTimeout(() => {
+    const textarea = document.querySelector(".comment-input-wrapper textarea") as HTMLTextAreaElement;
+    if (textarea) {
+      textarea.focus();
+    }
+  }, 100);
+};
+
+// 取消回复
+const handleCancelReply = () => {
+  replyingComment.value = null;
+  commentInput.value = "";
+};
+
+// 提交评论
+const handleSubmitComment = async () => {
+  if (!commentInput.value.trim()) {
+    ElMessage.warning("请输入评论内容");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.error("动态信息不存在");
+    return;
+  }
+
+  try {
+    commentSubmitting.value = true;
+
+    const phone = userStore.userInfo?.phone || "";
+    const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 判断是回复评论还是评论动态
+    const isReply = !!replyingComment.value;
+
+    const params: any = {
+      replyId: isReply ? replyingComment.value.id : "", // 回复评论时传评论ID,否则为空
+      commentContent: commentInput.value,
+      businessType: "2",
+      businessId: String(currentDetail.value.id),
+      storeId: userStore.userInfo?.storeId || userStore.userInfo?.createdId,
+      commentStar: "",
+      phoneId: phoneId
+    };
+
+    const res: any = await saveComment(params);
+    if (res.code === 200) {
+      ElMessage.success(isReply ? "回复成功" : "评论成功");
+      commentInput.value = "";
+      replyingComment.value = null;
+      await loadCommentList();
+    } else {
+      ElMessage.error(res.message || (isReply ? "回复失败" : "评论失败"));
+    }
+  } catch (error) {
+    console.error("提交评论失败:", error);
+    ElMessage.error(replyingComment.value ? "回复失败" : "评论失败");
+  } finally {
+    commentSubmitting.value = false;
+  }
+};
+
+// 分享
+const handleShare = async () => {
+  shareDialogVisible.value = true;
+  await loadShareFriendList();
+};
+
+// 加载好友列表
+const loadShareFriendList = async () => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    const res: any = await getMutualAttention({
+      page: 1,
+      size: 1000,
+      fansId: fansId,
+      name: ""
+    });
+
+    if (res.code === 200) {
+      const dataList = res.data?.records || res.data?.list || res.data || [];
+      shareFriendList.value = dataList.map((item: any) => ({
+        id: item.id || item.userId,
+        name: item.userName || item.nickname || item.name || "用户",
+        avatar: item.userImage || item.avatar || item.headImg || "",
+        phoneId: item.phoneId || item.fansId || ""
+      }));
+      console.log("加载好友列表成功:", shareFriendList.value);
+    }
+  } catch (error) {
+    console.error("加载好友列表失败:", error);
+    ElMessage.error("加载好友列表失败");
+    shareFriendList.value = [];
+  }
+};
+
+// 选择好友
+const handleSelectFriend = (friend: ShareFriend) => {
+  const index = selectedFriends.value.indexOf(friend.id);
+  if (index > -1) {
+    // 已选择,取消选择
+    selectedFriends.value.splice(index, 1);
+  } else {
+    // 未选择,添加选择
+    selectedFriends.value.push(friend.id);
+  }
+};
+
+// 移除已选择的好友
+const handleRemoveFriend = (friendId: number) => {
+  const index = selectedFriends.value.indexOf(friendId);
+  if (index > -1) {
+    selectedFriends.value.splice(index, 1);
+  }
+};
+
+// 确认分享
+const handleConfirmShare = async () => {
+  if (selectedFriends.value.length === 0) {
+    ElMessage.warning("请选择要分享的好友");
+    return;
+  }
+
+  if (!currentDetail.value) {
+    ElMessage.error("动态信息不存在");
+    return;
+  }
+
+  try {
+    shareSubmitting.value = true;
+
+    // 调用 addTransferCount 接口,传递动态 id
+    const res: any = await addTransferCount({
+      id: currentDetail.value.id
+    });
+
+    if (res.code === 200) {
+      ElMessage.success(`已分享给 ${selectedFriends.value.length} 位好友`);
+      shareDialogVisible.value = false;
+
+      // 可以在这里更新动态的分享数(如果需要的话)
+      console.log("分享成功,动态ID:", currentDetail.value.id);
+    } else {
+      ElMessage.error(res.message || "分享失败");
+    }
+  } catch (error) {
+    console.error("分享失败:", error);
+    ElMessage.error("分享失败");
+  } finally {
+    shareSubmitting.value = false;
+  }
+};
+
+// 关闭分享对话框
+const handleCloseShareDialog = () => {
+  shareSearch.value = "";
+  selectedFriends.value = [];
+  shareFriendList.value = [];
+};
+
+// 关注/取消关注(顶部按钮)
+const handleFollow = async () => {
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 根据当前状态调用不同接口
+    if (isFollowed.value) {
+      // 已关注 → 取消关注
+      await cancelFollowed({
+        followedId: targetPhoneId.value, // 被关注用户phoneId
+        fansId: currentUserPhoneId // 当前用户phoneId (格式: store_xxx)
+      });
+      isFollowed.value = false;
+      ElMessage.success("取消关注成功");
+    } else {
+      // 未关注 → 关注
+      await toggleFollowUser({
+        followedId: targetPhoneId.value, // 被关注用户phoneId
+        fansId: currentUserPhoneId, // 当前用户phoneId (格式: store_xxx)
+        fansType: 2 // 2表示关注
+      });
+      isFollowed.value = true;
+      ElMessage.success("关注成功");
+    }
+  } catch (error) {
+    console.error("关注操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 详情页关注(右侧操作栏)
+const handleFollowInDetail = async () => {
+  try {
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    await toggleFollowUser({
+      followedId: targetPhoneId.value,
+      fansId: currentUserPhoneId,
+      fansType: 2
+    });
+
+    isFollowed.value = true;
+    ElMessage.success("关注成功");
+  } catch (error) {
+    console.error("关注操作失败:", error);
+    ElMessage.error("操作失败");
+  }
+};
+
+// 更多操作菜单
+const handleCommand = (command: string) => {
+  switch (command) {
+    case "report":
+      handleReportUser();
+      break;
+    case "block":
+      handleBlockUserClick();
+      break;
+  }
+};
+
+// 举报用户
+const handleReportUser = () => {
+  reportDialogVisible.value = true;
+};
+
+// 举报动态(从详情页)
+const handleReportDynamic = () => {
+  reportDialogVisible.value = true;
+};
+
+// 拉黑用户(点击菜单项)
+const handleBlockUserClick = () => {
+  handleBlockUser(false);
+};
+
+// 从详情页拉黑用户
+const handleBlockUserFromDrawer = () => {
+  handleBlockUser(false);
+};
+
+// 拉黑用户
+const handleBlockUser = async (skipConfirm: boolean = false) => {
+  try {
+    if (!skipConfirm) {
+      await ElMessageBox.confirm("拉黑后将不再看到该用户的动态,确定要拉黑吗?", "拉黑确认", {
+        confirmButtonText: "确定拉黑",
+        cancelButtonText: "取消",
+        type: "warning"
+      });
+    }
+
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+    const currentUserId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
+    const currentUserType = userStore.userInfo?.userType || userStore.userInfo?.type || 1;
+
+    // 从 targetPhoneId 解析被拉黑用户类型
+    const prefix = targetPhoneId.value.split("_")[0];
+    const blockedUserType = prefix === "store" ? 1 : 2;
+
+    await blockUser({
+      blockerType: currentUserType,
+      blockedType: blockedUserType,
+      blockerId: currentUserId,
+      blockedId: targetUserId.value
+    });
+
+    if (!skipConfirm) {
+      ElMessage.success("已拉黑该用户");
+    }
+
+    // 返回上一页
+    router.back();
+  } catch (error) {
+    if (!skipConfirm && error !== "cancel") {
+      console.error("拉黑失败:", error);
+      ElMessage.error("拉黑失败");
+    } else if (skipConfirm) {
+      throw error;
+    }
+  }
+};
+
+// 举报图片预览
+const handleReportPreview = (uploadFile: any) => {
+  console.log("预览图片", uploadFile);
+};
+
+// 移除举报图片
+const handleReportRemove = (uploadFile: any, uploadFiles: any[]) => {
+  // 已由 v-model:file-list 自动处理
+};
+
+// 举报图片上传前验证
+const beforeReportUpload = (file: File) => {
+  const isImage = file.type.startsWith("image/");
+  const isLt5M = file.size / 1024 / 1024 < 5;
+
+  if (!isImage) {
+    ElMessage.error("只能上传图片文件!");
+    return false;
+  }
+  if (!isLt5M) {
+    ElMessage.error("图片大小不能超过 5MB!");
+    return false;
+  }
+  return true;
+};
+
+// 自定义举报图片上传
+const handleReportUpload = async (options: any) => {
+  const { file, onSuccess, onError } = options;
+
+  try {
+    const uploadFormData = new FormData();
+    uploadFormData.append("file", file);
+
+    const response: any = await uploadImg(uploadFormData);
+
+    if (response && response.code === 200 && response.data && Array.isArray(response.data) && response.data.length > 0) {
+      onSuccess({
+        url: response.data[0]
+      });
+    } else {
+      ElMessage.error(response?.msg || "图片上传失败");
+      onError(new Error(response?.msg || "图片上传失败"));
+    }
+  } catch (error) {
+    console.error("图片上传失败:", error);
+    ElMessage.error("图片上传失败");
+    onError(error);
+  }
+};
+
+// 提交举报
+const handleSubmitReport = async () => {
+  if (!reportForm.reason) {
+    ElMessage.warning("请选择举报原因");
+    return;
+  }
+
+  if (reportForm.reason === "其他举报" && !reportForm.description.trim()) {
+    ElMessage.warning("请填写详细描述");
+    return;
+  }
+
+  reportSubmitting.value = true;
+
+  try {
+    const violationType = violationTypeMap[reportForm.reason];
+    const reportEvidenceImg = reportForm.fileList
+      .map((f: any) => f.url || f.response?.url)
+      .filter(Boolean)
+      .join(",");
+
+    const currentUserId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
+    const currentUserType = userStore.userInfo?.userType || userStore.userInfo?.type || 1;
+
+    // 从 targetPhoneId 解析被举报用户类型
+    const prefix = targetPhoneId.value.split("_")[0];
+    const reportedUserType = prefix === "store" ? 1 : 2;
+
+    // 根据手机号获取被举报人ID
+    let reportedUserId = targetUserId.value;
+    if (targetUserPhone.value) {
+      try {
+        const userRes = await getUserByPhone({ phone: targetUserPhone.value });
+        const userData = userRes.data as any;
+        if (userData && userData.id) {
+          reportedUserId = userData.id;
+        }
+      } catch (error) {
+        console.error("获取被举报人ID失败:", error);
+      }
+    }
+
+    await reportUserViolation({
+      dynamicsId: 0, // 举报用户不需要dynamicsId
+      reportContextType: "1", // 1表示举报用户
+      violationType: violationType,
+      otherReasonContent: reportForm.reason === "其他举报" ? reportForm.description : "",
+      reportEvidenceImg: reportEvidenceImg,
+      reportedUserId: reportedUserId,
+      reportedUserType: reportedUserType,
+      reportingUserId: currentUserId,
+      reportingUserType: currentUserType
+    });
+
+    if (reportForm.agreed) {
+      try {
+        await handleBlockUser(true);
+        ElMessage.success("举报提交成功,已拉黑该用户");
+      } catch (blockError) {
+        ElMessage.warning("举报提交成功,但拉黑失败");
+      }
+    } else {
+      ElMessage.success("举报提交成功,我们会尽快处理");
+    }
+
+    reportDialogVisible.value = false;
+  } catch (error) {
+    console.error("举报提交失败:", error);
+    ElMessage.error("举报提交失败");
+  } finally {
+    reportSubmitting.value = false;
+  }
+};
+
+// 关闭举报对话框
+const handleCloseReportDialog = () => {
+  reportForm.reason = "用户头像";
+  reportForm.description = "";
+  reportForm.fileList = [];
+  reportForm.agreed = false;
+};
+
+// 监听举报原因变化
+watch(
+  () => reportForm.reason,
+  newReason => {
+    if (newReason !== "其他举报") {
+      reportForm.description = "";
+    }
+  }
+);
+
+// 加载内容列表
+const loadContentList = async () => {
+  try {
+    if (!targetPhoneId.value) {
+      ElMessage.warning("用户信息异常");
+      return;
+    }
+
+    // 获取当前登录用户的phoneId
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    console.log("目标用户phoneId:", targetPhoneId.value);
+
+    const res = await getUserDynamicsList({
+      myselfPhoneId: currentUserPhoneId, // 当前登录用户的phoneId
+      phoneId: targetPhoneId.value, // 目标用户的phoneId
+      type: 2,
+      page: pagination.page,
+      size: pagination.pageSize
+    });
+
+    if (res.data) {
+      const responseData = res.data as any;
+      const list = responseData.lifeUserDynamics || [];
+
+      // 更新用户信息(从 storeUser 字段)
+      if (responseData.storeUser) {
+        const storeUserData = responseData.storeUser;
+        userInfo.name = storeUserData.storeName || storeUserData.userName || storeUserData.name || userInfo.name;
+        userInfo.avatar = storeUserData.avatar || storeUserData.headImg || userInfo.avatar;
+        userInfo.jianjie = storeUserData.jianjie || storeUserData.storeBlurb || storeUserData.bio || userInfo.jianjie || "";
+        userInfo.followCount = responseData.followListSum || 0;
+        userInfo.fansCount = responseData.fansListSum || 0;
+        userInfo.likeCount = responseData.likeListSum || 0;
+      }
+
+      // 更新关注状态(0未关注,1已关注)
+      const isFollowedValue = responseData.isFollowed ?? responseData.isFollow ?? 0;
+      isFollowed.value = isFollowedValue === 1;
+
+      contentList.value = list.map((item: any) => ({
+        id: item.id || item.dynamicId,
+        title: item.title || item.content || item.dynamicContent || "",
+        coverUrl: item.imagePath || item.coverUrl || "",
+        viewCount: item.viewCount || 0,
+        likeCount: item.likeCount || item.praiseCount || 0,
+        isLiked: item.isLiked || item.isPraise || false,
+        createTime: item.createTime || item.createDate || ""
+      }));
+
+      pagination.total = responseData.total || responseData.totalCount || list.length;
+    }
+  } catch (error) {
+    console.error("加载动态失败:", error);
+    ElMessage.error("加载动态失败");
+  }
+};
+
+// 初始化
+onMounted(() => {
+  loadContentList();
+});
+</script>
+
+<style scoped lang="scss">
+.user-dynamic-container {
+  min-height: calc(100vh - 120px);
+  padding: 20px;
+  background: #ffffff;
+
+  // 用户信息卡片
+  .user-card {
+    padding: 24px;
+    margin-bottom: 20px;
+    background: #ffffff;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    .user-header {
+      display: flex;
+      align-items: flex-start;
+      justify-content: space-between;
+      margin-bottom: 16px;
+      .user-avatar-section {
+        display: flex;
+        flex: 1;
+        gap: 16px;
+        align-items: center;
+        .user-avatar-large {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 80px;
+          height: 80px;
+          overflow: hidden;
+          background: #f5f7fa;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+        .user-info-text {
+          flex: 1;
+          .user-name {
+            margin-bottom: 8px;
+            font-size: 20px;
+            font-weight: 600;
+            color: #303133;
+          }
+          .user-stats {
+            display: flex;
+            gap: 8px;
+            align-items: center;
+            font-size: 14px;
+            color: #606266;
+            .stat-item {
+              cursor: pointer;
+              transition: color 0.3s;
+              &:hover {
+                color: #409eff;
+              }
+            }
+            .stat-divider {
+              color: #dcdfe6;
+            }
+          }
+        }
+      }
+      .user-actions {
+        display: flex;
+        gap: 12px;
+        align-items: center;
+        :deep(.el-button) {
+          min-width: 80px;
+        }
+      }
+    }
+    .user-bio {
+      font-size: 14px;
+      line-height: 1.6;
+      color: #606266;
+    }
+  }
+
+  // 标签页区域
+  .tabs-section {
+    margin-bottom: 20px;
+    border-bottom: 1px solid #e4e7ed;
+    :deep(.el-tabs) {
+      .el-tabs__header {
+        margin-bottom: 0;
+      }
+      .el-tabs__nav-wrap::after {
+        display: none;
+      }
+    }
+  }
+
+  // 内容区域
+  .content-section {
+    margin-top: 20px;
+  }
+
+  // 内容网格布局
+  .content-grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 16px;
+    margin-bottom: 20px;
+
+    @media (width <= 1400px) {
+      grid-template-columns: repeat(3, 1fr);
+    }
+
+    @media (width <= 1024px) {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    @media (width <= 768px) {
+      grid-template-columns: repeat(1, 1fr);
+    }
+  }
+
+  // 内容卡片
+  .content-card {
+    cursor: pointer;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    transition: all 0.3s;
+    &:hover {
+      box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+      transform: translateY(-2px);
+    }
+
+    // 封面区域
+    .content-cover-wrapper {
+      position: relative;
+      width: 100%;
+      aspect-ratio: 16 / 9;
+      margin-bottom: 8px;
+      overflow: hidden;
+      background: #f5f7fa;
+      border-radius: 8px;
+      .content-cover {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      .cover-placeholder {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+        background: #f5f7fa;
+      }
+    }
+
+    // 底部信息
+    .content-footer {
+      display: flex;
+      gap: 4px;
+      align-items: center;
+      padding: 0 4px;
+      .view-count {
+        font-size: 13px;
+        color: #666666;
+      }
+    }
+  }
+
+  // 空状态
+  .empty-section {
+    padding: 80px 0;
+    text-align: center;
+  }
+
+  // 分页
+  .pagination-section {
+    display: flex;
+    justify-content: center;
+    padding: 20px 0;
+    margin-top: 30px;
+  }
+
+  // 详情 Drawer
+  :deep(.detail-drawer) {
+    .el-drawer__header {
+      position: absolute;
+      top: 0;
+      right: 0;
+      left: 0;
+      z-index: 10;
+      padding: 0;
+      margin: 0;
+      background: transparent;
+    }
+    .el-drawer__body {
+      padding: 0;
+      background: #000000;
+    }
+    .drawer-header {
+      padding: 20px;
+      .close-btn {
+        padding: 8px;
+        font-size: 24px;
+        color: #ffffff;
+        background: #ffffff;
+      }
+    }
+    .detail-content {
+      position: relative;
+      display: flex;
+      width: 100%;
+      height: 100%;
+
+      // 主内容区域
+      .detail-main {
+        display: flex;
+        flex: 1;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 80px 120px 40px 40px;
+        .media-container {
+          position: relative;
+          display: flex;
+          flex: 1;
+          align-items: center;
+          justify-content: center;
+          width: 100%;
+          max-width: 800px;
+          min-height: 400px;
+          .media-carousel {
+            width: 100%;
+            height: 100%;
+            min-height: 400px;
+            max-height: 70vh;
+            :deep(.el-carousel__container) {
+              height: 100% !important;
+              min-height: 400px;
+            }
+            :deep(.el-carousel__item) {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              background: transparent;
+            }
+            :deep(.el-carousel__arrow) {
+              width: 48px;
+              height: 48px;
+              font-size: 24px;
+              color: #ffffff;
+              background-color: rgb(0 0 0 / 40%);
+              border: none;
+              &:hover {
+                background-color: rgb(0 0 0 / 60%);
+              }
+            }
+            :deep(.el-carousel__indicators) {
+              bottom: -30px;
+              .el-carousel__indicator {
+                .el-carousel__button {
+                  width: 8px;
+                  height: 8px;
+                  background-color: rgb(255 255 255 / 40%);
+                  border-radius: 50%;
+                }
+                &.is-active .el-carousel__button {
+                  background-color: #409eff;
+                }
+              }
+            }
+          }
+          .detail-media {
+            max-width: 100%;
+            max-height: 65vh;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+          .detail-image {
+            max-width: 100%;
+            max-height: 65vh;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+          .detail-video {
+            width: 100%;
+            max-height: 65vh;
+            background: #000000;
+            border-radius: 8px;
+          }
+          .media-placeholder {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 400px;
+            height: 400px;
+            background: rgb(255 255 255 / 5%);
+            border-radius: 8px;
+          }
+          .media-counter {
+            position: absolute;
+            right: 16px;
+            bottom: -24px;
+            padding: 4px 12px;
+            font-size: 14px;
+            color: #ffffff;
+            background: rgb(0 0 0 / 50%);
+            border-radius: 12px;
+          }
+        }
+        .detail-info {
+          width: 100%;
+          max-width: 800px;
+          padding: 20px 0;
+          .author-info {
+            display: flex;
+            gap: 12px;
+            align-items: center;
+            margin-bottom: 16px;
+            .author-avatar {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              width: 40px;
+              height: 40px;
+              overflow: hidden;
+              background: rgb(255 255 255 / 10%);
+              border-radius: 50%;
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .author-details {
+              flex: 1;
+              .author-name {
+                margin-bottom: 4px;
+                font-size: 16px;
+                font-weight: 500;
+                color: #ffffff;
+              }
+              .publish-time {
+                font-size: 13px;
+                color: rgb(255 255 255 / 60%);
+              }
+            }
+          }
+          .detail-description {
+            font-size: 15px;
+            line-height: 1.6;
+            color: #ffffff;
+            p {
+              margin: 0;
+            }
+          }
+        }
+      }
+
+      // 右侧操作栏
+      .action-bar {
+        position: fixed;
+        right: 40px;
+        bottom: 100px;
+        z-index: 10;
+        display: flex;
+        flex-direction: column;
+        gap: 24px;
+        .action-item {
+          display: flex;
+          flex-direction: column;
+          gap: 6px;
+          align-items: center;
+          cursor: pointer;
+          transition: transform 0.3s;
+          &:hover {
+            transform: scale(1.1);
+          }
+          &.author-action {
+            cursor: default;
+            &:hover {
+              transform: none;
+            }
+          }
+          .action-avatar {
+            position: relative;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 48px;
+            height: 48px;
+            overflow: visible;
+            background: rgb(255 255 255 / 20%);
+            border: 2px solid #ffffff;
+            border-radius: 50%;
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+              border-radius: 50%;
+            }
+            .follow-badge {
+              position: absolute;
+              right: -4px;
+              bottom: -4px;
+              z-index: 2;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              width: 24px;
+              height: 24px;
+              cursor: pointer;
+              background: #409eff;
+              border: 2px solid #ffffff;
+              border-radius: 50%;
+              transition: transform 0.2s;
+              &:hover {
+                transform: scale(1.15);
+              }
+            }
+          }
+          .action-icon {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 48px;
+            height: 48px;
+            background: rgb(0 0 0 / 50%);
+            backdrop-filter: blur(10px);
+            border-radius: 50%;
+            &.follow-icon {
+              background: #409eff;
+            }
+          }
+          .action-count {
+            font-size: 13px;
+            color: #ffffff;
+            text-align: center;
+            text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
+          }
+        }
+      }
+    }
+  }
+
+  // 更多操作 Popover
+  :deep(.more-actions-popover) {
+    min-width: 120px;
+    padding: 8px 0;
+    background: rgb(0 0 0 / 90%);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgb(255 255 255 / 20%);
+    .more-actions-menu {
+      .menu-item {
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        padding: 10px 16px;
+        font-size: 14px;
+        color: #ffffff;
+        cursor: pointer;
+        transition: background 0.3s;
+        &:hover {
+          background: rgb(255 255 255 / 10%);
+        }
+        .el-icon {
+          font-size: 18px;
+        }
+      }
+    }
+  }
+
+  // 举报对话框和分享对话框
+  :deep(.el-dialog) {
+    .report-dialog-content {
+      .report-tip {
+        margin-bottom: 20px;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #606266;
+      }
+      .report-reasons {
+        margin-bottom: 20px;
+        .el-radio-group {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 12px 16px;
+          .el-radio {
+            height: auto;
+            margin-right: 0;
+            white-space: nowrap;
+            .el-radio__label {
+              font-size: 14px;
+              color: #303133;
+            }
+          }
+        }
+      }
+      .report-description {
+        margin-bottom: 20px;
+        :deep(.el-textarea__inner) {
+          font-size: 14px;
+        }
+      }
+      .report-upload {
+        margin-bottom: 20px;
+        .upload-title {
+          margin-bottom: 12px;
+          font-size: 14px;
+          font-weight: 500;
+          color: #303133;
+        }
+        :deep(.el-upload-list) {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+        }
+        :deep(.el-upload--picture-card) {
+          width: 100px;
+          height: 100px;
+          border-radius: 4px;
+        }
+        :deep(.el-upload-list--picture-card .el-upload-list__item) {
+          width: 100px;
+          height: 100px;
+          margin: 0;
+          border-radius: 4px;
+        }
+      }
+      .report-agreement {
+        .el-checkbox {
+          .el-checkbox__label {
+            font-size: 14px;
+            color: #606266;
+          }
+        }
+      }
+    }
+
+    // 分享对话框
+    .share-dialog-content {
+      .search-box {
+        margin-bottom: 16px;
+      }
+      .share-friend-list {
+        max-height: 400px;
+        margin-bottom: 16px;
+        overflow-y: auto;
+        .share-friend-item {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 12px;
+          cursor: pointer;
+          border-radius: 8px;
+          transition: background-color 0.3s;
+          &:hover {
+            background-color: #f5f7fa;
+          }
+          .friend-info {
+            display: flex;
+            gap: 12px;
+            align-items: center;
+            .friend-avatar {
+              display: flex;
+              flex-shrink: 0;
+              align-items: center;
+              justify-content: center;
+              width: 40px;
+              height: 40px;
+              overflow: hidden;
+              background: #f5f5f5;
+              border-radius: 50%;
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .friend-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: #303133;
+            }
+          }
+        }
+      }
+      .selected-friends {
+        padding: 12px;
+        background: #f5f7fa;
+        border-radius: 8px;
+        .selected-title {
+          margin-bottom: 8px;
+          font-size: 13px;
+          color: #606266;
+        }
+        .selected-list {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+          .el-tag {
+            max-width: 120px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+        }
+      }
+    }
+    .dialog-footer {
+      display: flex;
+      gap: 12px;
+      justify-content: flex-end;
+    }
+  }
+
+  // 评论侧边栏样式
+  .comment-list-container {
+    flex: 1;
+    height: calc(100vh - 200px);
+    padding: 0 20px;
+    overflow-y: auto;
+    .comment-list {
+      .comment-item {
+        display: flex;
+        gap: 12px;
+        padding: 16px 0;
+        border-bottom: 1px solid #f0f0f0;
+        &:last-child {
+          border-bottom: none;
+        }
+        .comment-avatar {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          justify-content: center;
+          width: 40px;
+          height: 40px;
+          overflow: hidden;
+          background: #f5f5f5;
+          border-radius: 50%;
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+        .comment-content-wrapper {
+          flex: 1;
+          min-width: 0;
+          .comment-header,
+          .store-comment-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 8px;
+            .comment-user-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: #303133;
+            }
+            .comment-time {
+              font-size: 12px;
+              color: #909399;
+            }
+          }
+          .comment-text {
+            margin-bottom: 8px;
+            font-size: 14px;
+            line-height: 1.6;
+            color: #606266;
+            word-break: break-all;
+          }
+
+          // 商家回复样式
+          .store-comment-wrapper {
+            padding: 12px;
+            margin-top: 12px;
+            background: #f5f7fa;
+            border-radius: 8px;
+            .store-comment-item {
+              display: flex;
+              gap: 10px;
+              .store-comment-avatar {
+                display: flex;
+                flex-shrink: 0;
+                align-items: center;
+                justify-content: center;
+                width: 32px;
+                height: 32px;
+                overflow: hidden;
+                background: #ffffff;
+                border-radius: 50%;
+                img {
+                  width: 100%;
+                  height: 100%;
+                  object-fit: cover;
+                }
+              }
+              .store-comment-content {
+                flex: 1;
+                min-width: 0;
+                .store-comment-header {
+                  display: flex;
+                  align-items: center;
+                  justify-content: space-between;
+                  margin-bottom: 6px;
+                  .store-comment-user-name {
+                    font-size: 13px;
+                    font-weight: 500;
+                    color: #409eff;
+                  }
+                  .store-comment-time {
+                    font-size: 11px;
+                    color: #909399;
+                  }
+                }
+                .store-comment-text {
+                  display: flex;
+                  align-items: center;
+                  justify-content: space-between;
+                  font-size: 13px;
+                  line-height: 1.5;
+                  color: #606266;
+                  word-break: break-all;
+                }
+              }
+            }
+          }
+          .comment-actions {
+            display: flex;
+            gap: 20px;
+            .comment-action-item {
+              display: flex;
+              gap: 4px;
+              align-items: center;
+              font-size: 13px;
+              color: #909399;
+              cursor: pointer;
+              transition: color 0.3s;
+              &:hover {
+                color: #409eff;
+              }
+              span {
+                font-size: 13px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  .comment-input-wrapper {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    padding: 16px 20px;
+    background: #ffffff;
+    border-top: 1px solid #e4e7ed;
+    box-shadow: 0 -2px 8px rgb(0 0 0 / 5%);
+    .reply-hint {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 8px 12px;
+      margin-bottom: 8px;
+      background: #f5f7fa;
+      border-radius: 4px;
+      .reply-text {
+        font-size: 13px;
+        color: #409eff;
+      }
+      .cancel-reply {
+        font-size: 16px;
+        color: #909399;
+        cursor: pointer;
+        transition: color 0.3s;
+        &:hover {
+          color: #f56c6c;
+        }
+      }
+    }
+    :deep(.el-textarea) {
+      margin-bottom: 12px;
+    }
+    .el-button {
+      width: 100%;
+    }
+  }
+}
+</style>

+ 24 - 1
src/views/login/components/LoginForm.vue

@@ -95,6 +95,8 @@ import { useKeepAliveStore } from "@/stores/modules/keepAlive";
 import { initDynamicRouter } from "@/routers/modules/dynamicRouter";
 import type { ElForm } from "element-plus";
 import md5 from "md5";
+import { aiLogin } from "@/api/indexAi";
+import { localSet } from "@/utils";
 
 const router = useRouter();
 const userStore = useUserStore();
@@ -256,8 +258,29 @@ const handleLogin = async () => {
 
       const { data } = (await loginApi(loginParams)) as { data: Login.ResLogin };
 
-      if (data.result) {
+      if (data) {
         userStore.setToken(data.token);
+        console.log("AI登录");
+
+        // 保存用户信息到localStorage,供AI登录使用
+        const userInfo = {
+          userInfo: {
+            nickName: formData.username, // 使用登录时的用户名
+            phone: formData.username, // 假设用户名就是手机号
+            password: loginType.value === "password" ? formData.password : undefined // 保存密码用于AI登录
+          },
+          token: data.token
+        };
+        localSet("geeker-user", userInfo);
+
+        // 旧登录接口成功后,调用AI登录接口
+        try {
+          console.log("AI登录");
+          await aiLogin();
+        } catch (error) {
+          console.error("AI服务登录失败:", error);
+          // AI登录失败不影响主系统登录流程,静默处理
+        }
 
         // 添加动态路由
         await initDynamicRouter();

+ 8 - 0
src/views/login/index.vue

@@ -614,6 +614,7 @@ import { getMerchantByPhone } from "@/api/modules/homeEntry";
 import { localGet, localRemove, localSet } from "@/utils";
 import * as path from "node:path";
 import { checkMenuClickPermission } from "@/utils/permission";
+import { aiLogin } from "@/api/indexAi";
 
 const router = useRouter();
 const route = useRoute();
@@ -935,6 +936,13 @@ const handleLogin = async () => {
         } else {
           localRemove("mealsFlag");
         }
+        // 旧登录接口成功后,调用AI登录接口
+        // AI登录失败不影响主系统登录流程,静默处理
+        // try {
+        //   await aiLogin();
+        // } catch (error) {
+        //   console.error("AI服务登录失败:", error);
+        // }
         // 登录成功后,立即获取完整的用户信息(包括头像)
         if (res.data.phone) {
           try {

+ 200 - 5
src/views/operationManagement/newActivity.vue

@@ -59,6 +59,29 @@
               <el-input v-model="activityModel.couponQuantity" placeholder="请输入" maxlength="5" />
             </el-form-item>
 
+            <!-- AI生成活动宣传图 -->
+            <el-form-item label="活动宣传图描述">
+              <div class="ai-generate-wrapper">
+                <el-input
+                  v-model="aiPromptText"
+                  :rows="4"
+                  maxlength="200"
+                  placeholder="请描述您想举办的活动图,AI助手会帮您自动生成海报图~"
+                  show-word-limit
+                  type="textarea"
+                />
+                <el-button
+                  :disabled="!aiPromptText.trim() || aiGenerating"
+                  :loading="aiGenerating"
+                  class="generate-btn"
+                  type="primary"
+                  @click="handleAIGenerate"
+                >
+                  {{ aiGenerating ? "生成中..." : "生成图片" }}
+                </el-button>
+              </div>
+            </el-form-item>
+
             <!-- 活动标题图 -->
             <el-form-item label="活动标题图" prop="activityTitleImage">
               <div class="upload-item-wrapper">
@@ -75,10 +98,11 @@
                     :on-preview="handlePictureCardPreview"
                     :on-remove="handleTitleRemove"
                     :show-file-list="true"
+                    :class="{ 'upload-hidden': aiGenerated }"
                     list-type="picture-card"
                   >
                     <template #trigger>
-                      <div v-if="titleFileList.length < 1" class="upload-trigger-card el-upload--picture-card">
+                      <div v-if="titleFileList.length < 1 && !aiGenerated" class="upload-trigger-card el-upload--picture-card">
                         <el-icon>
                           <Plus />
                         </el-icon>
@@ -86,7 +110,9 @@
                     </template>
                   </el-upload>
                 </div>
-                <div class="upload-hint">请上传21:9尺寸图片效果更佳,支持jpg、jpeg、png格式,上传图片不得超过20M</div>
+                <div v-if="!aiGenerated" class="upload-hint">
+                  请上传21:9尺寸图片效果更佳,支持jpg、jpeg、png格式,上传图片不得超过20M
+                </div>
               </div>
             </el-form-item>
 
@@ -106,10 +132,11 @@
                     :on-preview="handlePictureCardPreview"
                     :on-remove="handleDetailRemove"
                     :show-file-list="true"
+                    :class="{ 'upload-hidden': aiGenerated }"
                     list-type="picture-card"
                   >
                     <template #trigger>
-                      <div v-if="detailFileList.length < 1" class="upload-trigger-card el-upload--picture-card">
+                      <div v-if="detailFileList.length < 1 && !aiGenerated" class="upload-trigger-card el-upload--picture-card">
                         <el-icon>
                           <Plus />
                         </el-icon>
@@ -117,7 +144,7 @@
                     </template>
                   </el-upload>
                 </div>
-                <div class="upload-hint">请上传竖版图片,支持jpg、jpeg、png格式,上传图片不得超过20M</div>
+                <div v-if="!aiGenerated" class="upload-hint">请上传竖版图片,支持jpg、jpeg、png格式,上传图片不得超过20M</div>
               </div>
             </el-form-item>
           </div>
@@ -156,7 +183,8 @@ import {
   getActivityDetail,
   getActivityRuleOptions,
   getCouponList,
-  updateActivity
+  updateActivity,
+  generatePromotionImage
 } from "@/api/modules/operationManagement";
 import { uploadContractImage } from "@/api/modules/licenseManagement";
 import { localGet } from "@/utils";
@@ -177,6 +205,11 @@ const ruleFormRef = ref<FormInstance>();
 // 优惠券列表
 const couponList = ref<any[]>([]);
 
+// AI生成相关
+const aiPromptText = ref<string>("");
+const aiGenerating = ref<boolean>(false);
+const aiGenerated = ref<boolean>(false);
+
 // 文件上传相关
 const titleFileList = ref<UploadFile[]>([]);
 const detailFileList = ref<UploadFile[]>([]);
@@ -435,6 +468,12 @@ const handleTitleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) =>
     URL.revokeObjectURL(file.url);
   }
   titleFileList.value = [...uploadFiles];
+
+  // 如果删除的是AI生成的图片,重置AI生成状态
+  if (aiGenerated.value && uploadFiles.length === 0) {
+    aiGenerated.value = false;
+  }
+
   ElMessage.success("图片已删除");
 };
 
@@ -459,6 +498,12 @@ const handleDetailRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) =>
     URL.revokeObjectURL(file.url);
   }
   detailFileList.value = [...uploadFiles];
+
+  // 如果删除的是AI生成的图片,重置AI生成状态
+  if (aiGenerated.value && uploadFiles.length === 0) {
+    aiGenerated.value = false;
+  }
+
   ElMessage.success("图片已删除");
 };
 
@@ -828,6 +873,102 @@ const goBack = () => {
 };
 
 /**
+ * AI生成活动宣传图
+ */
+const handleAIGenerate = async () => {
+  return;
+  if (!aiPromptText.value.trim()) {
+    ElMessage.warning("请输入活动宣传图描述");
+    return;
+  }
+
+  aiGenerating.value = true;
+
+  try {
+    const res: any = await generatePromotionImage({
+      text: aiPromptText.value.trim()
+    });
+
+    if (res && res.code === 200 && res.data) {
+      const { banner_image, vertical_image } = res.data;
+
+      // 处理横版图片(活动标题图)
+      if (banner_image && banner_image.image_base64) {
+        const bannerFile = await base64ToFile(banner_image.image_base64, `banner_${Date.now()}.png`, "image/png");
+        const bannerUploadFile: UploadFile = {
+          uid: Date.now(),
+          name: bannerFile.name,
+          status: "success",
+          percentage: 100,
+          url: `data:image/png;base64,${banner_image.image_base64}`,
+          raw: bannerFile
+        } as UploadFile;
+
+        titleFileList.value = [bannerUploadFile];
+        titleImageUrl.value = `data:image/png;base64,${banner_image.image_base64}`;
+        activityModel.value.activityTitleImg = { url: titleImageUrl.value };
+        activityModel.value.activityTitleImage = titleImageUrl.value;
+      }
+
+      // 处理竖版图片(活动详情图)
+      if (vertical_image && vertical_image.image_base64) {
+        const verticalFile = await base64ToFile(vertical_image.image_base64, `vertical_${Date.now()}.png`, "image/png");
+        const verticalUploadFile: UploadFile = {
+          uid: Date.now() + 1,
+          name: verticalFile.name,
+          status: "success",
+          percentage: 100,
+          url: `data:image/png;base64,${vertical_image.image_base64}`,
+          raw: verticalFile
+        } as UploadFile;
+
+        detailFileList.value = [verticalUploadFile];
+        detailImageUrl.value = `data:image/png;base64,${vertical_image.image_base64}`;
+        activityModel.value.activityDetailImg = { url: detailImageUrl.value };
+        activityModel.value.activityDetailImage = detailImageUrl.value;
+      }
+
+      aiGenerated.value = true;
+
+      // 触发表单验证
+      nextTick(() => {
+        ruleFormRef.value?.validateField("activityTitleImage");
+        ruleFormRef.value?.validateField("activityDetailImage");
+      });
+
+      ElMessage.success("图片生成成功");
+    } else {
+      ElMessage.error(res?.message || "生成失败");
+    }
+  } catch (error: any) {
+    console.error("AI生成失败:", error);
+    ElMessage.error(error?.message || "生成失败,请稍后重试");
+  } finally {
+    aiGenerating.value = false;
+  }
+};
+
+/**
+ * Base64转File对象
+ */
+const base64ToFile = async (base64: string, filename: string, mimeType: string): Promise<File> => {
+  // 如果base64包含data URL前缀,去除它
+  const base64Data = base64.includes(",") ? base64.split(",")[1] : base64;
+
+  // 将base64转换为二进制数据
+  const byteCharacters = atob(base64Data);
+  const byteNumbers = new Array(byteCharacters.length);
+  for (let i = 0; i < byteCharacters.length; i++) {
+    byteNumbers[i] = byteCharacters.charCodeAt(i);
+  }
+  const byteArray = new Uint8Array(byteNumbers);
+  const blob = new Blob([byteArray], { type: mimeType });
+
+  // 创建File对象
+  return new File([blob], filename, { type: mimeType });
+};
+
+/**
  * 提交表单
  */
 const handleSubmit = async () => {
@@ -841,6 +982,37 @@ const handleSubmit = async () => {
 
   await ruleFormRef.value.validate(async valid => {
     if (valid) {
+      // 如果是AI生成的图片,需要先上传
+      if (aiGenerated.value) {
+        try {
+          // 上传标题图
+          if (titleFileList.value.length > 0 && titleFileList.value[0].raw) {
+            const titleFormData = new FormData();
+            titleFormData.append("file", titleFileList.value[0].raw);
+            titleFormData.append("user", "text");
+            const titleResult: any = await uploadContractImage(titleFormData);
+            if (titleResult?.code === 200 && titleResult.data) {
+              titleImageUrl.value = titleResult.data[0];
+            }
+          }
+
+          // 上传详情图
+          if (detailFileList.value.length > 0 && detailFileList.value[0].raw) {
+            const detailFormData = new FormData();
+            detailFormData.append("file", detailFileList.value[0].raw);
+            detailFormData.append("user", "text");
+            const detailResult: any = await uploadContractImage(detailFormData);
+            if (detailResult?.code === 200 && detailResult.data) {
+              detailImageUrl.value = detailResult.data[0];
+            }
+          }
+        } catch (error) {
+          console.error("上传AI生成图片失败:", error);
+          ElMessage.error("上传图片失败");
+          return;
+        }
+      }
+
       const [startTime, endTime] = activityModel.value.activityTimeRange || [];
       const params: any = {
         activityName: activityModel.value.activityName,
@@ -935,6 +1107,22 @@ const handleSubmit = async () => {
   }
 }
 
+/* AI生成包装器 */
+.ai-generate-wrapper {
+  display: flex;
+  gap: 12px;
+  align-items: flex-start;
+  width: 100%;
+  :deep(.el-textarea) {
+    flex: 1;
+  }
+  .generate-btn {
+    flex-shrink: 0;
+    align-self: flex-start;
+    margin-top: 2px;
+  }
+}
+
 /* 上传项容器 */
 .upload-item-wrapper {
   display: flex;
@@ -950,6 +1138,13 @@ const handleSubmit = async () => {
   color: #909399;
 }
 
+/* 隐藏上传触发器 */
+:deep(.upload-hidden) {
+  .el-upload--picture-card {
+    display: none !important;
+  }
+}
+
 /* 规则表格容器 */
 .rule-table-container {
   margin-top: 20px;

+ 4 - 1
vite.config.ts

@@ -43,7 +43,10 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
       port: viteEnv.VITE_PORT,
       open: viteEnv.VITE_OPEN,
       cors: true,
-      proxy: createProxy(viteEnv.VITE_PROXY)
+      proxy: {
+        ...createProxy(viteEnv.VITE_PROXY),
+        ...createProxy(viteEnv.VITE_PROXY_AI)
+      }
     },
     plugins: [tailwindcss(), createVitePlugins(viteEnv)],
     esbuild: {