| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- <template>
- <div class="review-appeal-detail-container">
- <!-- 返回按钮 -->
- <div class="page-header">
- <el-button type="primary" @click="goBack"> 返回 </el-button>
- </div>
- <!-- 标题和状态 -->
- <div class="detail-header">
- <h2 class="page-title">申诉详情</h2>
- <div class="status-section">
- <el-icon class="status-icon" :size="48">
- <Clock />
- </el-icon>
- <div class="status-text">
- {{ getStatusText(detailData.appealStatus) }}
- </div>
- <div class="status-desc" v-if="detailData.appealStatus === 0">您反馈的评价内容及账号行为正处于审核阶段,请您耐心等待</div>
- <div class="status-desc" v-else-if="detailData.appealStatus === 1">
- 您反馈的评价内容及账号行为经审核,暂未发现违规行为。建议您可以通过商家的回复功能对具体情况进行解释和沟通,以便于更好的解决问题。
- </div>
- <div class="status-desc" v-else-if="detailData.appealStatus === 2">
- 您反馈的评价内容及账号行为经审核,已发现违规行为。现已根据规定将此条评论进行删除,平台也会持续收集违规行为,一经发现,将按照规定处理。
- </div>
- </div>
- </div>
- <!-- 处理进度 -->
- <div class="progress-section">
- <h3 class="section-title">处理进度</h3>
- <el-timeline>
- <el-timeline-item v-for="(step, index) in progressSteps" :key="index" :timestamp="step.timestamp" placement="top">
- {{ step.content }}
- </el-timeline-item>
- </el-timeline>
- </div>
- <!-- 申诉详情 -->
- <div class="appeal-detail-section">
- <h3 class="section-title">申诉详情</h3>
- <!-- 顾客评价 -->
- <div class="review-card">
- <div class="card-label">顾客评价</div>
- <div class="review-header">
- <div class="user-info">
- <el-avatar :src="detailData.userImage" :size="40">
- <el-icon><User /></el-icon>
- </el-avatar>
- <div class="user-details">
- <div class="user-name">
- {{ detailData.isAnonymous == 1 || !detailData.userName ? "匿名用户" : detailData.userName }}
- </div>
- </div>
- </div>
- <div class="review-time">
- {{ detailData.commentTime }}
- </div>
- </div>
- <div class="review-content">
- {{ detailData.commentContent }}
- </div>
- <div v-if="commentMedia.length > 0" class="review-media">
- <template v-for="(m, idx) in commentMedia" :key="'c-' + idx">
- <el-image
- v-if="m.type === 'image'"
- :src="m.url"
- :preview-src-list="commentMediaImages"
- :initial-index="getMediaImageIndex(commentMedia, idx)"
- fit="cover"
- class="media-item review-image"
- />
- <div v-else class="media-item media-video-wrap" @click="openVideoPreview(m.url)">
- <video
- :src="m.url"
- class="video-thumb"
- preload="metadata"
- muted
- playsinline
- @loadeddata="onVideoLoadedData($event)"
- />
- <div class="video-mask">
- <el-icon class="video-play-icon">
- <VideoPlay />
- </el-icon>
- </div>
- </div>
- </template>
- </div>
- </div>
- <!-- 申诉信息 -->
- <div class="appeal-info-card">
- <div class="card-label">申诉信息</div>
- <div class="info-item">
- <span class="info-label">申诉账号</span>
- <span class="info-value">{{ detailData.storePhone }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">申诉时间</span>
- <span class="info-value">{{ detailData.appealTime }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">申诉原因</span>
- <span class="info-value">{{ detailData.appealReason }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">申诉图片</span>
- <div v-if="appealMedia.length > 0" class="appeal-media" style="width: 200px">
- <template v-for="(m, idx) in appealMedia" :key="'a-' + idx">
- <el-image
- v-if="m.type === 'image'"
- :src="m.url"
- :preview-src-list="appealMediaImages"
- :initial-index="getMediaImageIndex(appealMedia, idx)"
- fit="cover"
- class="media-item appeal-image"
- />
- <div v-else class="media-item media-video-wrap" @click="openVideoPreview(m.url)">
- <video
- :src="m.url"
- class="video-thumb"
- preload="metadata"
- muted
- playsinline
- @loadeddata="onVideoLoadedData($event)"
- />
- <div class="video-mask">
- <el-icon class="video-play-icon">
- <VideoPlay />
- </el-icon>
- </div>
- </div>
- </template>
- </div>
- <span v-else class="info-value">--</span>
- </div>
- </div>
- </div>
- <!-- 视频预览弹窗 -->
- <el-dialog
- v-model="videoPreviewVisible"
- title="视频预览"
- width="80%"
- max-width="720px"
- destroy-on-close
- align-center
- @closed="closeVideoPreview"
- >
- <video v-if="videoPreviewUrl" :src="videoPreviewUrl" controls autoplay style="width: 100%; max-height: 70vh" />
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts" name="reviewAppealDetail">
- import { ref, reactive, computed, onMounted } from "vue";
- import { useRouter, useRoute } from "vue-router";
- import { Clock, User, VideoPlay } from "@element-plus/icons-vue";
- import { ElMessage } from "element-plus";
- import { getAppealDetail } from "@/api/modules/newLoginApi";
- const router = useRouter();
- const route = useRoute();
- // 详情数据
- const detailData = reactive({
- appealStatus: 0, // 0-待审核, 1-已驳回, 2-已通过
- userName: "",
- userAvatar: "",
- isAnonymous: 0,
- commentTime: "",
- commentContent: "",
- commentImages: [] as string[],
- appealAccount: "",
- appealTime: "",
- appealReason: "",
- storePhone: "",
- imgList: [] as string[],
- userImage: "",
- commentImgList: [] as string[]
- });
- const VIDEO_EXT = [".mp4", ".mov", ".avi", ".wmv", ".flv", ".mkv", ".webm", ".m4v"];
- const isVideoUrl = (url: string) => {
- const lower = (url || "").toLowerCase();
- return VIDEO_EXT.some(ext => lower.includes(ext));
- };
- interface MediaItem {
- url: string;
- type: "image" | "video";
- }
- const getMediaFromUrls = (urls: string[]): MediaItem[] => {
- if (!urls?.length) return [];
- return urls.filter(Boolean).map(url => ({ url, type: isVideoUrl(url) ? "video" : "image" }));
- };
- const getMediaImageIndex = (media: MediaItem[], currentIndex: number): number => {
- let idx = 0;
- for (let i = 0; i < currentIndex; i++) if (media[i].type === "image") idx++;
- return idx;
- };
- const commentMedia = computed<MediaItem[]>(() => getMediaFromUrls(detailData.commentImgList || []));
- const commentMediaImages = computed(() => commentMedia.value.filter(m => m.type === "image").map(m => m.url));
- const appealMedia = computed<MediaItem[]>(() => getMediaFromUrls(detailData.imgList || []));
- const appealMediaImages = computed(() => appealMedia.value.filter(m => m.type === "image").map(m => m.url));
- const videoPreviewVisible = ref(false);
- const videoPreviewUrl = ref("");
- const openVideoPreview = (url: string) => {
- videoPreviewUrl.value = url;
- videoPreviewVisible.value = true;
- };
- const closeVideoPreview = () => {
- videoPreviewUrl.value = "";
- videoPreviewVisible.value = false;
- };
- const onVideoLoadedData = (e: Event) => {
- const video = e.target as HTMLVideoElement;
- if (video) {
- video.currentTime = 0;
- video.pause();
- }
- };
- // 处理进度步骤
- const progressSteps = ref<Array<{ content: string; timestamp: string }>>([
- {
- content: "商家提交申诉",
- timestamp: "2025/06/10 12:00:00"
- },
- {
- content: "通过系统初审,已为您安排专人审核",
- timestamp: "2025/06/10 12:00:00"
- },
- {
- content: "申诉成功",
- timestamp: "2025/06/10 12:00:00"
- }
- ]);
- // 返回
- const goBack = () => {
- router.back();
- };
- // 获取状态文本
- const getStatusText = (status: number) => {
- const statusMap: Record<number, string> = {
- 0: "待审核",
- 1: "已驳回",
- 2: "已通过"
- };
- return statusMap[status] || "待审核";
- };
- // 加载申诉详情
- const loadAppealDetail = async () => {
- try {
- const appealId = route.query.id;
- if (!appealId) {
- ElMessage.error("缺少申诉ID");
- return;
- }
- const res: any = await getAppealDetail({ id: appealId });
- console.log("申诉详情:", res);
- if (res.code === 200 || res.code == 200) {
- const data = res.data;
- Object.assign(detailData, {
- appealStatus: data.appealStatus ?? 0,
- userName: data.userName || "",
- userAvatar: data.userAvatar || "",
- isAnonymous: data.isAnonymous ?? 0,
- commentTime: data.commentTime || data.createdTime || "",
- commentContent: data.commentContent || "",
- commentImages: data.commentImages || [],
- appealAccount: data.appealAccount || data.phone || "",
- appealTime: data.appealTime || data.createdTime || "",
- appealReason: data.appealReason || "",
- storePhone: data.storePhone || "",
- imgList: data.imgList || [],
- userImage: data.userImage || "",
- commentImgList: data.commentImgList || []
- });
- // 更新处理进度
- if (data.progressSteps && Array.isArray(data.progressSteps)) {
- progressSteps.value = data.progressSteps;
- }
- } else {
- ElMessage.error(res.msg || "获取申诉详情失败");
- }
- } catch (error: any) {
- console.error("获取申诉详情失败", error);
- ElMessage.error(error?.msg || "获取申诉详情失败");
- }
- };
- // 初始化
- onMounted(() => {
- loadAppealDetail();
- });
- </script>
- <style lang="scss" scoped>
- .review-appeal-detail-container {
- min-height: calc(100vh - 120px);
- padding: 20px;
- background: #f5f7fa;
- .page-header {
- margin-bottom: 20px;
- }
- // 标题和状态
- .detail-header {
- padding: 24px;
- margin-bottom: 20px;
- text-align: center;
- background: #ffffff;
- border-radius: 8px;
- .page-title {
- margin: 0 0 24px;
- font-size: 20px;
- font-weight: 600;
- color: #303133;
- }
- .status-section {
- .status-icon {
- margin-bottom: 12px;
- color: #e6a23c;
- }
- .status-text {
- margin-bottom: 8px;
- font-size: 18px;
- font-weight: 600;
- color: #303133;
- }
- .status-desc {
- font-size: 14px;
- color: #909399;
- }
- }
- }
- // 处理进度
- .progress-section {
- padding: 24px;
- margin-bottom: 20px;
- background: #ffffff;
- border-radius: 8px;
- .section-title {
- margin: 0 0 20px;
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- }
- }
- // 申诉详情
- .appeal-detail-section {
- padding: 24px;
- background: #ffffff;
- border-radius: 8px;
- .section-title {
- margin: 0 0 20px;
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- }
- .card-label {
- margin-bottom: 16px;
- font-size: 14px;
- font-weight: 600;
- color: #606266;
- }
- // 顾客评价卡片
- .review-card {
- padding: 16px;
- margin-bottom: 24px;
- background: #f5f7fa;
- border-radius: 8px;
- .review-header {
- display: flex;
- justify-content: space-between;
- margin-bottom: 12px;
- .user-info {
- display: flex;
- gap: 12px;
- align-items: center;
- .user-details {
- .user-name {
- font-size: 14px;
- font-weight: 600;
- color: #303133;
- }
- }
- }
- .review-time {
- font-size: 13px;
- color: #909399;
- }
- }
- .review-content {
- margin-bottom: 12px;
- font-size: 14px;
- line-height: 1.6;
- color: #606266;
- }
- .review-media,
- .appeal-media {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- .media-item {
- flex-shrink: 0;
- width: 100px;
- height: 100px;
- overflow: hidden;
- border-radius: 4px;
- }
- .review-image,
- .appeal-image {
- object-fit: cover;
- }
- .media-video-wrap {
- position: relative;
- cursor: pointer;
- background: #000000;
- .video-thumb {
- display: block;
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .video-mask {
- position: absolute;
- inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgb(0 0 0 / 35%);
- .video-play-icon {
- font-size: 28px;
- color: #ffffff;
- }
- }
- }
- }
- }
- // 申诉信息卡片
- .appeal-info-card {
- .info-item {
- display: flex;
- margin-bottom: 16px;
- &:last-child {
- margin-bottom: 0;
- }
- .info-label {
- flex-shrink: 0;
- width: 100px;
- font-size: 14px;
- color: #909399;
- }
- .info-value {
- flex: 1;
- font-size: 14px;
- color: #606266;
- }
- }
- }
- }
- }
- </style>
|