topfans/frontend/pages/components/CastloveContent.vue
2026-04-12 21:24:23 +08:00

656 lines
14 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="castlove-container">
<!-- 背景图片 -->
<image class="background-image" src="/static/background/starbook.jpg" mode="aspectFill"></image>
<!-- 蒙层 -->
<!-- <view class="background-overlay"></view> -->
<!-- 左上角返回按钮 -->
<view class="back-button" :style="{ top: backButtonTop }" @click="handleBack">
<image class="back-icon" src="/static/icon/back.png" mode="aspectFit" />
</view>
<!-- 内容区域 -->
<scroll-view class="content-wrapper" :class="{ 'fixed-category': isFixed }" scroll-y @scrolltolower="loadMore" @scroll="handleScroll" :show-scrollbar="false" :scroll-top="scrollTop">
<!-- 区域一顶部运营轮播图 -->
<view class="banner-section">
<swiper
class="banner-swiper"
:indicator-dots="true"
:autoplay="true"
:interval="3000"
:duration="500"
indicator-color="rgba(255, 255, 255, 0.3)"
indicator-active-color="#FF6B9D"
>
<swiper-item v-for="(banner, index) in bannerList" :key="index" @click="handleBannerClick(banner)">
<image class="banner-image" :src="banner.image_url" mode="aspectFill"></image>
<view class="banner-overlay">
<text class="banner-title">{{ banner.title }}</text>
</view>
</swiper-item>
</swiper>
</view>
<!-- 区域二主Tab标签区星卡/吧唧/海报) -->
<view class="main-tab-section">
<view
v-for="(tab, index) in mainTabs"
:key="index"
class="tab-item"
@click="handleMainTabClick(tab)"
>
<image class="tab-icon" :src="tab.icon" mode="aspectFit"></image>
<text class="tab-name">{{ tab.name }}</text>
</view>
</view>
<!-- 区域三:分类标签区 -->
<view id="category-section" class="category-section" :class="{ fixed: isFixed }">
<scroll-view class="category-scroll" scroll-x :show-scrollbar="false">
<view
v-for="(category, index) in categories"
:key="index"
class="category-item"
:class="{ active: currentCategory === category.value }"
@click="handleCategoryChange(category.value)"
>
<text class="category-text">{{ category.label }}</text>
</view>
</scroll-view>
</view>
<!-- 占位元素始终存在但透明度为0避免跳动 -->
<!-- <view class="category-placeholder" :style="{ opacity: isFixed ? 1 : 0, height: isFixed ? '32rpx' : '0' }"></view> -->
<!-- 区域四:创作网格列表 -->
<view class="creation-grid">
<view
v-for="(item, index) in creationList"
:key="item.id"
class="creation-card"
@click="handleCardClick(item)"
>
<image class="creation-image" :src="item.cover_image" mode="aspectFill"></image>
<view class="creation-info">
<view class="creation-id">
<text class="id-text">#{{ item.certificate_id }}</text>
</view>
<view class="creation-meta">
<view class="creator-info">
<image class="creator-avatar" :src="item.creator_avatar" mode="aspectFill"></image>
<text class="creator-name">{{ item.creator_name }}</text>
</view>
<view class="like-info">
<text class="like-icon">♡</text>
<text class="like-count">{{ formatCount(item.like_count) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view class="loading-more" v-if="loading">
<text class="loading-text">加载中...</text>
</view>
<view class="no-more" v-if="noMore && creationList.length > 0">
<text class="no-more-text">没有更多了</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
// 判断是否为Android设备
const isAndroid = ref(false);
// 计算返回按钮的top值
const backButtonTop = computed(() => {
if (isAndroid.value) {
return 'calc(env(safe-area-inset-top) + 80rpx)';
}
return 'calc(env(safe-area-inset-top) + 32rpx)';
});
// 轮播图数据
const bannerList = ref([
{
image_url: '/static/sucai/image-01.png',
title: '这河非遇美学宇宙联名',
link_type: 'activity',
link_value: '1'
},
{
image_url: '/static/sucai/image-02.png',
title: '限时创作活动',
link_type: 'activity',
link_value: '2'
},
{
image_url: '/static/sucai/image-03.png',
title: '精选作品展示',
link_type: 'topic',
link_value: '3'
}
]);
// 主Tab配置
const mainTabs = ref([
{
name: '星卡',
type: 'star_card',
icon: '/static/sucai/image-04.png'
},
{
name: '吧唧',
type: 'badge',
icon: '/static/sucai/image-05.png'
},
{
name: '海报',
type: 'poster',
icon: '/static/sucai/image-06.png'
}
]);
// 分类配置
const categories = ref([
{ label: '热门作品', value: 'hot' },
{ label: '最新作品', value: 'latest' },
{ label: '星卡', value: 'star_card' },
{ label: '吧唧', value: 'badge' },
{ label: '海报', value: 'poster' }
]);
// 当前选中的分类
const currentCategory = ref('hot');
// 创作列表
const creationList = ref([]);
// 分页参数
const page = ref(1);
const pageSize = ref(10);
const loading = ref(false);
const noMore = ref(false);
// 分类栏吸附状态
const isFixed = ref(false);
// 滚动位置
const scrollTop = ref(0);
// 页面加载
onMounted(() => {
// 检测平台
const systemInfo = uni.getSystemInfoSync();
console.log('[CastloveContent] 系统信息:', systemInfo);
isAndroid.value = systemInfo.platform === 'android';
console.log('[CastloveContent] 是否为Android:', isAndroid.value);
loadBanners();
loadCreations();
});
// 加载轮播图
const loadBanners = async () => {
try {
// TODO: 调用后台接口获取轮播图数据
// const res = await request('/api/castlove/banners', 'GET');
// bannerList.value = res.data;
} catch (e) {
console.error('加载轮播图失败:', e);
}
};
// 加载创作列表
const loadCreations = async () => {
if (loading.value || noMore.value) return;
loading.value = true;
try {
// TODO: 调用后台接口获取创作列表
// const res = await request('/api/castlove/creations', 'GET', {
// category: currentCategory.value,
// page: page.value,
// page_size: pageSize.value
// });
// 模拟数据
const mockData = generateMockData();
if (page.value === 1) {
creationList.value = mockData;
} else {
creationList.value = [...creationList.value, ...mockData];
}
if (mockData.length < pageSize.value) {
noMore.value = true;
}
} catch (e) {
console.error('加载创作列表失败:', e);
uni.showToast({
title: '加载失败',
icon: 'none'
});
} finally {
loading.value = false;
}
};
// 素材图片列表
const sucaiImages = [
'/static/sucai/image-01.png',
'/static/sucai/image-02.png',
'/static/sucai/image-03.png',
'/static/sucai/image-04.png',
'/static/sucai/image-05.png',
'/static/sucai/image-06.png',
'/static/sucai/image-07.png',
'/static/sucai/image-08.png',
'/static/sucai/image-09.png',
'/static/sucai/image-10.png',
'/static/sucai/image-11.png',
'/static/sucai/image-12.png',
'/static/sucai/image-13.png',
'/static/sucai/image-14.png',
'/static/sucai/image-15.png',
'/static/sucai/image-16.png'
];
// 生成模拟数据
const generateMockData = () => {
const data = [];
for (let i = 0; i < pageSize.value; i++) {
// 随机选择一张素材图片
const randomImageIndex = Math.floor(Math.random() * sucaiImages.length);
data.push({
id: `${Date.now()}_${i}`,
cover_image: sucaiImages[randomImageIndex],
certificate_id: `ZUA-20260409-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,
creator_name: `用户${Math.floor(Math.random() * 1000)}`,
creator_avatar: '/static/avatar/default.png',
like_count: Math.floor(Math.random() * 1000),
type: currentCategory.value === 'hot' || currentCategory.value === 'latest'
? ['star_card', 'badge', 'poster'][Math.floor(Math.random() * 3)]
: currentCategory.value
});
}
return data;
};
// 轮播图点击
const handleBannerClick = (banner) => {
console.log('点击轮播图:', banner);
// TODO: 根据link_type跳转
};
// 主Tab点击 - 进入铸造页面
const handleMainTabClick = (tab) => {
console.log('点击主Tab:', tab);
// 只有星卡类型才跳转到创建页面
if (tab.type === 'star_card') {
uni.navigateTo({
url: `/pages/castlove/create?type=${tab.type}&name=${encodeURIComponent(tab.name)}`
});
} else {
// 其他类型暂时提示
uni.showToast({
title: `${tab.name}功能开发中`,
icon: 'none',
duration: 1500
});
}
};
// 分类切换
const handleCategoryChange = (value) => {
if (currentCategory.value === value) return;
currentCategory.value = value;
page.value = 1;
noMore.value = false;
creationList.value = [];
// 滚动到创作网格列表的顶部(分类栏位置)
// 轮播图360rpx + 主Tab约280rpx + 间距 ≈ 320px
scrollTop.value = 360;
// 强制触发滚动更新
setTimeout(() => {
scrollTop.value = 361;
}, 100);
loadCreations();
};
// 创作卡片点击
const handleCardClick = (item) => {
console.log('点击创作卡片:', item);
uni.navigateTo({
url: `/pages/castlove/detail?id=${item.id}`
});
};
// 加载更多
const loadMore = () => {
if (!loading.value && !noMore.value) {
page.value++;
loadCreations();
}
};
// 处理滚动事件
const handleScroll = (e) => {
const scrollTop = e.detail.scrollTop;
// 当滚动超过阈值时固定分类栏轮播图360rpx + 主Tab约280rpx + 间距)
isFixed.value = scrollTop > 360; // 约640rpx转px
};
// 格式化数字
const formatCount = (count) => {
if (count >= 10000) {
return (count / 10000).toFixed(1) + 'w';
}
return count;
};
// 返回按钮
const handleBack = () => {
uni.redirectTo({
url: '/pages/square/square'
});
};
</script>
<style scoped>
.castlove-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.background-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.background-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
background: rgba(0, 0, 0, 0.1);
}
/* 左上角返回按钮 */
.back-button {
position: fixed;
/* top值通过JS动态设置 */
left: 32rpx;
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 101;
}
.back-icon {
width: 64rpx;
height: 64rpx;
}
.content-wrapper {
position: relative;
z-index: 1;
width: 100%;
height: calc(100vh - 64rpx);
margin-top: 160rpx;
padding: 0 24rpx;
box-sizing: border-box;
transition: margin-top 0.3s ease;
}
.content-wrapper.fixed-category {
margin-top: 5rem;
}
/* 区域一:轮播图 */
.banner-section {
width: 100%;
margin-bottom: 32rpx;
}
.banner-swiper {
width: 100%;
height: 360rpx;
border-radius: 24rpx;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
}
.banner-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 24rpx;
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
}
.banner-title {
font-size: 28rpx;
color: #fff;
font-weight: 600;
}
/* 区域二主Tab */
.main-tab-section {
display: flex;
justify-content: space-around;
align-items: center;
padding: 24rpx 0;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200rpx;
height: 200rpx;
background: linear-gradient(135deg, rgba(240, 228, 177, 0.3), rgba(240, 131, 153, 0.3));
border-radius: 24rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.2);
transition: all 0.3s;
}
.tab-item:active {
transform: scale(0.95);
opacity: 0.8;
}
.tab-icon {
width: 120rpx;
height: 120rpx;
margin-bottom: 12rpx;
border-radius: 50%;
object-fit: cover;
}
.tab-name {
font-size: 28rpx;
color: #fff;
font-weight: 600;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
/* 区域三:分类标签 */
.category-section {
margin-bottom: 24rpx;
transition: all 0.3s ease;
will-change: transform;
}
.category-section.fixed {
position: fixed;
top: 64rpx;
left: 24rpx;
right: 24rpx;
z-index: 100;
padding: 16rpx 0;
}
/* 分类占位符 */
.category-placeholder {
margin-bottom: 4rpx;
transition: all 0.3s ease;
pointer-events: none;
}
.category-scroll {
white-space: nowrap;
}
.category-item {
display: inline-block;
padding: 16rpx 32rpx;
margin-right: 16rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 40rpx;
backdrop-filter: blur(10rpx);
transition: all 0.3s;
}
.category-item.active {
background: linear-gradient(135deg, #F0E4B1, #F08399);
box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.4);
}
.category-text {
font-size: 26rpx;
color: #fff;
font-weight: 500;
}
.category-item.active .category-text {
font-weight: 600;
}
/* 区域四:创作网格 */
.creation-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding-bottom: 40rpx;
position: relative;
z-index: 1;
}
.creation-card {
width: 48%;
margin-bottom: 24rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 20rpx;
overflow: hidden;
backdrop-filter: blur(10rpx);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.creation-image {
width: 100%;
height: 400rpx;
}
.creation-info {
padding: 16rpx;
}
.creation-id {
margin-bottom: 12rpx;
}
.id-text {
font-size: 22rpx;
color: #FFB800;
font-weight: 600;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.creation-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.creator-info {
display: flex;
align-items: center;
}
.creator-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 8rpx;
}
.creator-name {
font-size: 22rpx;
color: #fff;
}
.like-info {
display: flex;
align-items: center;
}
.like-icon {
font-size: 28rpx;
color: #FF6B9D;
margin-right: 4rpx;
}
.like-count {
font-size: 22rpx;
color: #fff;
}
/* 加载提示 */
.loading-more,
.no-more {
text-align: center;
padding: 32rpx 0;
}
.loading-text,
.no-more-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
</style>