diff --git a/frontend/pages/square/components/BannerCarousel.vue b/frontend/pages/square/components/BannerCarousel.vue index 0186824..c58eee5 100644 --- a/frontend/pages/square/components/BannerCarousel.vue +++ b/frontend/pages/square/components/BannerCarousel.vue @@ -48,9 +48,9 @@ const onTop3DataLoaded = (items) => { diff --git a/frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue b/frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue index fb5e371..086f1e2 100644 --- a/frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue +++ b/frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue @@ -27,7 +27,7 @@ :style="ringItemStyle(p)" @click="handleClick(items[i])" > - + {{ formatLabel(p.rank) }} + + @@ -91,6 +98,14 @@ function ringFrameSrc(rank) { return `/static/square/galaxy/LV${rank}.png`; } +function baseSrc(rank) { + // 不同名次区间使用不同的底座:4-6 → dizuo1, 7-9 → dizuo2, 10-12 → dizuo3 + if (rank >= 4 && rank <= 6) return "/static/square/galaxy/dizuo1.png"; + if (rank >= 7 && rank <= 9) return "/static/square/galaxy/dizuo2.png"; + if (rank >= 10 && rank <= 12) return "/static/square/galaxy/dizuo3.png"; + return ""; +} + function handleClick(item) { if (item) emit("cardClick", item); } @@ -125,6 +140,7 @@ function handleClick(item) { cursor: pointer; /* display: flex; */ /* flex-direction: column; */ + will-change: transform; animation: orbit 36s linear infinite; } @@ -169,6 +185,16 @@ function handleClick(item) { position: absolute; top: 0; } + +.base-image { + position: absolute; + bottom: -24rpx; + left: -8rpx; + width: 96rpx; /* 比 cover (84rpx) 略宽,呈现"承托"感 */ + height: 32rpx; + z-index: 0; /* 位于 cover 之下 */ + pointer-events: none; +} diff --git a/frontend/pages/square/composables/useSpotlight.js b/frontend/pages/square/composables/useSpotlight.js deleted file mode 100644 index c35e14a..0000000 --- a/frontend/pages/square/composables/useSpotlight.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * useSpotlight - 滚动 spotlight / 边缘渐隐 - * - * 行为: - * - opacity 等于「元素在视口中可见的比例」 - * - 元素完全在视口内 → 1 - * - 元素被滚出一半 → 0.5 - * - 元素完全在视口外 → 0 - * - 元素比视口高、且完全覆盖视口 → 1 - * - * 关键修复(针对 app-plus): - * - app-plus 的 是原生 UIScrollView,WebView 内容本身不滚动。 - * getBoundingClientRect() 永远返回初始 layout 位置(不随原生滚动更新), - * 所以基于 rAF/setTimeout + getBoundingClientRect 的方案在 app-plus 上完全无效。 - * - 用 uni.createSelectorQuery() 取位置 —— uni-app 官方 API,会桥到原生层, - * 在 app-plus / H5 / 小程序上都能拿到当前可视位置。 - * - 异步结果用 reqId 防止乱序(连续触发时丢弃旧结果)。 - * - * 用法(square.vue): - * const { start, stop } = useSpotlight({ getElements }) - * onMounted(start); onUnmounted(stop) - * // @scroll 事件也调一次 update(),作为双保险 - */ -import { getCurrentInstance } from 'vue' - -const SEEN_THRESHOLD = 0.5 // 透明度高于此值记为"首次出现" - -export function useSpotlight(options = {}) { - const { getElements = () => [] } = options - - // app-plus / Vue3 组件内 createSelectorQuery 必须挂 in(public proxy),否则 selectAll 在 app 端返回空、opacity 从不生效 - const pageProxy = getCurrentInstance()?.proxy || null - - const seenSet = typeof WeakSet !== "undefined" ? new WeakSet() : new Set() - - const computeOpacity = (rect, vh) => { - if (!rect) return 1 - if (rect.bottom <= 0 || rect.top >= vh) return 0 - - if (rect.height >= vh && rect.top <= 0 && rect.bottom >= vh) return 1 - - const visibleTop = Math.max(0, rect.top) - const visibleBottom = Math.min(vh, rect.bottom) - const visibleHeight = Math.max(0, visibleBottom - visibleTop) - const totalHeight = rect.height - - if (totalHeight <= 0) return 1 - return Math.min(1, visibleHeight / totalHeight) - } - - // 给每个 ref 对应的 DOM 节点加 data-spotlight-id,方便 SelectorQuery 结果映射回节点 - const idToNode = new Map() - let nextId = 1 - const tagNodes = () => { - const elements = getElements() - elements.forEach((ref) => { - if (!ref) return - const node = ref.$el || ref - if (!node || node.nodeType !== 1) return - if (node.dataset && node.dataset.spotlightId) return - const id = `sl${nextId++}` - node.setAttribute("data-spotlight-id", id) - idToNode.set(id, node) - }) - } - - // SelectorQuery 异步:连续触发时用 reqId 丢弃旧结果 - let reqId = 0 - - const applyRects = (rects, vh) => { - if (!rects || !rects.length) return - rects.forEach((rect) => { - const id = rect && rect.dataset && rect.dataset.spotlightId - if (!id) return - const node = idToNode.get(id) || (typeof document !== "undefined" && document.querySelector(`[data-spotlight-id="${id}"]`)) - if (!node) return - - const opacity = computeOpacity(rect, vh) - try { - node.style.opacity = opacity.toFixed(3) - } catch (e) {} - - if (opacity > SEEN_THRESHOLD && !seenSet.has(node)) { - seenSet.add(node) - try { node.classList.add("first-seen") } catch (e) {} - } - }) - } - - // 测高度 - const getVH = () => { - try { - if (typeof window !== "undefined" && window.innerHeight) return window.innerHeight - } catch (e) {} - try { - const info = uni.getSystemInfoSync() - return info.windowHeight || info.screenHeight || 667 - } catch (e) { - return 667 - } - } - - const update = () => { - if (typeof uni === "undefined" || typeof uni.createSelectorQuery !== "function") return - - tagNodes() - if (!idToNode.size) return - - const myReq = ++reqId - const vh = getVH() - - try { - const query = pageProxy ? uni.createSelectorQuery().in(pageProxy) : uni.createSelectorQuery() - query.selectAll("[data-spotlight-id]").boundingClientRect() - query.exec((res) => { - if (myReq !== reqId) return // 旧请求,丢弃 - if (!res || !res[0]) return - applyRects(res[0], vh) - }) - } catch (e) { - // SelectorQuery 不可用时不做事 - } - } - - // setTimeout 递归轮询:兜底,@scroll 漏触发时仍能跑 - let timerId = null - const tick = () => { - update() - timerId = setTimeout(tick, 50) // 50ms ≈ 20fps,SelectorQuery 开销小但不为零,频率不用太高 - } - - const start = () => { - if (timerId) return - timerId = setTimeout(tick, 50) - } - - const stop = () => { - if (timerId) { - clearTimeout(timerId) - timerId = null - } - } - - // 兼容:@scroll 触发时直接调一次(双保险,scroll 事件比 50ms 轮询更密) - const bindScroll = () => { - update() - } - - return { - update, - bindScroll, - start, - stop, - isH5: typeof window !== "undefined" && typeof document !== "undefined", - } -} diff --git a/frontend/pages/square/square.vue b/frontend/pages/square/square.vue index e72b642..c33beef 100644 --- a/frontend/pages/square/square.vue +++ b/frontend/pages/square/square.vue @@ -45,17 +45,14 @@ - @@ -98,7 +92,7 @@ @@ -412,9 +343,7 @@ onUnmounted(() => { } /* 区域二(主Tab)+ 区域三(分类标签)+ 占位 + 回弹动效 - 已迁入 CreationGrid 组件内部(创建于 components/CreationGrid.vue) - 这里的 .main-tab-section / .category-section / .tab-item / 等选择器被保留 - 是因为 .spotlight-ready 选择器需要它们,CSS specificity 才能匹配(容器外层应用 spotlight-ready)。 */ + 已迁入 CreationGrid 组件内部(创建于 components/CreationGrid.vue)。 */ /* 热门分类区块 */ .hot-category-wrapper { @@ -445,84 +374,4 @@ onUnmounted(() => { opacity: 1; } } - -/* ========== 滚动 spotlight(仅 H5) ========== - * JS 把 --scroll-opacity 写到每个 .spotlight 元素上; - * 边缘渐隐由 CSS transition 平滑。 - * 非 H5 不写变量,默认 1,元素完全可见。 */ - - - diff --git a/frontend/static/square/galaxy/LV3.png b/frontend/static/square/galaxy/LV3.png index 701a276..0723bdc 100644 Binary files a/frontend/static/square/galaxy/LV3.png and b/frontend/static/square/galaxy/LV3.png differ diff --git a/frontend/static/square/galaxy/dizuo3.png b/frontend/static/square/galaxy/dizuo3.png index 6818920..cc7d561 100644 Binary files a/frontend/static/square/galaxy/dizuo3.png and b/frontend/static/square/galaxy/dizuo3.png differ diff --git a/frontend/static/square/top/bj.png b/frontend/static/square/top/d78825f2ff1822841b630e360c1da04c0d7eca00.png similarity index 100% rename from frontend/static/square/top/bj.png rename to frontend/static/square/top/d78825f2ff1822841b630e360c1da04c0d7eca00.png