topfans/frontend/pages/square/square.vue
2026-05-06 10:51:08 +08:00

282 lines
7.1 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="square-container">
<!-- 固定背景 -->
<image class="background-fixed" src="/static/square/squearbj.png" mode="aspectFill" />
<!-- 横向瀑布流卡片层内部自带横向滚动 -->
<WaterfallGrid
:screenWidth="screenWidth"
:screenHeight="screenHeight"
:bannerBottom="bannerBottomPx"
:useMockData="USE_MOCK_DATA"
:category="activeContentTab"
@cardClick="handleCardClick"
class="fall-bg"
/>
<!-- Header组件 -->
<Header
:showGuideIcon="true"
:showTaskIcon="true"
:showStarActivityIcon="true"
backIconColor="#e6e6e6"
/>
<!-- 轮播图 + 内容分类 Tab -->
<view class="banner-tabs-wrapper">
<BannerCarousel
:bannerActivities="bannerActivities"
@activityClick="handleActivityClick"
@top3Click="showRankingModal = true"
/>
<ContentTabs class="tabs" v-model="activeContentTab" />
</view>
<!-- 蒙层 - 导航栏展开时显示 -->
<view v-if="navExpanded" class="nav-mask" @click="navExpanded = false"></view>
<!-- 排行榜弹窗 -->
<RankingModal
:visible="showRankingModal"
:parent-active="true"
:star-id="currentStarId"
@update:visible="handleRankingModalClose"
@visit="handleRankingVisit"
/>
<!-- 底部导航栏 -->
<BottomNav
:activeTab="0"
:isExpanded="navExpanded"
@update:activeTab="handleTabChange"
@update:isExpanded="navExpanded = $event"
/>
<!-- 全局引导遮罩 -->
<GuideOverlay />
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { useStore } from 'vuex'
import Header from '../components/Header.vue'
import BottomNav from '../components/BottomNav.vue'
import GuideOverlay from '@/components/GuideOverlay.vue'
import RankingModal from '../components/RankingModal.vue'
import BannerCarousel from './components/BannerCarousel.vue'
import WaterfallGrid from './components/WaterfallGrid.vue'
import ContentTabs from './components/ContentTabs.vue'
import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
import { useBanner } from './composables/useBanner.js'
import { USE_MOCK_DATA } from './config/mockData.js'
// ========== Store & User Info ==========
const store = useStore()
const currentUserNickname = computed(() => store.state.user?.userInfo?.nickname || '')
const currentStarId = ref(uni.getStorageSync('star_id') || null)
// ========== UI State ==========
const activeContentTab = ref('hot')
const navExpanded = ref(false)
const showRankingModal = ref(false)
// ========== Screen Info ==========
const screenWidth = ref(375)
const screenHeight = ref(812)
// ========== Composables ==========
const {
bannerActivities,
loadBannerActivities,
} = useBanner()
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
const bannerBottomPx = computed(() => Math.round(screenWidth.value / 750 * 716))
// ========== Handlers ==========
const handleCardClick = (card) => {
// WaterfallGrid 组件内部已处理单击跳转和双击点赞
}
const handleActivityClick = (item) => {
uni.navigateTo({
url: `/pages/support-activity/index?id=${item.id}`,
})
}
const handleRankingVisit = (userId) => {
showRankingModal.value = false
uni.navigateTo({
url: `/pages/exhibition/exhibition?target_uid=${userId}`,
})
}
const handleRankingModalClose = (visible) => {
showRankingModal.value = visible
// 使用 componentMode 判断,因为 isActive 可能在 END_GUIDE 后已变为 false
if (!visible && store.state.guide.componentMode) {
uni.$emit('guide:closeComponent')
}
}
const handleTabChange = (newTab) => {
if (newTab === 0) {
navExpanded.value = false
uni.navigateTo({
url: '/pages/square/square'
})
return
}
const routes = [
'/pages/square/square',
'/pages/starbook/index',
'/pages/castlove/mall',
'/pages/starcity/index',
'/pages/friends/index'
]
if (newTab >= 0 && newTab < routes.length) {
uni.navigateTo({
url: routes[newTab]
})
}
}
// ========== Tile Change Callback ==========
const handleTileChange = () => {}
// ========== Reset Square ==========
const resetSquare = async () => {}
// ========== Watch currentUserNickname ==========
// (no-op, kept for future use)
// ========== Lifecycle ==========
onMounted(() => {
const info = uni.getSystemInfoSync()
screenWidth.value = info.windowWidth
screenHeight.value = info.windowHeight
// 数据加载在后台进行,不阻塞渲染
resetSquare()
loadBannerActivities()
})
onShow(() => {
// 检查是否需要显示引导,如果需要则跳转到引导页面
if (shouldShowGuideStartModal()) {
uni.navigateTo({
url: '/pages/tasks/guide'
})
}
})
onLoad((options) => {
// 调试模式:读取 guide_debug 参数并设置存储
if (options && 'guide_debug' in options) {
const debugValue = options.guide_debug
const isDebug = debugValue === '1' || debugValue === 'true'
if (isDebug) {
uni.setStorageSync('guide_debug_mode', true)
uni.setStorageSync('is_new_user', true)
console.log('[Guide] 调试模式已开启')
} else {
uni.setStorageSync('guide_debug_mode', false)
uni.removeStorageSync('is_new_user')
console.log('[Guide] 调试模式已关闭')
}
}
// 处理引导跳转参数:如果传递了 guide_key则继续该引导
if (options && options.guide_key) {
console.log('[Guide] 收到引导跳转参数, guide_key:', options.guide_key, 'guide_step:', options.guide_step)
// 使用 resumeGuide 继续引导(不会检查 shouldShowGuide
store.dispatch('guide/resumeGuide', options.guide_key).then(res => {
console.log('[Guide] resumeGuide 结果:', res)
}).catch(err => {
console.error('[Guide] resumeGuide 失败:', err)
})
}
})
// 监听引导打开组件事件
uni.$on('guide:openComponent', (componentName) => {
if (componentName === 'RankingModal') {
showRankingModal.value = true
}
})
onUnmounted(() => {
uni.$off('guide:openComponent')
})
</script>
<style scoped>
.square-container {
position: relative;
width: 100vw;
height: 100vh;
min-height: 100vh;
overflow: hidden;
}
/* .fall-bg{
background: rgba(255, 180, 180, 0.25);
border-radius: 48rpx;
} */
.banner-tabs-wrapper {
position: fixed;
top: 216rpx;
left: 0;
right: 0;
z-index: 100;
display: flex;
flex-direction: column;
min-height: 448rpx;
justify-content: space-between;
background: rgb(249 159 192 / 45%);;
/* background: rgba(212, 127, 127, 0.8); */
border-radius: 48rpx;
overflow: visible;
}
/* .tabs{
margin-bottom: 8rpx;
} */
.background-fixed {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.nav-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
z-index: 999;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>