524 lines
12 KiB
Vue
524 lines
12 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>
|
|
|
|
<!-- 藏品列表 -->
|
|
<view v-else class="nft-list-container">
|
|
<!-- 普通藏品:按 grade 分组 -->
|
|
<view v-if="currentType === 'regular'">
|
|
<view v-for="group in regularGroups" :key="group.category" class="nft-group">
|
|
<!-- 分组标题 -->
|
|
<view class="group-header">
|
|
<text class="group-title">{{ group.category_name }} · {{ formatGrade(group.grade) }}</text>
|
|
</view>
|
|
<!-- 分组内容 -->
|
|
<view class="nft-row">
|
|
<view
|
|
v-for="(item, index) in group.items"
|
|
:key="item.asset_id"
|
|
class="nft-grid-item"
|
|
@click="handleCardClick(item)"
|
|
>
|
|
<NftCard
|
|
:cover-image="item.cover_url_signed"
|
|
:width="cardSize"
|
|
:height="cardSize"
|
|
:locked="false"
|
|
:custom-style="cardCustomStyle"
|
|
/>
|
|
<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)">
|
|
<NftCard
|
|
:cover-image="''"
|
|
:width="cardSize"
|
|
:height="cardSize"
|
|
:locked="false"
|
|
:show-add-button="false"
|
|
operation="none"
|
|
:custom-style="cardCustomStyle"
|
|
/>
|
|
<view class="more-overlay">
|
|
<text class="more-text">更多></text>
|
|
</view>
|
|
</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>
|
|
<!-- 分组内容 -->
|
|
<view class="nft-row">
|
|
<view
|
|
v-for="(item, index) in group.items"
|
|
:key="item.asset_id"
|
|
class="nft-grid-item"
|
|
@click="handleCardClick(item)"
|
|
>
|
|
<NftCard
|
|
:cover-image="item.cover_url_signed"
|
|
:width="cardSize"
|
|
:height="cardSize"
|
|
:locked="false"
|
|
:custom-style="cardCustomStyle"
|
|
/>
|
|
<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)">
|
|
<NftCard
|
|
:cover-image="''"
|
|
:width="cardSize"
|
|
:height="cardSize"
|
|
:locked="false"
|
|
:show-add-button="false"
|
|
operation="none"
|
|
:custom-style="cardCustomStyle"
|
|
/>
|
|
<view class="more-overlay">
|
|
<text class="more-text">更多></text>
|
|
</view>
|
|
</view>
|
|
</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>
|
|
<!-- 分组内容 -->
|
|
<view class="nft-row">
|
|
<view
|
|
v-for="(item, index) in group.items"
|
|
:key="item.asset_id"
|
|
class="nft-grid-item"
|
|
@click="handleCardClick(item)"
|
|
>
|
|
<NftCard
|
|
:cover-image="item.cover_url_signed"
|
|
:width="cardSize"
|
|
:height="cardSize"
|
|
:locked="false"
|
|
:custom-style="cardCustomStyle"
|
|
/>
|
|
<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)">
|
|
<NftCard
|
|
:cover-image="''"
|
|
:width="cardSize"
|
|
:height="cardSize"
|
|
:locked="false"
|
|
:show-add-button="false"
|
|
operation="none"
|
|
:custom-style="cardCustomStyle"
|
|
/>
|
|
<view class="more-overlay">
|
|
<text class="more-text">更多></text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onActivated, watch } from 'vue';
|
|
import { onShow } from '@dcloudio/uni-app';
|
|
import NftCard from './NftCard.vue';
|
|
import { getStarbookHomeApi } from '@/utils/api.js';
|
|
|
|
// 屏幕宽度
|
|
const screenWidth = ref(0);
|
|
|
|
// 加载状态
|
|
const loading = ref(false);
|
|
|
|
// 当前选中的类型
|
|
const currentType = ref('regular');
|
|
|
|
// 星册首页数据
|
|
const starbookData = ref([]);
|
|
|
|
// 上次加载时间(用于防抖)
|
|
let lastLoadedAt = 0;
|
|
|
|
// 计算卡片尺寸
|
|
const cardSize = computed(() => {
|
|
if (screenWidth.value === 0) return 200;
|
|
const rpxToPx = screenWidth.value / 750;
|
|
const padding = 30 * rpxToPx; // 左右各30rpx
|
|
const gap = 15 * rpxToPx; // 卡片间距15rpx
|
|
const availableWidth = screenWidth.value - (padding * 2) - (gap * 2);
|
|
return Math.floor(availableWidth / 3);
|
|
});
|
|
|
|
// 卡片自定义样式
|
|
const cardCustomStyle = {
|
|
position: 'absolute',
|
|
top: '0',
|
|
left: '0'
|
|
};
|
|
|
|
// grade 中文转换
|
|
const gradeMap = { 1: '一', 2: '二', 3: '三', 4: '四', 5: '五' };
|
|
function formatGrade(grade) {
|
|
return `等级${gradeMap[grade] || grade}`;
|
|
}
|
|
|
|
// 判断是否有数据
|
|
const hasData = computed(() => {
|
|
return starbookData.value.length > 0;
|
|
});
|
|
|
|
// 根据当前类型获取分组数据
|
|
const regularGroups = computed(() => {
|
|
const group = starbookData.value.find(g => g.type === 'regular');
|
|
if (!group) return [];
|
|
return (group.grades || []).sort((a, b) => b.grade - a.grade); // grade大的在上
|
|
});
|
|
|
|
const collectionGroups = computed(() => {
|
|
return starbookData.value.filter(g => g.type === 'collection');
|
|
});
|
|
|
|
const activityGroups = computed(() => {
|
|
return starbookData.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.groups) {
|
|
starbookData.value = response.data.groups;
|
|
}
|
|
} catch (error) {
|
|
console.error('获取星册数据失败:', error);
|
|
uni.showToast({
|
|
title: error.message || '获取星册数据失败',
|
|
icon: 'none',
|
|
duration: 2000
|
|
});
|
|
starbookData.value = [];
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// 点击卡片跳转到详情页
|
|
const handleCardClick = (item) => {
|
|
if (item.asset_id) {
|
|
uni.navigateTo({
|
|
url: `/pages/asset-detail/asset-detail?asset_id=${item.asset_id}`
|
|
});
|
|
}
|
|
};
|
|
|
|
// 点击更多跳转到查看更多页面
|
|
const goToMore = (group) => {
|
|
const params = {
|
|
type: currentType.value,
|
|
category: group.category
|
|
};
|
|
if (currentType.value === 'regular' && group.grade) {
|
|
params.grade = group.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-x: 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: 224rpx 30rpx 120rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* 类型Tab */
|
|
.type-tabs {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 40rpx;
|
|
margin-bottom: 30rpx;
|
|
padding: 0 20rpx;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* 加载中 */
|
|
.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: 40rpx;
|
|
}
|
|
|
|
/* 分组标题 */
|
|
.group-header {
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.group-title {
|
|
font-size: 26rpx;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
/* 藏品行(横向滚动) */
|
|
.nft-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 15rpx;
|
|
}
|
|
|
|
/* 藏品网格项 */
|
|
.nft-grid-item {
|
|
position: relative;
|
|
width: 210rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.nft-grid-item.more-item {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* 藏品信息 */
|
|
.nft-info {
|
|
padding: 8rpx 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: 100%;
|
|
height: 100%;
|
|
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>
|