diff --git a/frontend/pages/square/components/CreationGrid.vue b/frontend/pages/square/components/CreationGrid.vue index b8b428b..c54c072 100644 --- a/frontend/pages/square/components/CreationGrid.vue +++ b/frontend/pages/square/components/CreationGrid.vue @@ -1,48 +1,115 @@ \ No newline at end of file + diff --git a/frontend/pages/square/components/HotCategoryBlock.vue b/frontend/pages/square/components/HotCategoryBlock.vue index 6dfa9b1..9a49a3f 100644 --- a/frontend/pages/square/components/HotCategoryBlock.vue +++ b/frontend/pages/square/components/HotCategoryBlock.vue @@ -101,6 +101,18 @@ import { ref, watch, onMounted, onUnmounted } from "vue"; import { onShow } from "@dcloudio/uni-app"; import { getHotRankingApi } from "@/utils/api.js"; +import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js"; + +// 把后端返回的 cover_url / cover_image 转成真实可访问的 URL +// 处理 3 种形态:/static/... (本地)、相对路径 (需 presign)、完整 URL (可能过期) +async function resolveItemUrls(item) { + if (!item) return item + const cover = item.cover_url || item.cover_image || "" + if (cover) { + item.cover_url = await getAssetCoverRealUrl(cover) + } + return item +} const props = defineProps({ title: { @@ -179,10 +191,15 @@ const loadData = async () => { try { const res = await getHotRankingApi(props.dimension, null, 1, 11); if (res.code === 200 && res.data?.items) { - items.value = res.data.items.map((item) => ({ - ...item, - id: item.id || item.asset_id, - })); + // 逐个把 cover_url 转换成真实可访问 URL(OSS 预签名前端实现) + items.value = await Promise.all( + res.data.items.map(async (item) => { + return await resolveItemUrls({ + ...item, + id: item.id || item.asset_id, + }) + }) + ); } } catch (e) { console.error("[HotCategoryBlock] 加载数据失败", e?.message ?? e); diff --git a/frontend/pages/square/square.vue b/frontend/pages/square/square.vue index 48b6ece..a027349 100644 --- a/frontend/pages/square/square.vue +++ b/frontend/pages/square/square.vue @@ -46,40 +46,11 @@ - - - - - - - - {{ tab.name }} - - - - - - - - {{ category.label }} - - - - - - - + @@ -113,8 +84,6 @@ const activeCategoryTab = ref("hot"); const navExpanded = ref(false); const showRankingModal = ref(false); const isActive = ref(true); -const isFixed = ref(false); -const justFixed = ref(false); const creationGridRef = ref(null); const cardTapTimers = {}; const likingMap = ref({}); @@ -123,18 +92,7 @@ const likingMap = ref({}); const bannerSectionRef = ref(null); const contentTabsRef = ref(null); const hotCategoryRef = ref(null); -const mainTabsRef = ref(null); -const categoryRef = ref(null); - -// ========== 分类标签 fixed 触发的"占位 + 阈值" ========== -// 记录 section 在滚动内容中的初始位置(offsetTop)和自然高度 -// 滚动时用 scrollTop + 阈值 对比,决定是否切到 fixed -const categoryOffsetTop = ref(null); -const categoryHeight = ref(0); -const fixedTopPx = ref(50); // 近似对应 CSS 的 top: 96rpx - -// ========== 滚动 spotlight ========== -// 把所有"会随滚动淡出/淡入"的元素喂给 composable: +// mainTabsRef / categoryRef 已迁入 CreationGrid 内部(通过 creationGridRef.value.mainTabsRef / categoryRef 拿) // - 4 个区块 // - CreationGrid 里的所有卡片(通过 defineExpose 暴露的 getCardRefs 拿到) const allSpotlightRefs = () => { @@ -142,8 +100,8 @@ const allSpotlightRefs = () => { bannerSectionRef.value, contentTabsRef.value?.$el || contentTabsRef.value, hotCategoryRef.value, - mainTabsRef.value, - categoryRef.value, + creationGridRef.value?.mainTabsRef?.value, + creationGridRef.value?.categoryRef?.value, ].filter(Boolean) const cards = creationGridRef.value?.getCardRefs?.() || [] @@ -154,13 +112,12 @@ const { update, bindScroll, start: startSpotlight, stop: stopSpotlight, isH5 } = getElements: allSpotlightRefs, }) -// 统一 scroll 处理:spotlight + isFixed +// 统一 scroll 处理:spotlight + 驱动 CreationGrid 切 fixed const onScroll = (e) => { bindScroll() // 兼容路径:scroll 事件触发时也跑一次(主驱动是 rAF 轮询) const scrollTop = (e && e.detail && e.detail.scrollTop) || 0 - if (categoryOffsetTop.value !== null) { - isFixed.value = scrollTop + fixedTopPx.value >= categoryOffsetTop.value - } + // 通知 CreationGrid:根据 scrollTop 决定分类标签是否切到 fixed + creationGridRef.value?.updateScroll?.(scrollTop) } // 移动端 rAF 在用户滚动时会被节流,opacity 跟不上。 @@ -172,15 +129,6 @@ const onTouchMove = () => { update() } -// 分类标签 fixed 触发的回弹:状态变化瞬间加 class,动画播完摘掉 -watch(isFixed, (val) => { - if (!val) return; - justFixed.value = true; - setTimeout(() => { - justFixed.value = false; - }, 500); -}); - // tab / 分类变化 → 新内容可能进入视口,重做一次 spotlight 计算 watch(activeContentTab, () => { nextTick(() => setTimeout(update, 50)); @@ -196,51 +144,6 @@ const onGridLoaded = (count) => { } }; -// 主Tab配置 -const mainTabs = ref([ - { - name: "星卡", - type: "star_card", - icon: "/static/square/xingka.png", - width: 96, - height: 96, - rotate: -15, - }, - { - name: "吧唧", - type: "badge", - icon: "/static/square/baji.png", - width: 100, - height: 100, - rotate: 0, - }, - { - name: "海报", - type: "poster", - icon: "/static/square/haibao.png", - width: 100, - height: 108, - rotate: -15, - }, -]); - -// ========== 分类配置 ========== -const categories = ref([ - { label: "热门作品", value: "hot" }, - { label: "最新作品", value: "new" }, - { label: "星卡", value: "star_card" }, - { label: "吧唧", value: "badge" }, - { label: "海报", value: "poster" }, -]); - -// ========== Watch activeContentTab ========== -// watch(activeContentTab, (newTab) => { -// if (newTab === 'myworks') { -// uni.navigateTo({ url: '/pages/profile/myWorks' }) -// return -// } -// }) - // ========== Screen Info ========== const screenWidth = ref(375); const screenHeight = ref(812); @@ -392,14 +295,9 @@ onMounted(() => { loadBannerActivities(); loadBanners(); - // 等首屏 DOM 稳定后,测量分类标签 section 的位置/高度 + 启动 spotlight 轮询 + // 等首屏 DOM 稳定后,启动 spotlight 轮询 + // 分类标签的位置/高度测量已迁入 CreationGrid 内部(onMounted 自动测) nextTick(() => { - if (categoryRef.value) { - categoryOffsetTop.value = categoryRef.value.offsetTop; - categoryHeight.value = categoryRef.value.offsetHeight; - } - // rpx → px:96rpx 在不同屏幕宽度下换算 - fixedTopPx.value = (96 * screenWidth.value) / 750; startSpotlight(); }); }); @@ -505,103 +403,10 @@ onUnmounted(() => { /* margin-bottom: 32rpx; */ } -/* 区域二:主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: space-around; - 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-wrap { - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 12rpx; -} - -.tab-icon { - margin-bottom: 0; - object-fit: cover; - box-shadow: 8rpx 8rpx 16rpx rgba(229, 76, 93, 0.9); -} - -.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: 96rpx; - left: 24rpx; - right: 24rpx; - z-index: 100; - padding: 16rpx 0; -} - -.category-placeholder { - /* 仅占位,无内容;高度由内联 style 写入 */ - margin-bottom: 24rpx; -} - -.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; -} +/* 区域二(主Tab)+ 区域三(分类标签)+ 占位 + 回弹动效 + 已迁入 CreationGrid 组件内部(创建于 components/CreationGrid.vue) + 这里的 .main-tab-section / .category-section / .tab-item / 等选择器被保留 + 是因为 .spotlight-ready 选择器需要它们,CSS specificity 才能匹配(容器外层应用 spotlight-ready)。 */ /* 热门分类区块 */ .hot-category-wrapper {