587 lines
14 KiB
Vue
587 lines
14 KiB
Vue
<template>
|
||
<view class="starbook-content">
|
||
<!-- 背景图片 -->
|
||
<image class="background-image" src="/static/background/starbook.jpg" mode="aspectFill"></image>
|
||
|
||
<!-- 内容区域 -->
|
||
<view class="content-wrapper">
|
||
<!-- 类型Tab - 固定定位 -->
|
||
<view class="type-tabs">
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: currentType === 'regular' }"
|
||
@click="switchType('regular')"
|
||
>
|
||
<text>原创</text>
|
||
</view>
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: currentType === 'collection' }"
|
||
@click="switchType('collection')"
|
||
>
|
||
<text>典藏</text>
|
||
</view>
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: currentType === 'activity' }"
|
||
@click="switchType('activity')"
|
||
>
|
||
<text>活动</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载中 -->
|
||
<view v-if="loading" class="loading-container">
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-else-if="!hasData" class="empty-container">
|
||
<text class="empty-text">暂无藏品</text>
|
||
</view>
|
||
|
||
<!-- 藏品列表 - 可滚动区域 -->
|
||
<scroll-view v-else class="nft-scroll-view" scroll-y :show-scrollbar="false">
|
||
<view class="nft-list-container">
|
||
<!-- 原创藏品:按 category > grade 分组 -->
|
||
<view v-if="currentType === 'regular'">
|
||
<view v-for="(group, gIndex) in regularGroups" :key="group.category" class="nft-group">
|
||
<!-- 分组标题:category_name -->
|
||
<!-- <view class="group-header">
|
||
<text class="group-title">{{ group.category_name }}</text>
|
||
</view> -->
|
||
<!-- 该 category 下的所有 grades -->
|
||
<view v-for="gradeItem in group.grades" :key="gradeItem.grade" class="grade-section">
|
||
<view class="group-header">
|
||
<text class="group-title">{{ formatGrade(gradeItem.grade) }}</text>
|
||
</view>
|
||
<scroll-view class="nft-row" scroll-x :show-scrollbar="false" :enable-flex="true">
|
||
<view class="nft-row-content">
|
||
<view
|
||
v-for="item in gradeItem.items"
|
||
:key="item.asset_id"
|
||
class="nft-grid-item"
|
||
@click="handleCardClick(item)"
|
||
@touchstart.stop
|
||
>
|
||
<image
|
||
class="nft-image"
|
||
:src="item.coverUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="nft-info">
|
||
<text class="nft-name">{{ item.name }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 更多按钮 -->
|
||
<view v-if="gradeItem.has_more" class="nft-grid-item more-item" @click="goToMore(group, gradeItem.grade)" @touchstart.stop>
|
||
<!-- <view class="nft-image more-placeholder"></view> -->
|
||
<view class="more-overlay">
|
||
<text class="more-text">更多></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 典藏藏品:按 category 分组 -->
|
||
<view v-if="currentType === 'collection'">
|
||
<view v-for="group in collectionGroups" :key="group.category" class="nft-group">
|
||
<!-- 分组标题 -->
|
||
<!-- <view class="group-header">
|
||
<text class="group-title">{{ group.category_name }}</text>
|
||
</view> -->
|
||
<!-- 分组内容 -->
|
||
<scroll-view class="nft-row" scroll-x :show-scrollbar="false" :enable-flex="true">
|
||
<view class="nft-row-content">
|
||
<view
|
||
v-for="(item, index) in group.items"
|
||
:key="item.asset_id"
|
||
class="nft-grid-item"
|
||
@click="handleCardClick(item)"
|
||
@touchstart.stop
|
||
>
|
||
<image
|
||
class="nft-image"
|
||
:src="item.coverUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="nft-info">
|
||
<text class="nft-name">{{ item.name }}</text>
|
||
<text class="nft-likes">★{{ item.like_count }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 更多按钮 -->
|
||
<view v-if="group.has_more" class="nft-grid-item more-item" @click="goToMore(group)" @touchstart.stop>
|
||
<!-- <view class="nft-image more-placeholder"></view> -->
|
||
<view class="more-overlay">
|
||
<text class="more-text">更多></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 活动藏品:按 activity_type 分组 -->
|
||
<view v-if="currentType === 'activity'">
|
||
<view v-for="group in activityGroups" :key="group.category" class="nft-group">
|
||
<!-- 分组标题 -->
|
||
<!-- <view class="group-header">
|
||
<text class="group-title">{{ group.category_name }}</text>
|
||
</view> -->
|
||
<!-- 分组内容 -->
|
||
<scroll-view class="nft-row" scroll-x :show-scrollbar="false" :enable-flex="true">
|
||
<view class="nft-row-content">
|
||
<view
|
||
v-for="(item, index) in group.items"
|
||
:key="item.asset_id"
|
||
class="nft-grid-item"
|
||
@click="handleCardClick(item)"
|
||
@touchstart.stop
|
||
>
|
||
<image
|
||
class="nft-image"
|
||
:src="item.coverUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="nft-info">
|
||
<text class="nft-name">{{ item.name }}</text>
|
||
<text class="nft-likes">★{{ item.like_count }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 更多按钮 -->
|
||
<view v-if="group.has_more" class="nft-grid-item more-item" @click="goToMore(group)" @touchstart.stop>
|
||
<!-- <view class="nft-image more-placeholder"></view> -->
|
||
<view class="more-overlay">
|
||
<text class="more-text">更多></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onActivated, watch } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import { getStarbookHomeApi } from '@/utils/api.js';
|
||
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
|
||
|
||
// 屏幕宽度
|
||
const screenWidth = ref(0);
|
||
|
||
// 加载状态
|
||
const loading = ref(false);
|
||
|
||
// 当前选中的类型
|
||
const currentType = ref('regular');
|
||
|
||
// 星册首页数据
|
||
const starbookData = ref([]);
|
||
|
||
// 处理后的数据(带有有效的封面URL)
|
||
const processedData = ref([]);
|
||
|
||
// 上次加载时间(用于防抖)
|
||
let lastLoadedAt = 0;
|
||
|
||
// 计算卡片尺寸(一行约2.5张卡片的宽度,让图片更大)
|
||
const cardSize = computed(() => {
|
||
const marginLeft = 24; // 左边距24rpx
|
||
const gap = 15; // 卡片间距15rpx
|
||
const availableWidth = 750 - marginLeft - (gap * 3.5);
|
||
return Math.floor(availableWidth / 2.5);
|
||
});
|
||
|
||
// grade 中文转换
|
||
const gradeMap = { 1: '一', 2: '二', 3: '三', 4: '四', 5: '五' };
|
||
function formatGrade(grade) {
|
||
return `等级${gradeMap[grade] || grade}`;
|
||
}
|
||
|
||
// 判断是否有数据
|
||
const hasData = computed(() => {
|
||
return processedData.value.length > 0;
|
||
});
|
||
|
||
// 根据当前类型获取分组数据(使用处理后的数据)
|
||
const regularGroups = computed(() => {
|
||
return processedData.value.filter(g => g.type === 'regular');
|
||
});
|
||
const collectionGroups = computed(() => {
|
||
return processedData.value.filter(g => g.type === 'collection');
|
||
});
|
||
|
||
const activityGroups = computed(() => {
|
||
return processedData.value.filter(g => g.type === 'activity');
|
||
});
|
||
|
||
// 切换类型
|
||
const switchType = (type) => {
|
||
currentType.value = type;
|
||
};
|
||
|
||
// 加载星册数据
|
||
const loadStarbookData = async () => {
|
||
const now = Date.now();
|
||
if (now - lastLoadedAt < 1000) return; // 1秒内不重复加载
|
||
lastLoadedAt = now;
|
||
|
||
loading.value = true;
|
||
try {
|
||
const response = await getStarbookHomeApi();
|
||
if (response.code === 200 && response.data && response.data.data.groups) {
|
||
// 处理数据,获取有效的封面URL
|
||
await processGroupsWithValidUrls(response.data.data.groups);
|
||
}
|
||
} catch (error) {
|
||
console.error('获取星册数据失败:', error);
|
||
uni.showToast({
|
||
title: error.message || '获取星册数据失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
starbookData.value = [];
|
||
processedData.value = [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
// 处理分组数据,获取有效的封面URL
|
||
const processGroupsWithValidUrls = async (groups) => {
|
||
const processed = JSON.parse(JSON.stringify(groups)); // 深拷贝
|
||
|
||
// 遍历所有分组和藏品,获取有效的封面URL
|
||
for (const group of processed) {
|
||
// 处理 regular 类型的 grades
|
||
if (group.grades) {
|
||
for (const grade of group.grades) {
|
||
for (const item of grade.items || []) {
|
||
item.coverUrl = await getAssetCoverRealUrl(item.cover_url_signed || '');
|
||
}
|
||
}
|
||
}
|
||
// 处理 collection/activity 类型的 items
|
||
if (group.items) {
|
||
for (const item of group.items) {
|
||
item.coverUrl = await getAssetCoverRealUrl(item.cover_url_signed || '');
|
||
}
|
||
}
|
||
}
|
||
|
||
processedData.value = processed;
|
||
};
|
||
|
||
// 点击卡片跳转到详情页
|
||
const handleCardClick = (item) => {
|
||
if (item.asset_id) {
|
||
uni.navigateTo({
|
||
url: `/pages/asset-detail/asset-detail?asset_id=${item.asset_id}`
|
||
});
|
||
}
|
||
};
|
||
|
||
// 点击更多跳转到查看更多页面
|
||
const goToMore = (group, grade) => {
|
||
const params = {
|
||
type: currentType.value,
|
||
category: group.category
|
||
};
|
||
if (currentType.value === 'regular' && grade) {
|
||
params.grade = grade;
|
||
}
|
||
const queryString = Object.entries(params)
|
||
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
||
.join('&');
|
||
uni.navigateTo({
|
||
url: `/pages/starbook/items?${queryString}`
|
||
});
|
||
};
|
||
|
||
// 定义 props
|
||
const props = defineProps({
|
||
isActive: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
});
|
||
|
||
onMounted(() => {
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
screenWidth.value = systemInfo.windowWidth;
|
||
loadStarbookData();
|
||
});
|
||
|
||
// 每次组件激活时重新加载数据
|
||
onActivated(() => {
|
||
loadStarbookData();
|
||
});
|
||
|
||
// 监听页面显示事件
|
||
onShow(() => {
|
||
if (props.isActive) {
|
||
loadStarbookData();
|
||
}
|
||
});
|
||
|
||
// 监听 isActive prop 变化
|
||
watch(() => props.isActive, (newVal) => {
|
||
if (newVal) {
|
||
loadStarbookData();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.starbook-content {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
/* overflow-y: auto; */
|
||
overflow: hidden;
|
||
background: #0d0820;
|
||
}
|
||
|
||
/* 滚动条样式 */
|
||
.starbook-content::-webkit-scrollbar {
|
||
width: 6rpx;
|
||
}
|
||
|
||
.starbook-content::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.starbook-content::-webkit-scrollbar-thumb {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
border-radius: 10rpx;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.starbook-content::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.background-image {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
object-fit: cover;
|
||
min-width: 100%;
|
||
min-height: 100%;
|
||
}
|
||
|
||
.content-wrapper {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 100%;
|
||
min-height: 100%;
|
||
padding: 192rpx 30rpx 120rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 类型Tab - 固定在顶部 */
|
||
.type-tabs {
|
||
/* position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0; */
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 40rpx;
|
||
padding: 20rpx 30rpx;
|
||
z-index: 100;
|
||
}
|
||
|
||
.tab-item {
|
||
padding: 12rpx 30rpx;
|
||
font-size: 28rpx;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
border-bottom: 4rpx solid transparent;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.tab-item.active {
|
||
color: #ffffff;
|
||
border-bottom-color: #ffffff;
|
||
}
|
||
|
||
/* 可滚动区域 */
|
||
.nft-scroll-view {
|
||
height: calc(100vh - 320rpx);
|
||
}
|
||
|
||
/* 隐藏滚动条 */
|
||
.nft-scroll-view::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
.nft-scroll-view {
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
|
||
/* 加载中 */
|
||
.loading-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding-top: 200rpx;
|
||
}
|
||
|
||
.loading-text {
|
||
color: rgba(255, 255, 255, 0.6);
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
/* 空状态 */
|
||
.empty-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding-top: 200rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
color: rgba(255, 255, 255, 0.6);
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
/* 藏品列表容器 */
|
||
.nft-list-container {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 藏品分组 */
|
||
.nft-group {
|
||
margin-bottom: 50rpx;
|
||
}
|
||
|
||
/* 等级区块 */
|
||
.grade-section {
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
/* 分组标题 */
|
||
.group-header {
|
||
margin-bottom: 16rpx;
|
||
padding-bottom: 10rpx;
|
||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.group-title {
|
||
font-size: 26rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
/* 藏品行 - 水平滚动 */
|
||
.nft-row {
|
||
width: 100%;
|
||
height: 320rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 藏品行内容容器 */
|
||
.nft-row-content {
|
||
display: inline-block;
|
||
white-space: nowrap;
|
||
padding-left: 24rpx;
|
||
height: 100%;
|
||
}
|
||
|
||
/* 隐藏滚动条 */
|
||
.nft-row::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
.nft-row {
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
|
||
/* 藏品网格项 */
|
||
.nft-grid-item {
|
||
position: relative;
|
||
display: inline-block;
|
||
vertical-align: top;
|
||
margin-right: 32rpx;
|
||
height: 100%;
|
||
}
|
||
|
||
/* NFT 图片 */
|
||
.nft-image {
|
||
width: 192rpx;
|
||
height: 224rpx;
|
||
border-radius: 16rpx;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
display: block;
|
||
}
|
||
|
||
/* 更多占位符 */
|
||
.more-placeholder {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.nft-grid-item.more-item {
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 藏品信息 */
|
||
.nft-info {
|
||
padding: 12rpx 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.nft-name {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.nft-likes {
|
||
font-size: 20rpx;
|
||
color: rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
/* 更多覆盖层 */
|
||
.more-overlay {
|
||
/* position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%); */
|
||
width: 192rpx;
|
||
height: 224rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.more-text {
|
||
font-size: 26rpx;
|
||
color: #ffffff;
|
||
}
|
||
</style>
|