Bläddra i källkod

接口对接 黑名单列表。拉黑,解除拉黑,动态广场列表,关注取消关注,动态详情,关注详情,发布动态,他人主页

sunshibo 1 vecka sedan
förälder
incheckning
d55f93d98a

+ 133 - 4
src/api/modules/dynamicManagement.ts

@@ -1,11 +1,12 @@
 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;
@@ -14,11 +15,61 @@ export const getDynamicList = (params: {
   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);
@@ -90,21 +141,99 @@ 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: {
+  // 动态ID
+  userId: string;
+  huifuId: number; // 当前用户phoneId
+  type: number; // 类型:2表示点赞
+}) => {
+  return httpStore.post(PORT_NONE + `/comment/like`, params);
+};
+
 // 举报动态
 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);

+ 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 });
 };
 
 // 视频上传

+ 42 - 26
src/assets/json/authMenuList.json

@@ -688,32 +688,48 @@
           }
         },
         {
-        "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/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",

+ 49 - 32
src/layouts/components/Header/components/BlackListDialog.vue

@@ -39,15 +39,22 @@
 import { ref } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { Avatar } from "@element-plus/icons-vue";
-// import { getBlacklist, unblockUser } from '@/api/modules/user';
+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[]>([]);
@@ -68,34 +75,40 @@ defineExpose({
 // 加载黑名单列表
 const loadBlacklist = async () => {
   try {
-    // TODO: 集成真实接口时,取消下面的注释
-    // const res = await getBlacklist();
-    // blacklist.value = res.data.map(user => ({ ...user, unblocking: false }));
-
-    // 临时方案:使用模拟数据
-    const mockData: BlacklistUser[] = [
-      {
-        id: 1,
-        name: "甜品店",
-        avatar: "",
-        unblocking: false
-      },
-      {
-        id: 2,
-        name: "甜品店",
-        avatar: "",
-        unblocking: false
-      },
-      {
-        id: 3,
-        name: "甜品店",
-        avatar: "",
-        unblocking: false
-      }
-    ];
+    // 获取当前用户信息
+    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);
 
-    blacklist.value = mockData;
+    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("加载黑名单失败");
   }
 };
@@ -111,11 +124,14 @@ const handleUnblock = async (user: BlacklistUser) => {
 
     user.unblocking = true;
 
-    // TODO: 集成真实接口时,取消下面的注释
-    // await unblockUser({ userId: user.id });
-
-    // 模拟延迟
-    await new Promise(resolve => setTimeout(resolve, 500));
+    // 调用取消拉黑接口
+    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);
@@ -127,6 +143,7 @@ const handleUnblock = async (user: BlacklistUser) => {
     ElMessage.success("解除拉黑成功");
   } catch (error) {
     if (error !== "cancel") {
+      console.error("解除拉黑失败:", error);
       ElMessage.error("解除拉黑失败");
     }
   } finally {

+ 1151 - 39
src/views/dynamicManagement/index.vue

@@ -16,9 +16,13 @@
       <!-- 动态卡片网格 -->
       <div class="dynamic-grid">
         <div v-for="item in paginatedList" :key="item.id" class="dynamic-card" @click="handleCardClick(item)">
-          <!-- 图片区域 -->
+          <!-- 图片/视频区域 -->
           <div class="dynamic-image-wrapper">
-            <img v-if="item.imageUrl" :src="item.imageUrl" :alt="item.title" class="dynamic-image" />
+            <!-- 视频 -->
+            <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 />
@@ -73,17 +77,301 @@
         @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">
+            <!-- 视频 -->
+            <video
+              v-if="currentDetail.isVideo && currentDetail.imageUrl"
+              :src="currentDetail.imageUrl"
+              class="detail-media"
+              controls
+            />
+            <!-- 图片 -->
+            <img
+              v-else-if="currentDetail.imageUrl"
+              :src="currentDetail.imageUrl"
+              :alt="currentDetail.title"
+              class="detail-media"
+            />
+            <!-- 占位符 -->
+            <div v-else class="media-placeholder">
+              <el-icon :size="80" color="#dcdfe6">
+                <Picture />
+              </el-icon>
+            </div>
+          </div>
+
+          <!-- 底部信息 -->
+          <div class="detail-info">
+            <div class="author-info">
+              <div class="author-avatar">
+                <img v-if="currentDetail.author?.avatar" :src="currentDetail.author.avatar" :alt="currentDetail.author.name" />
+                <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" :src="currentDetail.author.avatar" :alt="currentDetail.author.name" />
+              <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.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 }}
+            </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" @click="handleDeleteDynamic">
+                  <el-icon :size="18">
+                    <Delete />
+                  </el-icon>
+                  <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-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="dynamicManagementIndex">
-import { ref, reactive, computed, onMounted } from "vue";
+import { ref, reactive, computed, onMounted, watch } from "vue";
 import { useRouter } from "vue-router";
-import { ElMessage } from "element-plus";
-import { Picture, Avatar, Star } from "@element-plus/icons-vue";
-// import { getDynamicList, likeDynamic, unlikeDynamic } from "@/api/modules/dynamicManagement";
+import { ElMessage, ElMessageBox } from "element-plus";
+import {
+  Picture,
+  Avatar,
+  Star,
+  StarFilled,
+  Close,
+  ChatDotRound,
+  Share,
+  MoreFilled,
+  Edit,
+  Delete,
+  Warning,
+  CircleClose,
+  Plus
+} from "@element-plus/icons-vue";
+import {
+  getUserDynamics,
+  likeDynamic,
+  unlikeDynamic,
+  reportUserViolation,
+  blockUser,
+  getUserByPhone,
+  toggleFollowUser,
+  cancelFollowed,
+  likeDynamicNew
+} from "@/api/modules/dynamicManagement";
+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 DynamicItem {
@@ -96,11 +384,46 @@ interface DynamicItem {
   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已关注
+  isVideo?: boolean; // 是否为视频
+  mediaType?: string; // 媒体类型:image 或 video
+}
+
+interface DetailItem extends DynamicItem {
+  author?: {
+    id: number;
+    name: string;
+    avatar: string;
+  };
+  description: string;
+  publishTime: 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 reportDialogVisible = ref(false);
+const reportSubmitting = ref(false);
+const reportForm = reactive({
+  reason: "用户头像", // 默认选择第一个选项
+  description: "",
+  fileList: [] as any[],
+  agreed: false
+});
 
 // 分页
 const pagination = reactive({
@@ -109,21 +432,47 @@ const pagination = reactive({
   total: 0
 });
 
-// 计算分页后的列表
+// 直接使用动态列表(后端已完成分页)
 const paginatedList = computed(() => {
-  const start = (pagination.page - 1) * pagination.pageSize;
-  const end = start + pagination.pageSize;
-  return dynamicList.value.slice(start, end);
+  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 = () => {
+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");
 };
 
@@ -131,50 +480,119 @@ const handlePublish = () => {
 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 {
-    // TODO: 集成真实接口时,取消下面的注释
-    // const res = await getDynamicList({
-    //   type: activeTab.value,
-    //   page: pagination.page,
-    //   pageSize: pagination.pageSize
-    // });
-    // dynamicList.value = res.data.list;
-    // pagination.total = res.data.total;
-
-    // 临时方案:使用模拟数据
-    const mockData: DynamicItem[] = Array.from({ length: 30 }, (_, i) => ({
-      id: i + 1,
-      title: "这家店超好吃....",
-      content: "非常好吃的甜品店,环境优雅,服务态度好,推荐大家来尝试!",
-      imageUrl: "",
-      userName: "甜品店",
-      userAvatar: "",
-      likeCount: 20,
-      isLiked: false,
-      createTime: new Date().toISOString()
-    }));
-
-    dynamicList.value = mockData;
-    pagination.total = mockData.length;
+    // 获取店铺ID(从 userStore 中获取,如果没有则使用默认值)
+    const phoneId = userStore.userInfo?.phoneId || "store_18900957960";
+
+    const res = await getUserDynamics({
+      type: 2, // 固定值,表示动态类型
+      isfollowed: isfollowed.value, // 0 推荐, 1 关注(使用全局变量)
+      myself: 0, // 0 表示他人的动态
+      page: pagination.page,
+      size: pagination.pageSize,
+      phoneId
+    });
+
+    // 处理返回的数据
+    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=已关注)");
+        }
+
+        // 判断是否为视频(只检查逗号之前的第一个字符串,判断是否以.mp4结尾)
+        const mediaUrl = item.imagePath || "";
+        const firstUrl = mediaUrl.split(",")[0].trim(); // 截取逗号之前的第一个字符串
+        const isVideo = firstUrl.toLowerCase().endsWith(".mp4"); // 判断是否以.mp4结尾
+        const mediaType = isVideo ? "video" : "image";
+
+        return {
+          id: item.id || item.dynamicId,
+          title: item.title || item.content || item.dynamicContent || "这家店超好吃....",
+          content: item.content || item.dynamicContent || "",
+          imageUrl: firstUrl, // 使用第一个URL
+          userName: item.userName || item.nickname || item.storeName || "用户",
+          userAvatar: item.userAvatar || item.avatar || item.headImg || "",
+          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已关注(隐藏按钮)
+          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 = (item: DynamicItem) => {
-  console.log("查看动态详情", item);
-  // TODO: 可以添加查看详情的逻辑
-  ElMessage.info(`查看动态: ${item.title}`);
+  console.log("点击动态:", item);
+  console.log("isFollowThis值:", item, "(0=未关注显示按钮, 1=已关注隐藏按钮)");
+  console.log("isMyDynamic:", isMyDynamic.value);
+
+  // 直接使用列表数据构建详情
+  currentDetail.value = {
+    ...item,
+    author: {
+      id: 0,
+      name: item.userName,
+      avatar: item.userAvatar
+    },
+    description: item.content || item.title,
+    publishTime: item.createTime,
+    commentCount: 0,
+    isFollowThis: item.isFollowed // 添加isFollowThis字段用于判断关注按钮显示
+  };
+
+  console.log("详情中的isFollowThis:", currentDetail.value.isFollowThis);
+  console.log("按钮显示条件:", currentDetail.value.isFollowThis == 0);
+  detailDrawerVisible.value = true;
 };
 
 // 点赞/取消点赞
@@ -197,6 +615,383 @@ const handleLike = async (item: DynamicItem) => {
   }
 };
 
+// 关闭详情
+const handleCloseDetail = () => {
+  detailDrawerVisible.value = false;
+  setTimeout(() => {
+    currentDetail.value = null;
+  }, 300);
+};
+
+// 详情页点赞
+const handleDetailLike = async () => {
+  if (!currentDetail.value) return;
+
+  try {
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 调用点赞接口
+    await likeDynamicNew({
+      userId: currentUserPhoneId,
+      huifuId: currentDetail.value.id, // ✅ 动态ID(不是phoneId)
+      // 当前用户phoneId (格式: store_xxx)
+      type: 2 // 2表示点赞
+    });
+
+    // 切换点赞状态
+    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 handleShowComments = () => {
+  ElMessage.info("评论功能开发中");
+};
+
+// 分享
+const handleShare = () => {
+  ElMessage.success("分享链接已复制");
+};
+
+// 查看用户主页
+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;
+    }
+
+    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();
@@ -364,5 +1159,322 @@ onMounted(() => {
     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;
+        &: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 {
+              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;
+    }
+  }
 }
 </style>

+ 274 - 79
src/views/dynamicManagement/publishDynamic.vue

@@ -1,19 +1,12 @@
 <template>
   <div class="publish-dynamic-container">
     <!-- 头部导航 -->
-    <div class="header-bar">
-      <div class="header-left">
-        <el-button class="return-btn" @click="handleGoBack"> 返回 </el-button>
-      </div>
-      <div class="header-title">发布动态</div>
-      <div class="header-right" />
-    </div>
 
     <!-- 表单内容 -->
     <div class="form-container">
       <el-form ref="formRef" :model="formData" :rules="rules" label-position="top">
-        <!-- 图片上传 -->
-        <el-form-item label="图片" prop="images">
+        <!-- 图片/视频上传 -->
+        <el-form-item label="图片/视频" prop="images">
           <div class="upload-section">
             <el-upload
               v-model:file-list="fileList"
@@ -24,10 +17,27 @@
               :on-change="handleFileChange"
               :before-upload="beforeImageUpload"
               :http-request="handleImageUpload"
-              accept="image/*"
+              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 />
@@ -74,9 +84,12 @@
       <el-button type="primary" size="large" class="publish-btn" :loading="publishing" @click="handlePublish"> 发布 </el-button>
     </div>
 
-    <!-- 图片预览对话框 -->
+    <!-- 图片/视频预览对话框 -->
     <el-dialog v-model="previewDialogVisible" width="800px" append-to-body>
-      <img :src="previewImageUrl" alt="预览图片" style="width: 100%" />
+      <!-- 视频预览 -->
+      <video v-if="previewIsVideo" :src="previewImageUrl" controls style="width: 100%; max-height: 70vh" />
+      <!-- 图片预览 -->
+      <img v-else :src="previewImageUrl" alt="预览图片" style="width: 100%" />
     </el-dialog>
 
     <!-- 位置选择对话框 -->
@@ -105,11 +118,15 @@
 import { ref, reactive, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import { ElMessage, ElMessageBox } from "element-plus";
-import { ArrowLeft, Plus, Location, Search } from "@element-plus/icons-vue";
+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, uploadVideo } from "@/api/modules/upload";
+import { useUserStore } from "@/stores/modules/user";
 
 const router = useRouter();
+const userStore = useUserStore();
 
 // 接口定义
 interface FormData {
@@ -118,12 +135,17 @@ interface FormData {
   images: string[];
   location: string;
   locationId?: string;
+  address?: string; // 经纬度
+  addressProvince?: string; // 省市
 }
 
 interface LocationItem {
   id: string;
   name: string;
   address: string;
+  latitude?: string; // 纬度
+  longitude?: string; // 经度
+  province?: string; // 省市
 }
 
 // 响应式数据
@@ -131,6 +153,7 @@ 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[]>([]);
@@ -143,7 +166,9 @@ const formData = reactive<FormData>({
   title: "",
   content: "",
   images: [],
-  location: ""
+  location: "",
+  address: "", // 经纬度
+  addressProvince: "" // 省市
 });
 
 // 表单验证规则
@@ -176,26 +201,37 @@ const handleFileChange = (uploadFile: UploadFile, uploadFiles: UploadFile[]) =>
   // 验证文件
   if (uploadFile.raw) {
     const isImage = uploadFile.raw.type.startsWith("image/");
-    if (!isImage) {
+    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("只能上传图片文件");
+      ElMessage.warning("只能上传图片或视频文件");
       return;
     }
 
-    const isLt20M = uploadFile.raw.size / 1024 / 1024 < 20;
-    if (!isLt20M) {
+    // 根据文件类型设置不同的大小限制
+    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("图片大小不能超过 20MB");
+      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) {
@@ -240,39 +276,47 @@ const uploadSingleFile = async (file: UploadFile) => {
   uploading.value = true;
 
   try {
-    // TODO: 集成真实上传接口时,取消下面的注释
-    // const result = await uploadDynamicImage(uploadFormData);
-    // if (result?.code === 200 && result.data) {
-    //   const imageUrl = result.data.url;
-    //   file.status = "success";
-    //   file.percentage = 100;
-    //   file.url = imageUrl;
-    //   file.response = { url: imageUrl };
-    //   if (!formData.images.includes(imageUrl)) {
-    //     formData.images.push(imageUrl);
-    //   }
-    // } else {
-    //   throw new Error(result?.msg || "图片上传失败");
-    // }
-
-    // 临时方案:使用 FileReader 模拟上传
-    await new Promise<void>((resolve, reject) => {
-      const reader = new FileReader();
-      reader.onload = e => {
-        const imageUrl = e.target?.result as string;
+    console.log("开始上传文件:", rawFile.name, "类型:", rawFile.type);
+
+    // 根据文件类型选择上传接口
+    const isVideo = rawFile.type.startsWith("video/");
+    const result: any = isVideo ? await uploadVideo(uploadFormData) : 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 = imageUrl;
-        file.response = { url: imageUrl };
-        if (!formData.images.includes(imageUrl)) {
-          formData.images.push(imageUrl);
+        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);
         }
-        resolve();
-      };
-      reader.onerror = () => reject(new Error("读取文件失败"));
-      reader.readAsDataURL(rawFile);
-    });
+      } 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);
@@ -281,7 +325,7 @@ const uploadSingleFile = async (file: UploadFile) => {
     if (index > -1) {
       fileList.value.splice(index, 1);
     }
-    ElMessage.error(error?.message || "图片上传失败");
+    ElMessage.error(error?.message || "文件上传失败");
   } finally {
     uploading.value = false;
     fileList.value = [...fileList.value];
@@ -291,14 +335,19 @@ const uploadSingleFile = async (file: UploadFile) => {
 // 图片上传前验证
 const beforeImageUpload = (file: File) => {
   const isImage = file.type.startsWith("image/");
-  const isLt20M = file.size / 1024 / 1024 < 20;
+  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 (!isImage) {
-    ElMessage.error("只能上传图片文件!");
+  if (!isValidType) {
+    ElMessage.error("只能上传图片或视频文件!");
     return false;
   }
-  if (!isLt20M) {
-    ElMessage.error("图片大小不能超过 20MB!");
+  if (!isLtMaxSize) {
+    ElMessage.error(`${isVideo ? "视频" : "图片"}大小不能超过 ${maxSize}MB!`);
     return false;
   }
   return true;
@@ -310,18 +359,70 @@ const handleImageUpload = async (options: any) => {
   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[]) => {
-  const index = formData.images.findIndex(url => url === uploadFile.url);
-  if (index > -1) {
-    formData.images.splice(index, 1);
+  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);
 };
 
 // 选择位置
@@ -360,6 +461,21 @@ const loadLocationList = (keyword?: string) => {
 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;
 };
 
@@ -370,19 +486,36 @@ const handleSaveDraft = async () => {
     return;
   }
 
+  // 检查是否有图片/视频正在上传
+  if (uploading.value || pendingUploadFiles.value.length > 0) {
+    ElMessage.warning("图片/视频正在上传中,请稍候...");
+    return;
+  }
+
   try {
-    // TODO: 集成真实接口时,取消下面的注释
-    // await saveDraft({
-    //   title: formData.title,
-    //   content: formData.content,
-    //   images: formData.images,
-    //   location: formData.location,
-    //   locationId: formData.locationId
-    // });
+    // 获取当前用户信息
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+    const createId = userStore.userInfo?.id || userStore.userInfo?.userId || 0;
+
+    // 调用保存草稿接口
+    await addOrUpdateDynamic({
+      title: formData.title || "未命名",
+      context: formData.content || "", // 正文
+      imagePath: formData.images.join(","), // 多个图片/视频用逗号分隔
+      phoneId: currentUserPhoneId, // 店铺ID
+      createId: createId, // 创建者ID
+      type: "2", // 动态类型
+      draft: 1, // ✅ 1表示草稿
+      addressName: formData.location || "", // 地址名称
+      address: formData.address || "", // 经纬度
+      addressProvince: formData.addressProvince || "" // 省市
+    });
 
     ElMessage.success("草稿保存成功");
     router.back();
   } catch (error) {
+    console.error("保存草稿失败:", error);
     ElMessage.error("保存草稿失败");
   }
 };
@@ -395,28 +528,42 @@ const handlePublish = async () => {
     if (valid) {
       // 检查是否有图片正在上传
       if (uploading.value || pendingUploadFiles.value.length > 0) {
-        ElMessage.warning("图片正在上传中,请稍候...");
+        ElMessage.warning("图片/视频正在上传中,请稍候...");
+        return;
+      }
+
+      // 检查是否有上传的文件
+      if (formData.images.length === 0) {
+        ElMessage.warning("请至少上传一张图片或一个视频");
         return;
       }
 
       publishing.value = true;
 
       try {
-        // TODO: 集成真实接口时,取消下面的注释
-        // await publishDynamic({
-        //   title: formData.title,
-        //   content: formData.content,
-        //   images: formData.images,
-        //   location: formData.location,
-        //   locationId: formData.locationId
-        // });
-
-        // 模拟发布延迟
-        await new Promise(resolve => setTimeout(resolve, 1000));
+        // 获取当前用户信息
+        const phone = userStore.userInfo?.phone || "";
+        const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+        const createId = userStore.userInfo?.id || userStore.userInfo?.userId || 0;
+
+        // 调用发布动态接口
+        await addOrUpdateDynamic({
+          title: formData.title,
+          context: formData.content, // 正文
+          imagePath: formData.images.join(","), // 多个图片/视频用逗号分隔
+          phoneId: currentUserPhoneId, // 店铺ID
+          createId: createId, // 创建者ID
+          type: "2", // 动态类型
+          draft: 0, // 0表示发布
+          addressName: formData.location || "", // 地址名称
+          address: formData.address || "", // 经纬度
+          addressProvince: formData.addressProvince || "" // 省市
+        });
 
         ElMessage.success("发布成功");
         router.back();
       } catch (error) {
+        console.error("发布失败:", error);
         ElMessage.error("发布失败");
       } finally {
         publishing.value = false;
@@ -531,6 +678,54 @@ onMounted(() => {
         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;
+        }
+      }
     }
 
     // 输入框样式

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

@@ -0,0 +1,1224 @@
+<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)">
+          <!-- 封面图片区域 -->
+          <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">
+            <img v-if="currentDetail.coverUrl" :src="currentDetail.coverUrl" :alt="currentDetail.title" class="detail-media" />
+            <div v-else class="media-placeholder">
+              <el-icon :size="80" color="#dcdfe6">
+                <Picture />
+              </el-icon>
+            </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="handleShare">
+            <div class="action-icon">
+              <el-icon :size="28" color="#fff">
+                <Share />
+              </el-icon>
+            </div>
+            <div class="action-count">分享</div>
+          </div>
+        </div>
+      </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>
+  </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
+} 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";
+
+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 ContentItem {
+  id: number;
+  title: string;
+  coverUrl: string;
+  viewCount: number;
+  likeCount: number;
+  isLiked: boolean;
+  createTime: 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 reportDialogVisible = ref(false);
+const reportSubmitting = ref(false);
+const reportForm = reactive({
+  reason: "用户头像", // 默认选择第一个选项
+  description: "",
+  fileList: [] as any[],
+  agreed: false
+});
+
+// 举报原因到违规类型的映射
+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 = (item: ContentItem) => {
+  currentDetail.value = item;
+  detailDrawerVisible.value = true;
+};
+
+// 关闭详情
+const handleCloseDetail = () => {
+  detailDrawerVisible.value = false;
+  setTimeout(() => {
+    currentDetail.value = null;
+  }, 300);
+};
+
+// 详情页点赞
+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 handleShare = () => {
+  ElMessage.success("分享链接已复制");
+};
+
+// 关注/取消关注(顶部按钮)
+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 handleBlockUserClick = () => {
+  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;
+    transition: all 0.3s;
+    &:hover {
+      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;
+        &: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 {
+              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%);
+          }
+        }
+      }
+    }
+  }
+
+  // 举报对话框
+  :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;
+    }
+  }
+}
+</style>