From fdc2f8b94b58d7ef13c497d6cbce44d07d3e6e08 Mon Sep 17 00:00:00 2001 From: cc Date: Thu, 25 Jun 2026 21:09:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E9=9A=8F=E6=9C=BA=E5=AE=9E?= =?UTF-8?q?=E5=BF=83=E9=BC=A0=E6=A0=87=20+=20=E6=8B=96=E5=8A=A8=E9=9A=90?= =?UTF-8?q?=E8=97=8F=20+=20=E4=BF=AE=E5=A4=8D=E5=88=B7=E6=96=B0=E9=97=AA?= =?UTF-8?q?=E5=8D=9A=E5=AE=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CursorFollower: 空心环→随机实心几何形状(7选1)+随机颜色,每次刷新换 - 照片墙拖动照片(移动后)隐藏自定义鼠标,松手复现 - App: 加 resolving 闸,异步路由解析完成前不渲染目标页, 修复已登录刷新特殊文件夹时先闪 BlogPage 再跳回照片墙 Co-Authored-By: Claude Opus 4.8 --- Client/src/App.tsx | 41 +++++++++++++-------- Client/src/components/CursorFollower.tsx | 47 +++++++++++++++++++----- Client/src/components/PhotoWallPage.tsx | 2 + Client/src/index.css | 5 +++ 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/Client/src/App.tsx b/Client/src/App.tsx index 0238f03..b3c4fab 100644 --- a/Client/src/App.tsx +++ b/Client/src/App.tsx @@ -20,6 +20,12 @@ function App() { ) // 特殊文件夹入口:如果 hash 匹配一个注册文件夹,存文件夹名 const [wantsFolder, setWantsFolder] = useState(null) + // 路由是否还在异步解析中。非 #photo 的 hash 要等 checkFolder/checkGate 返回才知道 + // 去向;解析完成前先不渲染任何目标页,避免已登录时一闪 BlogPage 兜底再跳回。 + const [resolving, setResolving] = useState(() => { + const h = window.location.hash.replace(/^#/, '') + return !!h && h !== PHOTO_HASH + }) const signOut = auth.signOut // 监听地址栏 hash: @@ -34,22 +40,24 @@ function App() { setWantsPhoto(photo) setWantsFolder(null) setShowLogin(false) + // #photo 与无 hash 都能同步定向,不需要异步解析 + setResolving(!!hash && !photo) } - if (photo) return + if (photo || !hash) return - if (hash) { - // 先查是否是已注册的特殊文件夹 - const isFolder = await checkFolder(hash) - if (!alive || hash !== window.location.hash.replace(/^#/, '')) return - if (isFolder) { - setWantsFolder(hash) - return - } - // 不是文件夹,查 gate - const ok = await checkGate(hash) - if (alive && hash === window.location.hash.replace(/^#/, '')) { - setShowLogin(ok) - } + // 先查是否是已注册的特殊文件夹 + const isFolder = await checkFolder(hash) + if (!alive || hash !== window.location.hash.replace(/^#/, '')) return + if (isFolder) { + setWantsFolder(hash) + setResolving(false) + return + } + // 不是文件夹,查 gate + const ok = await checkGate(hash) + if (alive && hash === window.location.hash.replace(/^#/, '')) { + setShowLogin(ok) + setResolving(false) } } check() @@ -79,7 +87,10 @@ function App() { } let view - if (auth.username) { + if (resolving) { + // 路由解析中:先留空,等 checkFolder/checkGate 定向后再渲染目标页 + view = null + } else if (auth.username) { if (wantsPhoto) { view = } else if (wantsFolder) { diff --git a/Client/src/components/CursorFollower.tsx b/Client/src/components/CursorFollower.tsx index 113c06e..c356fa5 100644 --- a/Client/src/components/CursorFollower.tsx +++ b/Client/src/components/CursorFollower.tsx @@ -1,13 +1,39 @@ import { useEffect, useState } from 'react' import { motion, useMotionValue } from 'framer-motion' -// 跟随鼠标的小圆环(仿 Few & Far)。 +// 跟随鼠标的实心几何图案(仿 Few & Far)。 +// 每次刷新随机抽一个形状 + 随机颜色;悬停可交互元素时放大。 // 仅在精确指针设备启用;触摸设备与 reduced-motion 下不挂载。 + +// 形状库:viewBox 0 0 24 24,统一实心填充。 +const SHAPES: Array<(c: string) => React.ReactNode> = [ + // 圆 + (c) => , + // 圆角方块 + (c) => , + // 菱形 + (c) => , + // 三角 + (c) => , + // 五角星 + (c) => , + // 心形 + (c) => , + // 六边形 + (c) => , +] + export function CursorFollower() { // 位置 1:1 跟手,不做弹簧平滑(平滑在掉帧时会显得发涩/拖影)。 const x = useMotionValue(-100) const y = useMotionValue(-100) + // 每次挂载(= 每次刷新)固定一个随机形状 + 随机颜色。 + const [{ shape, color }] = useState(() => ({ + shape: SHAPES[Math.floor(Math.random() * SHAPES.length)], + color: `hsl(${Math.floor(Math.random() * 360)}, 70%, 55%)`, + })) + const [enabled, setEnabled] = useState(false) const [hovering, setHovering] = useState(false) const [visible, setVisible] = useState(false) @@ -45,20 +71,23 @@ export function CursorFollower() { return ( - + > + + {shape(color)} + + ) } diff --git a/Client/src/components/PhotoWallPage.tsx b/Client/src/components/PhotoWallPage.tsx index fd905b5..ecc78de 100644 --- a/Client/src/components/PhotoWallPage.tsx +++ b/Client/src/components/PhotoWallPage.tsx @@ -644,6 +644,7 @@ export function PhotoWallPage({ onExit, token, folderName, isAdmin, onForbidden const dx = ev.clientX - startX const dy = ev.clientY - startY if (Math.abs(dx) > 4 || Math.abs(dy) > 4) { + if (!moved) document.body.classList.add('pw-hide-cursor') moved = true clearTimeout(longPress) } @@ -654,6 +655,7 @@ export function PhotoWallPage({ onExit, token, folderName, isAdmin, onForbidden const cleanup = () => { clearTimeout(longPress) node.classList.remove('pw-dragging') + document.body.classList.remove('pw-hide-cursor') window.removeEventListener('pointermove', move) window.removeEventListener('pointerup', up) } diff --git a/Client/src/index.css b/Client/src/index.css index f50bc7a..6796254 100644 --- a/Client/src/index.css +++ b/Client/src/index.css @@ -46,6 +46,11 @@ body { } } +/* 照片墙拖动照片时隐藏自定义鼠标(拖拽中由 PhotoWallPage 切换此类) */ +body.pw-hide-cursor .cursor-follower { + opacity: 0 !important; +} + /* 全站文字默认不可选;输入框/文本域/可编辑区例外,加 data-selectable 可单独放开 */ body { -webkit-user-select: none;