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