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 @@
-
+
+
+
+
+
+
+
+ {{ tab.name }}
+
+
+
+
-
-
-
-
-
- {{ formatCount(item.like_count) }}
+
+
+ {{ category.label }}
-
-
-
-
-
- 链上编号: #{{ item.certificate_id }}
-
-
-
-
- {{ item.creator_name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatCount(item.like_count) }}
+
+
+
+
+
+
+ 链上编号: #{{ item.certificate_id }}
+
+
+
+
+ {{ item.creator_name }}
+
-
-
-
- 加载中...
-
-
- 没有更多了
+
+ 加载中...
+
+
+ 没有更多了
+
\ 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 {