topfans/frontend/pages/square/square.vue

389 lines
10 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="bg-wrapper"
src="/static/square/squearbj11.png"
mode="aspectFill"
></image> -->
<!-- Header组件 -->
<Header
:showGuideIcon="true"
:showTaskIcon="true"
:showStarActivityIcon="true"
backIconColor="#e6e6e6"
/>
<!-- 蒙层 - 导航栏展开时显示 -->
<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="4"
:isExpanded="navExpanded"
@update:activeTab="handleTabChange"
@update:isExpanded="navExpanded = $event"
/>
<!-- 全局引导遮罩 -->
<GuideOverlay />
<!-- 内容区域 -->
<scroll-view
class="content-wrapper"
:scroll-y="activeContentTab === 'guangchang'"
:scroll-top="outerScrollTop"
:show-scrollbar="false"
:bounce="false"
@scroll="onScroll"
@scrolltolower="handleScrollToLower"
>
<!-- 区域一:顶部运营轮播图 -->
<view class="banner-section">
<BannerCarousel
:bannerActivities="bannerActivities"
:banners="banners"
@activityClick="handleActivityClick"
@top3Click="showRankingModal = true"
@bannerClick="handleBannerClick"
/>
</view>
<ContentTabs
class="tabs"
:modelValue="activeContentTab"
@update:modelValue="activeContentTab = $event"
/>
<!-- 星河区块 - 仅在 星河 时显示 -->
<view v-if="activeContentTab === 'xinghe'" class="star-galaxy-wrapper">
<StarGalaxy @cardClick="handleCardClick" />
</view>
<!-- 在线榜单区块 - 仅在 星榜 时显示 -->
<view
v-if="activeContentTab === 'xingbang'"
class="hot-category-wrapper"
>
<HotCategoryBlock @cardClick="handleCardClick" />
</view>
<!-- CreationGrid 组件主Tab + 分类标签 + 网格列表 - 仅在 广场 时显示 -->
<CreationGrid
v-if="activeContentTab === 'guangchang'"
@cardClick="handleCardClick"
ref="creationGridRef"
/>
</scroll-view>
</view>
</template>
<script setup>
import { ref, watch, 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 ContentTabs from "./components/ContentTabs.vue";
import HotCategoryBlock from "./components/HotCategoryBlock.vue";
import CreationGrid from "./components/CreationGrid.vue";
import StarGalaxy from "./components/StarGalaxy/index.vue";
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
import { useBanner } from "./composables/useBanner.js";
import { doubleTapLike } from "@/utils/likeHelper.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("xinghe");
const navExpanded = ref(false);
const showRankingModal = ref(false);
const creationGridRef = ref(null);
const cardTapTimers = {};
const likingMap = ref({});
// 外层 scroll-view 的 scrollTop 受控值:通过 ref 绑定实现程序化重置
// uni.pageScrollTo 操作的是 page 滚动条,不会作用于 <scroll-view>
const outerScrollTop = ref(0);
// 切 tab 时重置外层 scroll-view 的 scrollTop,避免残留滚动位置
watch(activeContentTab, () => {
// 星河/星榜 时 scroll-y=false,但 scrollTop 残留可能导致显示异常
outerScrollTop.value = 0;
});
// 统一 scroll 处理:驱动 CreationGrid 切 fixed
const onScroll = (e) => {
const scrollTop = (e && e.detail && e.detail.scrollTop) || 0;
// 通知 CreationGrid根据 scrollTop 决定分类标签是否切到 fixed
creationGridRef.value?.updateScroll?.(scrollTop);
};
// ========== Composables ==========
const { bannerActivities, banners, loadBannerActivities, loadBanners } =
useBanner();
// ========== Handlers ==========
const handleCardClick = (card) => {
if (cardTapTimers[card.id]) {
// 第二次点击,双击点赞
clearTimeout(cardTapTimers[card.id]);
delete cardTapTimers[card.id];
// 触发动画
likingMap.value = { ...likingMap.value, [card.id]: true };
setTimeout(() => {
likingMap.value = { ...likingMap.value, [card.id]: false };
}, 600);
doubleTapLike(card.id, card.exhibition_id || 0, (success) => {
if (success) {
uni.showToast({ title: "点赞成功", icon: "success" });
}
});
} else {
// 第一次点击,单击跳转
if (card.id) {
cardTapTimers[card.id] = setTimeout(() => {
delete cardTapTimers[card.id];
uni.navigateTo({
url: `/pages/asset-detail/asset-detail?asset_id=${card.id}`,
});
}, 300);
}
}
};
const handleScrollToLower = () => {
if (creationGridRef.value) {
creationGridRef.value.loadMore();
}
};
const handleActivityClick = (item) => {
uni.navigateTo({
url: `/pages/support-activity/index?id=${item.id}`,
});
};
// 运营 banner 点击(铸造活动)
const handleBannerClick = (banner) => {
console.log("[square] banner click", banner);
// 优先使用自定义 route
if (banner.route) {
return uni.navigateTo({ url: banner.route });
}
if (banner.link_type === "activity") {
return uni.navigateTo({
url: `/pages/castlove/detail?id=${banner.link_value}`,
});
}
if (banner.link_type === "topic") {
return uni.navigateTo({
url: `/pages/topic/detail?id=${banner.link_value}`,
});
}
};
const handleRankingVisit = ({ userId, nickname }) => {
showRankingModal.value = false;
uni.navigateTo({
url: `/pages/profile/hisWorks?userId=${userId}&nickname=${encodeURIComponent(nickname)}`,
});
};
const handleRankingModalClose = (visible) => {
showRankingModal.value = visible;
if (!visible && store.state.guide.componentMode) {
uni.$emit("guide:closeComponent");
}
};
const handleTabChange = (newTab) => {
// if (newTab === 4) {
// navExpanded.value = false;
// return;
// }
const routes = [
"/pages/ai-dazi/index",
"/pages/starbook/index",
"/pages/castlove/mall",
"/pages/starcity/index",
"/pages/profile/myWorks",
];
if (newTab >= 0 && newTab < routes.length) {
uni.navigateTo({
url: routes[newTab],
});
}
};
// 主Tab点击 / 分类切换 已在 CreationGrid 内部处理,不再冒泡
// ========== Tile Change Callback ==========
const handleTileChange = () => {};
// ========== Reset Square ==========
const resetSquare = async () => {};
// ========== Lifecycle ==========
onMounted(() => {
resetSquare();
loadBannerActivities();
loadBanners();
});
onShow(() => {
activeContentTab.value = "xinghe";
});
// onLoad((options) => {
// 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] 调试模式已关闭')
// }
// }
// if (options && options.guide_key) {
// console.log('[Guide] 收到引导跳转参数, guide_key:', options.guide_key, 'guide_step:', options.guide_step)
// 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 lang="scss" scoped>
.square-container {
position: relative;
background: linear-gradient(
179.98deg,
rgba(255, 229, 229, 0.25) -32.49%,
rgba(243, 160, 161, 0.25) -32.49%,
rgba(255, 156, 156, 0.25) 86.46%,
rgba(255, 32, 36, 0.25) 180.79%
);
backdrop-filter: blur(4px);
width: 100vw;
height: 100vh;
min-height: 100vh;
overflow: hidden;
&::before {
content: "";
position: absolute;
inset: 0;
background: url("/static/dashboard/bj.png") center / cover no-repeat;
opacity: 0.2; // ⬅ 调这个数控制图片透明度0=完全透明1=完全不透明)
pointer-events: none;
z-index: 0;
}
}
/* 背景图片 */
.bg-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
/* 内容区域 */
.content-wrapper {
position: relative;
z-index: 1;
width: 100%;
height: 100vh;
/* margin-top: 160rpx; */
padding: 208rpx 0 0;
box-sizing: border-box;
}
/* 区域一:轮播图 */
.banner-section {
width: 100%;
height: 360rpx;
/* margin-bottom: 32rpx; */
}
/* 区域二主Tab+ 区域三(分类标签)+ 占位 + 回弹动效
已迁入 CreationGrid 组件内部(创建于 components/CreationGrid.vue。 */
/* 热门分类区块 */
.hot-category-wrapper {
position: relative;
/* 100vh - header(208rpx) - banner(360rpx) - tabs(88rpx) = 可用 body 高度 */
height: calc(100vh - 208rpx - 360rpx - 88rpx);
}
/* 蒙层 */
.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>