Files
rating/pages/index/index.vue

581 lines
15 KiB
Vue
Raw Normal View History

2026-05-07 11:05:30 +08:00
<template>
<view class="home-container">
<!-- 顶部固定栏 (使用 padding-top 避开状态栏) -->
<view class="header-section" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="header-content">
<image
class="user-avatar"
:src="userInfo.avatarUrl || '/static/images/default-avatar.png'"
mode="aspectFill"
@tap="handleLogin"
/>
2026-05-07 11:09:32 +08:00
<view class="header-texts">
<text class="title">全民评分</text>
<text class="subtitle">看看全国网友怎么评分</text>
</view>
2026-05-07 11:05:30 +08:00
</view>
2026-05-07 11:09:32 +08:00
<!-- 顶部切换 tab -->
<scroll-view class="category-tabs" scroll-x :show-scrollbar="false">
<view class="tabs-wrapper">
<view
class="tab-item"
:class="{ active: currentCategory === index }"
v-for="(category, index) in categories"
:key="index"
@tap="switchCategory(index)"
>
<text class="tab-text">{{ category.name }}</text>
<view class="tab-line" v-if="currentCategory === index"></view>
</view>
</view>
</scroll-view>
2026-05-07 11:05:30 +08:00
</view>
2026-05-07 11:09:32 +08:00
<!-- 占位防止固定头部遮挡内容 (增加 tab 的高度) -->
<view :style="{ height: statusBarHeight + 104 + 'px' }"></view>
2026-05-07 11:05:30 +08:00
<!-- 列表内容 -->
<view class="topic-list">
<view class="topic-card" v-for="topic in topicList" :key="topic.id">
<!-- 卡片头部标题与统计信息 -->
<view class="topic-header">
<text class="topic-title">{{ topic.title }}</text>
<view class="topic-stats">
<view class="stat-item">
<text class="stat-icon icon-group"></text>
<text>{{ topic.participants }}人参与</text>
</view>
<view class="stat-item heat">
<text class="stat-icon icon-fire"></text>
<text>热度 {{ topic.heat }}</text>
</view>
</view>
</view>
<!-- 排行榜列表 -->
<view class="rank-list">
2026-05-07 15:57:39 +08:00
<view
class="rank-item"
v-for="(item, index) in topic.items"
:key="item.id"
@tap="goToRating(topic.id, item.id)"
>
2026-05-07 11:05:30 +08:00
<text class="rank-num" :class="'rank-' + (index + 1)">{{ index + 1 }}</text>
<image class="item-avatar" :src="item.avatar" mode="aspectFill"></image>
<text class="item-name">{{ item.name }}</text>
<text class="item-score" :class="'score-' + (index + 1)">{{ item.score }}</text>
</view>
</view>
<!-- AI 总结 -->
<view class="ai-summary">
<text class="ai-icon icon-robot"></text>
<view class="summary-text">
<text class="summary-label">AI总结</text>
<text class="summary-content">{{ topic.aiSummary }}</text>
</view>
</view>
<!-- 标签列表 -->
<view class="tag-list">
<view class="tag" v-for="(tag, tIndex) in topic.tags" :key="tIndex">
<text>#{{ tag }}</text>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more">
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text>
</view>
</view>
<!-- 登录弹窗 -->
<LoginPopup @logind="handleLoginSuccess" />
</view>
</template>
<script setup>
import { ref, computed } from "vue";
import { getStatusBarHeight } from "@/utils/system";
import {
onShareAppMessage,
onShareTimeline,
onShow,
onPullDownRefresh,
onReachBottom,
onLoad,
} from "@dcloudio/uni-app";
import { useUserStore } from "@/stores/user";
import { getShareReward } from "@/api/system";
import { getShareToken, saveViewRequest } from "@/utils/common.js";
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
import adManager from "@/utils/adManager.js";
const userStore = useUserStore();
const statusBarHeight = ref(getStatusBarHeight());
const userInfo = computed(() => userStore?.userInfo || {});
const loading = ref(false);
const hasMore = ref(true);
2026-05-07 11:09:32 +08:00
const currentCategory = ref(0);
const categories = ref([
{ id: 0, name: '全部' },
{ id: 1, name: '历史' },
{ id: 2, name: '皇帝' },
{ id: 3, name: '数码' },
{ id: 4, name: '手机' },
{ id: 5, name: '城市' },
{ id: 6, name: '生活' },
]);
const switchCategory = (index) => {
if (currentCategory.value === index) return;
currentCategory.value = index;
// 切换分类时重新加载数据
topicList.value = [];
loading.value = true;
hasMore.value = true;
setTimeout(() => {
// 模拟根据分类筛选数据
const filteredData = mockData.filter(item => {
if (index === 0) return true;
const categoryName = categories.value[index].name;
return item.tags.includes(categoryName);
});
topicList.value = [...filteredData];
loading.value = false;
hasMore.value = filteredData.length >= 10 ? true : false;
}, 500);
};
2026-05-07 11:05:30 +08:00
const mockData = [
{
id: 1,
title: '你心中的千古一帝',
participants: '23.5万',
heat: '98k',
items: [
{ id: 1, name: '李世民', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=1', score: '9.8' },
{ id: 2, name: '秦始皇', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=2', score: '9.5' },
{ id: 3, name: '康熙', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=3', score: '9.2' },
],
aiSummary: '多数用户认为李世民综合能力最强,兼具文治武功。',
tags: ['历史', '皇帝', '争议']
},
{
id: 2,
title: '国产手机系统谁最强',
participants: '15万',
heat: '76k',
items: [
{ id: 1, name: 'iOS', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=4', score: '8.5' },
{ id: 2, name: 'HyperOS', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=5', score: '8.2' },
{ id: 3, name: 'OriginOS', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=6', score: '8.0' },
],
aiSummary: '系统流畅度与动效是用户关注的核心,底层优化成为评分关键。',
tags: ['数码', '手机', '测评']
},
{
id: 3,
title: '最值得定居的城市',
participants: '12.8万',
heat: '62k',
items: [
{ id: 1, name: '成都', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=7', score: '9.2' },
{ id: 2, name: '杭州', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=8', score: '8.9' },
{ id: 3, name: '厦门', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=9', score: '8.7' },
],
aiSummary: '宜居性与就业机会是主要博弈点,慢节奏生活深受青睐。',
tags: ['城市', '生活', '推荐']
}
];
const topicList = ref([...mockData]);
2026-05-07 15:57:39 +08:00
const goToRating = (topicId, itemId) => {
uni.navigateTo({
url: `/pages/rating/index?topicId=${topicId}&itemId=${itemId}`
});
};
2026-05-07 11:05:30 +08:00
const handleLogin = () => {
if (!userInfo.value.nickName) {
uni.$emit("show-login-popup");
}
};
let firstLoad = true;
const initData = () => {
if (adManager.isAdShowing) return; // 防止广告页返回时立即重新渲染引发 insertTextView:fail
};
onLoad((options) => {
uni.$trackRecord({
eventName: "index_page_visit",
eventType: `visit`,
})
if (options.shareToken) {
saveViewRequest(options.shareToken, "index");
}
initData();
});
onShow(() => {
if (firstLoad) {
firstLoad = false;
return;
}
initData();
});
const handleLoginSuccess = () => {
};
// 下拉刷新
onPullDownRefresh(() => {
setTimeout(() => {
topicList.value = [...mockData];
hasMore.value = true;
uni.stopPullDownRefresh();
}, 1000);
});
// 上拉加载更多
onReachBottom(() => {
if (!hasMore.value || loading.value) return;
loading.value = true;
setTimeout(() => {
const newData = mockData.map(item => ({ ...item, id: item.id + topicList.value.length }));
topicList.value.push(...newData);
loading.value = false;
if (topicList.value.length >= 10) {
hasMore.value = false;
}
}, 1000);
});
onShareAppMessage(async () => {
const shareToken = await getShareToken("index");
getShareReward();
return {
title: "全民评分",
path: "/pages/index/index?shareToken=" + shareToken,
};
});
onShareTimeline(async () => {
const shareToken = await getShareToken("index");
getShareReward();
return {
title: "全民评分",
query: `shareToken=${shareToken}`,
};
});
</script>
<style lang="scss" scoped>
.home-container {
min-height: 100vh;
background-color: #f5f6fa;
box-sizing: border-box;
}
.header-section {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: rgba(245, 246, 250, 0.95);
backdrop-filter: blur(20rpx);
padding-bottom: 20rpx;
}
.header-content {
display: flex;
align-items: center;
padding: 0 32rpx;
height: 120rpx;
}
.header-texts {
display: flex;
flex-direction: column;
justify-content: center;
}
.title {
font-size: 44rpx;
font-weight: 800;
color: #111;
margin-bottom: 6rpx;
}
.subtitle {
font-size: 24rpx;
color: #666;
}
.user-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
border: 2rpx solid #fff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
2026-05-07 11:09:32 +08:00
margin-right: 24rpx; /* 头像在左边时,增加右侧间距 */
}
/* Tab 样式 */
.category-tabs {
width: 100%;
white-space: nowrap;
height: 88rpx;
}
.tabs-wrapper {
display: inline-flex;
padding: 0 16rpx;
height: 100%;
align-items: center;
}
.tab-item {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 24rpx;
height: 100%;
position: relative;
}
.tab-text {
font-size: 30rpx;
color: #666;
transition: all 0.3s ease;
}
.tab-item.active .tab-text {
font-size: 34rpx;
font-weight: bold;
color: #111;
}
.tab-line {
position: absolute;
bottom: 12rpx;
width: 32rpx;
height: 6rpx;
background-color: #2953ff;
border-radius: 4rpx;
transition: all 0.3s ease;
2026-05-07 11:05:30 +08:00
}
.topic-list {
padding: 24rpx 32rpx;
}
.topic-card {
background-color: #ffffff;
2026-05-07 15:24:28 +08:00
border-radius: 40rpx; /* 增加圆角,更圆润 */
padding: 36rpx; /* 稍微增加内边距,让内容更呼吸 */
margin-bottom: 36rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.04); /* 更柔和、扩散的阴影 */
transition: transform 0.3s ease, box-shadow 0.3s ease; /* 增加过渡动画 */
}
.topic-card:active {
transform: scale(0.98); /* 点击时微缩放效果 */
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02);
2026-05-07 11:05:30 +08:00
}
.topic-header {
margin-bottom: 32rpx;
}
.topic-title {
2026-05-07 15:24:28 +08:00
font-size: 38rpx; /* 稍微调大标题 */
font-weight: 800; /* 加粗标题 */
color: #1a1a1a;
2026-05-07 11:05:30 +08:00
margin-bottom: 16rpx;
display: block;
2026-05-07 15:24:28 +08:00
letter-spacing: 1rpx; /* 增加字间距 */
2026-05-07 11:05:30 +08:00
}
.topic-stats {
display: flex;
align-items: center;
}
.stat-item {
display: flex;
align-items: center;
font-size: 24rpx;
2026-05-07 15:24:28 +08:00
color: #888; /* 稍微调浅次要文本颜色 */
2026-05-07 11:05:30 +08:00
margin-right: 32rpx;
2026-05-07 15:24:28 +08:00
background-color: #f8f9fa; /* 增加极浅的背景色块 */
padding: 6rpx 16rpx;
border-radius: 100rpx; /* 完全圆角的统计标签 */
}
.stat-item.heat {
color: #ff4d4f;
background-color: #fff1f0; /* 热度专属浅红背景 */
2026-05-07 11:05:30 +08:00
}
.stat-icon {
width: 24rpx;
height: 24rpx;
margin-right: 8rpx;
display: inline-block;
background-size: cover;
}
.icon-group {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999999'%3E%3Cpath d='M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z'/%3E%3C/svg%3E");
}
.icon-fire {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff4d4f'%3E%3Cpath d='M19.48 13.03c-.02-.19-.13-.36-.29-.46-.17-.11-.38-.13-.56-.05-.98.42-2.12.3-2.92-.31-1.07-.81-1.55-2.22-1.28-3.75.12-.66.1-1.32-.06-1.97-.24-.95-1.1-1.6-2.09-1.58-1 .02-1.83.74-2 1.72-.25 1.55-.95 2.94-2 4.02-1.38 1.41-2.05 3.39-1.85 5.4.2 2.05 1.42 3.86 3.25 4.82 1.37.72 2.94 1.01 4.5.83 2.74-.32 5.06-2.28 5.86-4.95.23-.75.29-1.52.19-2.28-.06-.5-.21-.99-.41-1.45l-.34-.99zm-4.98 6.47c-1.37 0-2.5-1.13-2.5-2.5s1.13-2.5 2.5-2.5 2.5 1.13 2.5 2.5-1.13 2.5-2.5 2.5z'/%3E%3C/svg%3E");
}
.rank-list {
2026-05-07 15:24:28 +08:00
margin-bottom: 32rpx;
background-color: #fafbfc; /* 增加极浅的灰底色作为容器 */
border-radius: 32rpx;
padding: 16rpx 24rpx;
2026-05-07 11:05:30 +08:00
}
.rank-item {
display: flex;
align-items: center;
2026-05-07 15:24:28 +08:00
padding: 16rpx 0;
border-bottom: 2rpx dashed rgba(0, 0, 0, 0.03); /* 柔和的虚线分割 */
}
.rank-item:last-child {
border-bottom: none;
2026-05-07 11:05:30 +08:00
}
.rank-num {
2026-05-07 15:24:28 +08:00
width: 44rpx;
2026-05-07 11:05:30 +08:00
font-size: 32rpx;
2026-05-07 15:24:28 +08:00
font-weight: 800;
2026-05-07 11:05:30 +08:00
text-align: center;
margin-right: 16rpx;
2026-05-07 15:24:28 +08:00
font-style: italic; /* 数字增加倾斜感,更动感 */
2026-05-07 11:05:30 +08:00
}
.rank-1 {
color: #2953ff;
2026-05-07 15:24:28 +08:00
font-size: 36rpx; /* 第一名数字更大 */
2026-05-07 11:05:30 +08:00
}
2026-05-07 15:24:28 +08:00
.rank-2 {
color: #ff8f00; /* 第二名橘色 */
}
.rank-3 {
color: #ffc107; /* 第三名黄色 */
2026-05-07 11:05:30 +08:00
}
.item-avatar {
2026-05-07 15:24:28 +08:00
width: 76rpx;
height: 76rpx;
2026-05-07 11:05:30 +08:00
border-radius: 50%;
margin-right: 24rpx;
background-color: #f0f0f0;
2026-05-07 15:24:28 +08:00
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.06); /* 头像增加微阴影 */
border: 4rpx solid #fff; /* 头像增加白边 */
2026-05-07 11:05:30 +08:00
}
.item-name {
flex: 1;
2026-05-07 15:24:28 +08:00
font-size: 32rpx;
color: #2c2c2c;
font-weight: 500;
2026-05-07 11:05:30 +08:00
}
.item-score {
2026-05-07 15:24:28 +08:00
font-size: 36rpx;
font-weight: 800;
font-family: 'DIN Alternate', sans-serif; /* 推荐使用数字字体,如果没有则回退到 sans-serif */
2026-05-07 11:05:30 +08:00
}
.score-1 {
color: #2953ff;
}
2026-05-07 15:24:28 +08:00
.score-2 {
color: #ff8f00;
}
.score-3 {
color: #ffc107;
2026-05-07 11:05:30 +08:00
}
.ai-summary {
display: flex;
align-items: flex-start;
2026-05-07 15:24:28 +08:00
padding: 24rpx 32rpx;
background: linear-gradient(135deg, #f8f6ff 0%, #ffffff 100%); /* 增加极浅的渐变背景 */
border: 2rpx solid rgba(157, 91, 254, 0.15); /* 边框更加柔和透明 */
border-radius: 24rpx;
2026-05-07 11:05:30 +08:00
margin-bottom: 24rpx;
2026-05-07 15:24:28 +08:00
box-shadow: 0 4rpx 16rpx rgba(157, 91, 254, 0.04); /* 增加紫色的微光阴影 */
2026-05-07 11:05:30 +08:00
}
.ai-icon {
2026-05-07 15:24:28 +08:00
width: 36rpx;
height: 36rpx;
margin-right: 16rpx;
margin-top: 2rpx;
2026-05-07 11:05:30 +08:00
flex-shrink: 0;
2026-05-07 15:24:28 +08:00
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");
2026-05-07 11:05:30 +08:00
}
.summary-text {
flex: 1;
2026-05-07 15:24:28 +08:00
font-size: 28rpx;
line-height: 1.6;
2026-05-07 11:05:30 +08:00
}
.summary-label {
2026-05-07 15:24:28 +08:00
color: #7b46f1; /* 柔和一点的紫色 */
font-weight: 800;
2026-05-07 11:05:30 +08:00
}
.summary-content {
2026-05-07 15:24:28 +08:00
color: #4a4a4a;
2026-05-07 11:05:30 +08:00
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.tag {
background-color: #f4eaff;
padding: 6rpx 20rpx;
border-radius: 100rpx;
}
.tag text {
color: #9d5bfe;
font-size: 22rpx;
}
.load-more {
text-align: center;
padding: 30rpx 0;
color: #999;
font-size: 24rpx;
}
</style>