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"
|
2026-05-21 06:56:42 +08:00
|
|
|
|
:class="{ active: currentCategoryId === category.id }"
|
|
|
|
|
|
v-for="category in categories"
|
|
|
|
|
|
:key="category.id"
|
|
|
|
|
|
@tap="switchCategory(category.id)"
|
2026-05-07 11:09:32 +08:00
|
|
|
|
>
|
2026-05-21 06:56:42 +08:00
|
|
|
|
<text class="tab-text">{{ category.title }}</text>
|
|
|
|
|
|
<view class="tab-line" v-if="currentCategoryId === category.id"></view>
|
2026-05-07 11:09:32 +08:00
|
|
|
|
</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">
|
2026-05-21 18:00:07 +08:00
|
|
|
|
<TopicCard
|
|
|
|
|
|
v-for="topic in topicList"
|
|
|
|
|
|
:key="topic.id"
|
|
|
|
|
|
:topic="topic"
|
|
|
|
|
|
@item-click="goToRating"
|
|
|
|
|
|
/>
|
2026-05-07 11:05:30 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 加载更多 -->
|
|
|
|
|
|
<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";
|
2026-05-21 06:56:42 +08:00
|
|
|
|
import { fetchTopicList, fetchTopicCategories } from "@/api/topic";
|
2026-05-07 11:05:30 +08:00
|
|
|
|
import { getShareToken, saveViewRequest } from "@/utils/common.js";
|
|
|
|
|
|
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
2026-05-21 18:00:07 +08:00
|
|
|
|
import TopicCard from "@/components/TopicCard/TopicCard.vue";
|
2026-05-07 11:05:30 +08:00
|
|
|
|
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-21 06:56:42 +08:00
|
|
|
|
const currentCategoryId = ref('');
|
|
|
|
|
|
const categories = ref([]);
|
|
|
|
|
|
const currentPage = ref(1);
|
2026-05-07 11:09:32 +08:00
|
|
|
|
|
2026-05-21 06:56:42 +08:00
|
|
|
|
const loadCategories = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetchTopicCategories();
|
|
|
|
|
|
// 提取真实数据数组
|
|
|
|
|
|
const list = res?.data?.list || res?.list || res || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 增加一个“全部”的默认选项,id 置空
|
2026-05-21 18:00:07 +08:00
|
|
|
|
categories.value = [{ id: 'recommend', title: '推荐' }, ...list];
|
2026-05-21 06:56:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化选中的ID为“全部”
|
|
|
|
|
|
if (!currentCategoryId.value && categories.value.length > 0) {
|
|
|
|
|
|
currentCategoryId.value = categories.value[0].id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载初始列表
|
2026-05-21 18:00:07 +08:00
|
|
|
|
// loadTopicList(currentCategoryId.value, currentPage.value);
|
2026-05-21 06:56:42 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取分类失败:", error);
|
|
|
|
|
|
// 兜底数据
|
|
|
|
|
|
categories.value = [
|
|
|
|
|
|
{ id: '', title: '全部' },
|
|
|
|
|
|
{ id: 1, title: '历史' },
|
|
|
|
|
|
{ id: 2, title: '数码' },
|
|
|
|
|
|
];
|
|
|
|
|
|
// 使用兜底也尝试加载一次
|
|
|
|
|
|
loadTopicList(currentCategoryId.value, currentPage.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const loadTopicList = async (categoryId, page = 1) => {
|
|
|
|
|
|
if (page === 1) {
|
|
|
|
|
|
topicList.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
const res = await fetchTopicList(categoryId, page);
|
|
|
|
|
|
// 提取真实数据数组
|
|
|
|
|
|
const list = res?.list || [];
|
|
|
|
|
|
// 合并数据
|
|
|
|
|
|
topicList.value = [...topicList.value, ...list];
|
|
|
|
|
|
hasMore.value = list.length >= 5;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取主题列表失败:", error);
|
|
|
|
|
|
hasMore.value = false;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-05-07 11:09:32 +08:00
|
|
|
|
|
2026-05-21 06:56:42 +08:00
|
|
|
|
const switchCategory = (categoryId) => {
|
|
|
|
|
|
if (currentCategoryId.value === categoryId) return;
|
|
|
|
|
|
currentCategoryId.value = categoryId;
|
|
|
|
|
|
|
2026-05-07 11:09:32 +08:00
|
|
|
|
// 切换分类时重新加载数据
|
2026-05-21 06:56:42 +08:00
|
|
|
|
currentPage.value = 1;
|
2026-05-07 11:09:32 +08:00
|
|
|
|
hasMore.value = true;
|
2026-05-21 06:56:42 +08:00
|
|
|
|
loadTopicList(categoryId, currentPage.value);
|
2026-05-07 11:09:32 +08:00
|
|
|
|
};
|
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
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-21 06:56:42 +08:00
|
|
|
|
onLoad(async (options) => {
|
2026-05-07 11:05:30 +08:00
|
|
|
|
uni.$trackRecord({
|
|
|
|
|
|
eventName: "index_page_visit",
|
|
|
|
|
|
eventType: `visit`,
|
|
|
|
|
|
})
|
|
|
|
|
|
if (options.shareToken) {
|
|
|
|
|
|
saveViewRequest(options.shareToken, "index");
|
|
|
|
|
|
}
|
2026-05-21 06:56:42 +08:00
|
|
|
|
await loadCategories(); // 初始化时加载分类
|
|
|
|
|
|
await loadTopicList(currentCategoryId.value); // 初始化时加载主题列表
|
2026-05-07 11:05:30 +08:00
|
|
|
|
initData();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onShow(() => {
|
|
|
|
|
|
if (firstLoad) {
|
|
|
|
|
|
firstLoad = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
initData();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const handleLoginSuccess = () => {
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 下拉刷新
|
2026-05-21 06:56:42 +08:00
|
|
|
|
onPullDownRefresh(async () => {
|
|
|
|
|
|
currentPage.value = 1;
|
|
|
|
|
|
hasMore.value = true;
|
|
|
|
|
|
await loadTopicList(currentCategoryId.value, currentPage.value);
|
|
|
|
|
|
uni.stopPullDownRefresh();
|
2026-05-07 11:05:30 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 上拉加载更多
|
|
|
|
|
|
onReachBottom(() => {
|
|
|
|
|
|
if (!hasMore.value || loading.value) return;
|
2026-05-21 06:56:42 +08:00
|
|
|
|
currentPage.value += 1;
|
|
|
|
|
|
loadTopicList(currentCategoryId.value, currentPage.value);
|
2026-05-07 11:05:30 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.load-more {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 30rpx 0;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|