PersonalWebApplication/Client/src/App.tsx
cc fdc2f8b94b feat(ui): 随机实心鼠标 + 拖动隐藏 + 修复刷新闪博客
- CursorFollower: 空心环→随机实心几何形状(7选1)+随机颜色,每次刷新换
- 照片墙拖动照片(移动后)隐藏自定义鼠标,松手复现
- App: 加 resolving 闸,异步路由解析完成前不渲染目标页,
  修复已登录刷新特殊文件夹时先闪 BlogPage 再跳回照片墙

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:09:18 +08:00

127 lines
4.2 KiB
TypeScript
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.

import { useEffect, useState } from 'react'
import { CursorFollower } from './components/CursorFollower'
import { LoginPage } from './components/LoginPage'
import { BlogPage } from './components/BlogPage'
import { StartPage } from './components/StartPage'
import { PhotoWallPage } from './components/PhotoWallPage'
import { useAuth } from './hooks/useAuth'
import { checkGate } from './api/gate'
import { checkFolder } from './api/folders'
// 固定入口 hash#photo 进个人照片墙(公开路由,不走后端 gate登录成功即进
const PHOTO_HASH = 'photo'
function App() {
const auth = useAuth()
const [showLogin, setShowLogin] = useState(false)
// 当前是否走个人照片墙入口(地址栏 #photo
const [wantsPhoto, setWantsPhoto] = useState(
() => window.location.hash.replace(/^#/, '') === PHOTO_HASH,
)
// 特殊文件夹入口:如果 hash 匹配一个注册文件夹,存文件夹名
const [wantsFolder, setWantsFolder] = useState<string | null>(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
// - #photo → 个人照片墙(公开入口,直接放行到登录页)
// - 其余 hash → 先查是否是特殊文件夹 → 否则查 gate → 否则起始页
useEffect(() => {
let alive = true
const check = async () => {
const hash = window.location.hash.replace(/^#/, '')
const photo = hash === PHOTO_HASH
if (alive) {
setWantsPhoto(photo)
setWantsFolder(null)
setShowLogin(false)
// #photo 与无 hash 都能同步定向,不需要异步解析
setResolving(!!hash && !photo)
}
if (photo || !hash) return
// 先查是否是已注册的特殊文件夹
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()
window.addEventListener('hashchange', check)
return () => {
alive = false
window.removeEventListener('hashchange', check)
}
}, [])
// 回到导航页:清掉登录态,并抹掉地址栏 hash
const goStart = () => {
signOut()
setShowLogin(false)
setWantsFolder(null)
if (window.location.hash) {
history.replaceState(null, '', window.location.pathname + window.location.search)
}
}
// 403 无权访问特殊文件夹时,也退回起始页
const onFolderForbidden = () => {
setWantsFolder(null)
if (window.location.hash) {
history.replaceState(null, '', window.location.pathname + window.location.search)
}
}
let view
if (resolving) {
// 路由解析中:先留空,等 checkFolder/checkGate 定向后再渲染目标页
view = null
} else if (auth.username) {
if (wantsPhoto) {
view = <PhotoWallPage onExit={goStart} token={auth.token ?? ''} />
} else if (wantsFolder) {
view = (
<PhotoWallPage
onExit={goStart}
token={auth.token ?? ''}
folderName={wantsFolder}
isAdmin={auth.username === 'cc'}
onForbidden={onFolderForbidden}
/>
)
} else {
view = <BlogPage username={auth.username} onSignOut={goStart} />
}
} else if (wantsPhoto || wantsFolder) {
// 照片墙 / 特殊文件夹未登录:复用登录页,去掉顶部服务器负载栏
view = <LoginPage onSignIn={auth.signIn} onBack={goStart} hideStats />
} else if (showLogin) {
view = <LoginPage onSignIn={auth.signIn} onBack={goStart} />
} else {
view = <StartPage />
}
return (
<>
<CursorFollower />
{view}
</>
)
}
export default App