Files
rating/pages/rating/detail.vue
2026-05-26 07:29:52 +08:00

704 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page-container">
<!-- 顶部导航栏 -->
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-left" @tap="goBack">
<text class="back-icon"></text>
<text class="nav-title">全民评分</text>
</view>
<view class="nav-right">
<text class="search-icon"></text>
<image class="nav-avatar" src="/static/images/default-avatar.png" mode="aspectFill" />
</view>
</view>
</view>
<view :style="{ height: statusBarHeight + 44 + 'px' }"></view>
<view class="content-wrap">
<!-- 头部信息卡片 -->
<view class="hero-card card">
<image class="hero-avatar" :src="detailData.avatar" mode="aspectFill" />
<view class="hero-info">
<view class="hero-name-row">
<text class="hero-name">{{ detailData.name }}</text>
<text class="hero-score">{{ detailData.score }}</text>
</view>
<view class="hero-meta">
<text class="meta-rank">🥇 No.1</text>
<text class="meta-heat">🔥 {{ detailData.hotScore }} 热度</text>
<text class="meta-count" style="margin-left: 20rpx; color: #999;">{{ detailData.ratingCount }} 人评分</text>
</view>
</view>
</view>
<!-- AI 观察员 -->
<view class="ai-card">
<view class="ai-header">
<text class="ai-icon"></text>
<text class="ai-title">AI观察员</text>
</view>
<text class="ai-content">{{ detailData.aiSummary }}</text>
</view>
<!-- 你的态度 -->
<AttitudePanel
:initial-action="currentAction"
@action-change="handleActionChange"
@send-comment="handleSendComment"
/>
<!-- 大家都在 PK -->
<view class="pk-panel card">
<view class="pk-header">
<text class="section-title">大家都在 PK</text>
<view class="go-compare">去对比<text class="arrow-right"></text></view>
</view>
<view class="pk-content">
<view class="pk-person">
<image class="pk-avatar blue-border" :src="detailData.avatar" mode="aspectFill" />
<text class="pk-name">{{ detailData.name }}</text>
</view>
<text class="vs-text">VS</text>
<view class="pk-person">
<image class="pk-avatar yellow-border" src="https://api.dicebear.com/7.x/avataaars/svg?seed=13" mode="aspectFill" />
<text class="pk-name">康熙</text>
</view>
</view>
<view class="pk-bar-wrap">
<view class="pk-bar blue-bar" style="width: 63%;"></view>
<view class="pk-bar yellow-bar" style="width: 37%;"></view>
</view>
<view class="pk-data-text">
<text class="blue-text">63% 支持</text>
<text class="yellow-text">37% 支持</text>
</view>
</view>
<!-- 评论区 -->
<view class="comments-section card">
<view class="tabs-header">
<view class="tabs">
<text class="tab active">最热</text>
<text class="tab">最新</text>
</view>
<text class="filter-icon"></text>
</view>
<view class="comment-list">
<view class="comment-item" v-for="comment in comments" :key="comment.id">
<image class="c-avatar" :src="comment.avatar" mode="aspectFill" />
<view class="c-content-wrap">
<view class="c-header">
<view class="c-user-info">
<text class="c-name">{{ comment.name }}</text>
<view class="c-badge" v-if="comment.badge">
<text class="b-icon">{{ comment.badgeIcon }}</text>
<text class="b-text">{{ comment.badge }}</text>
</view>
</view>
<view class="c-like">
<text class="like-icon"></text>
<text class="like-count">{{ comment.likes }}</text>
</view>
</view>
<text class="c-time">{{ comment.time }}</text>
<text class="c-text">{{ comment.content }}</text>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more">
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text>
</view>
</view>
</view>
<!-- 评分成功特效弹窗 -->
<SuccessPopup ref="successPopupRef" />
</view>
</template>
<script setup>
import { ref } from 'vue';
import { getStatusBarHeight } from "@/utils/system";
import { onLoad, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
import AttitudePanel from "@/components/AttitudePanel/AttitudePanel.vue";
import SuccessPopup from "@/components/SuccessPopup/SuccessPopup.vue";
import { FILE_BASE_URL } from "@/utils/constants";
import { fetchTopicRatingItems, topicItemScore, topicItemComment } from "@/api/topicItem";
const statusBarHeight = ref(getStatusBarHeight() || 44);
const loading = ref(false);
const hasMore = ref(true);
const currentAction = ref('');
const successPopupRef = ref(null);
const goBack = () => {
uni.navigateBack();
};
const detailData = ref({
id: '',
name: '',
score: '0.0',
avatar: '',
aiSummary: '加载中...',
userAction: "",
topicId: ''
});
const comments = ref([]);
const itemId = ref('');
const handleActionChange = async (action) => {
const originAction = detailData.value.userAction;
const originScore = detailData.value.score;
const isCancel = detailData.value.userAction === action.label;
try {
detailData.value.userAction = isCancel ? '' : action.label;
currentAction.value = detailData.value.userAction;
const res = await topicItemScore({
topicId: detailData.value.topicId,
itemId: itemId.value,
scoreType: action.scoreType
});
detailData.value.score = res?.scoreAvg || originScore;
// 如果是成功评分(非取消),展示特效弹窗
if (!isCancel && successPopupRef.value) {
successPopupRef.value.show(action);
}
} catch (error) {
detailData.value.userAction = originAction;
detailData.value.score = originScore;
currentAction.value = originAction;
}
};
const handleSendComment = async (payload) => {
if (!detailData.value.topicId || !itemId.value) return;
try {
uni.showLoading({ title: '发送中...' });
await topicItemComment({
topicId: detailData.value.topicId,
itemId: itemId.value,
content: payload.content
});
uni.hideLoading();
// 弹出成功弹窗
if (successPopupRef.value) {
successPopupRef.value.show({
emoji: '💬',
label: '你的锐评已发布'
}, 'comment');
}
// 刷新页面数据以获取最新评论
getDetailData();
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '发送失败',
icon: 'none'
});
}
};
const getDetailData = async () => {
if (!itemId.value) return;
try {
uni.showLoading({ title: '加载中' });
const res = await fetchTopicRatingItems(itemId.value);
const data = res?.data || res || {};
detailData.value = {
id: itemId.value,
name: data.name || '未知',
score: data.scoreAvg ? Number(data.scoreAvg).toFixed(1) : '0.0',
avatar: data.avatarUrl ? `${FILE_BASE_URL}${data.avatarUrl}` : '',
aiSummary: data.description || '暂无总结',
hotScore: data.hotScore || '0.00',
topicId: data.topicId || '',
ratingCount: data.scoreCount || 0,
userAction: data.userAction || ""
};
// 如果接口返回了当前用户的态度,初始化它
if (data.userAction) {
currentAction.value = data.userAction;
}
// 如果有评论数据,更新评论
if (data.comments && Array.isArray(data.comments)) {
comments.value = data.comments;
} else {
comments.value = [];
}
} catch (error) {
console.error('获取详情失败:', error);
uni.showToast({
title: '获取详情失败',
icon: 'none'
});
} finally {
uni.hideLoading();
uni.stopPullDownRefresh();
}
};
onLoad((options) => {
if (options.itemId) {
itemId.value = options.itemId;
getDetailData();
}
});
onPullDownRefresh(() => {
getDetailData();
});
onReachBottom(() => {
if (!hasMore.value || loading.value) return;
// TODO: 评论分页加载逻辑
});
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f6fa;
padding-bottom: 40rpx;
}
/* 导航栏 */
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background-color: #f5f6fa;
}
.nav-content {
height: 44px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 32rpx;
}
.nav-left {
display: flex;
align-items: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='19' y1='12' x2='5' y2='12'%3E%3C/line%3E%3Cpolyline points='12 19 5 12 12 5'%3E%3C/polyline%3E%3C/svg%3E");
background-size: cover;
margin-right: 16rpx;
}
.nav-title {
font-size: 34rpx;
font-weight: 700;
color: #111;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
}
.nav-right {
display: flex;
align-items: center;
}
.search-icon {
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232953ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
background-size: cover;
margin-right: 24rpx;
}
.nav-avatar {
width: 52rpx;
height: 52rpx;
border-radius: 8rpx;
background-color: #eee;
}
.content-wrap {
padding: 24rpx 32rpx;
}
.card {
background-color: #fff;
border-radius: 32rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
/* 英雄卡片 */
.hero-card {
display: flex;
align-items: center;
}
.hero-avatar {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
margin-right: 32rpx;
border: 4rpx solid #fff;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.1);
}
.hero-info {
flex: 1;
}
.hero-name-row {
display: flex;
align-items: baseline;
margin-bottom: 12rpx;
}
.hero-name {
font-size: 44rpx;
font-weight: bold;
color: #111;
margin-right: 16rpx;
}
.hero-score {
font-size: 44rpx;
font-weight: 900;
color: #2953ff;
font-family: 'DIN Alternate', sans-serif;
}
.hero-meta {
display: flex;
align-items: center;
font-size: 24rpx;
color: #666;
}
.meta-rank {
margin-right: 24rpx;
font-weight: bold;
color: #d9a000;
}
.meta-heat {
color: #666;
}
/* AI 观察员 */
.ai-card {
background: #ffffff;
border: 2rpx solid #e0d6ff;
border-radius: 32rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.ai-header {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.ai-icon {
width: 36rpx;
height: 36rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237B46F1'%3E%3Cpath d='M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h5a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h5V5.73A2 2 0 0 1 10 4a2 2 0 0 1 2-2zm-3 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm6 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z'/%3E%3C/svg%3E");
background-size: cover;
margin-right: 12rpx;
}
.ai-title {
font-size: 28rpx;
font-weight: 800;
color: #7b46f1;
}
.ai-content {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
/* 你的态度面板 */
/* 大家都在 PK */
.section-title {
font-size: 34rpx;
font-weight: 700;
color: #111;
margin-bottom: 32rpx;
display: block;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
}
.pk-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.pk-header .section-title {
margin-bottom: 0;
}
.go-compare {
font-size: 24rpx;
color: #2953ff;
display: flex;
align-items: center;
}
.arrow-right {
width: 24rpx;
height: 24rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232953ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'%3E%3C/polyline%3E%3C/svg%3E");
background-size: cover;
}
.pk-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
padding: 0 40rpx;
}
.pk-person {
display: flex;
flex-direction: column;
align-items: center;
}
.pk-avatar {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
border: 4rpx solid transparent;
margin-bottom: 12rpx;
}
.blue-border { border-color: #2953ff; }
.yellow-border { border-color: #ffc107; }
.pk-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.vs-text {
font-size: 40rpx;
font-weight: 900;
color: #a1a1a1;
}
.pk-bar-wrap {
height: 16rpx;
border-radius: 100rpx;
display: flex;
overflow: hidden;
margin-bottom: 16rpx;
}
.pk-bar { height: 100%; }
.blue-bar { background-color: #4d44f1; }
.yellow-bar { background-color: #ffe066; }
.pk-data-text {
display: flex;
justify-content: space-between;
font-size: 24rpx;
font-weight: bold;
}
.blue-text { color: #4d44f1; }
.yellow-text { color: #333; }
/* 评论区 */
.comments-section {
padding: 32rpx;
}
.tabs-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.tabs {
display: flex;
gap: 40rpx;
}
.tab {
font-size: 32rpx;
color: #999;
font-weight: 500;
position: relative;
padding-bottom: 12rpx;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
}
.tab.active {
color: #111;
font-size: 36rpx;
font-weight: 700; /* 稍微减轻字重从800改为700显得不那么生硬 */
}
.tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 32rpx;
height: 8rpx;
background: linear-gradient(90deg, #4d44f1, #8a78ff);
border-radius: 4rpx;
}
.filter-icon {
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='4' y1='21' x2='4' y2='14'%3E%3C/line%3E%3Cline x1='4' y1='10' x2='4' y2='3'%3E%3C/line%3E%3Cline x1='12' y1='21' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='8' x2='12' y2='3'%3E%3C/line%3E%3Cline x1='20' y1='21' x2='20' y2='16'%3E%3C/line%3E%3Cline x1='20' y1='12' x2='20' y2='3'%3E%3C/line%3E%3Cline x1='1' y1='14' x2='7' y2='14'%3E%3C/line%3E%3Cline x1='9' y1='8' x2='15' y2='8'%3E%3C/line%3E%3Cline x1='17' y1='16' x2='23' y2='16'%3E%3C/line%3E%3C/svg%3E");
background-size: cover;
transition: opacity 0.2s;
}
.filter-icon:active {
opacity: 0.7;
}
.comment-list {
display: flex;
flex-direction: column;
}
.comment-item {
display: flex;
padding: 32rpx 0;
position: relative;
}
.comment-item::after {
content: '';
position: absolute;
bottom: 0;
left: 104rpx; /* 头像宽度(80) + 间距(24) */
right: 0;
height: 2rpx;
background-color: #f0f2f7;
}
.comment-item:last-child::after {
display: none;
}
.c-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 24rpx;
background-color: #f5f6fa;
flex-shrink: 0;
border: 2rpx solid #f0f2f7;
}
.c-content-wrap {
flex: 1;
}
.c-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8rpx;
}
.c-user-info {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.c-name {
font-size: 30rpx;
font-weight: 800;
color: #111;
margin-right: 16rpx;
}
.c-badge {
display: flex;
align-items: center;
background: linear-gradient(90deg, #f5f6fa, #f0f2f7);
padding: 4rpx 16rpx;
border-radius: 100rpx;
}
.b-icon { font-size: 20rpx; margin-right: 6rpx; }
.b-text { font-size: 20rpx; color: #666; font-weight: bold; }
.c-like {
display: flex;
align-items: center;
color: #999;
font-size: 24rpx;
font-weight: 600;
}
.like-icon {
width: 32rpx;
height: 32rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3'%3E%3C/path%3E%3C/svg%3E");
background-size: cover;
margin-right: 8rpx;
transition: transform 0.2s;
}
.c-like:active .like-icon {
transform: scale(1.1);
}
.c-time {
font-size: 24rpx;
color: #a1a1a1;
display: block;
margin-bottom: 16rpx;
}
.c-text {
font-size: 32rpx;
color: #222;
line-height: 1.6;
}
.load-more {
text-align: center;
padding: 40rpx 0 0;
color: #999;
font-size: 24rpx;
}
</style>