feat(admin): 登录改页内弹窗 + P1 站点设置(页头/侧栏)
- 登录:LoginDialog(Modal)替代 /login 页面;/login 重定向回首页 - 设置接口:GET /api/settings 公开读、PATCH 需登录 + version 乐观锁(409/400) - setting 表存 site JSON:页头(启用/速度/条目+标签+色调)、侧栏(hidden/order) - /admin:Tabs 页头/侧栏,条目增删与上下移,保存后首页生效 - 首页应用设置:ticker 文本占位符与速度、侧栏顺序与显隐 - 测试 42 passed(settings 5 例)
This commit is contained in:
+42
-6
@@ -29,6 +29,13 @@ import { useProjects } from './useProjects'
|
||||
import { useCatalog } from './useCatalog'
|
||||
import { useViewRoute } from './useViewRoute'
|
||||
import { fetchSession, logout as logoutRequest, type SessionState } from './auth'
|
||||
import {
|
||||
applySidebarSettings,
|
||||
defaultSettings,
|
||||
fetchSettings,
|
||||
type SettingsDocument,
|
||||
} from './settings'
|
||||
import { LoginDialog } from './components/auth/LoginDialog'
|
||||
import { parseViewPath, viewPath, type View } from './data/view'
|
||||
import './App.css'
|
||||
|
||||
@@ -40,6 +47,11 @@ export default function App() {
|
||||
// 当前视图由地址栏决定:刷新 / 收藏 / 前进后退都回到同一页(见 useViewRoute.ts)
|
||||
const { view, go } = useViewRoute()
|
||||
const [session, setSession] = useState<SessionState | null>(null)
|
||||
const [settings, setSettings] = useState<SettingsDocument>({
|
||||
version: 0,
|
||||
settings: defaultSettings(),
|
||||
})
|
||||
const [loginOpen, setLoginOpen] = useState(false)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const searchRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
@@ -49,6 +61,9 @@ export default function App() {
|
||||
fetchSession().then((next) => {
|
||||
if (alive) setSession(next)
|
||||
})
|
||||
fetchSettings().then((next) => {
|
||||
if (alive) setSettings(next)
|
||||
})
|
||||
return () => {
|
||||
alive = false
|
||||
}
|
||||
@@ -63,13 +78,17 @@ export default function App() {
|
||||
[categories, keyword],
|
||||
)
|
||||
|
||||
// 侧栏不显示「本地服务」,也不显示被标记 show_in_sidebar = 0 的分区
|
||||
// 侧栏不显示「本地服务」,也不显示被标记 show_in_sidebar = 0 的分区;
|
||||
// 再按 /admin 里的顺序与显隐设置重排一遍
|
||||
const sidebarCategories = useMemo(
|
||||
() =>
|
||||
categories.filter(
|
||||
(category) => category.id !== 'local' && category.showInSidebar !== false,
|
||||
applySidebarSettings(
|
||||
categories.filter(
|
||||
(category) => category.id !== 'local' && category.showInSidebar !== false,
|
||||
),
|
||||
settings.settings.sidebar,
|
||||
),
|
||||
[categories],
|
||||
[categories, settings.settings.sidebar],
|
||||
)
|
||||
|
||||
const detailView = view.type === 'detail' ? view : undefined
|
||||
@@ -258,11 +277,24 @@ export default function App() {
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
void logoutRequest().then(() => window.location.assign('/'))
|
||||
void logoutRequest().then(() => {
|
||||
setSession({ authenticated: false, has_admin: true })
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 登录弹窗:成功后直接把会话状态写回,不刷新页面 */}
|
||||
<LoginDialog
|
||||
open={loginOpen}
|
||||
hasAdmin={session?.has_admin ?? false}
|
||||
onClose={() => setLoginOpen(false)}
|
||||
onSuccess={(next) => {
|
||||
setSession(next)
|
||||
setLoginOpen(false)
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 主题的顶部背景图:固定 65vh,底部渐隐到页面底色 */}
|
||||
<div className="site-background" aria-hidden="true" />
|
||||
|
||||
@@ -287,11 +319,15 @@ export default function App() {
|
||||
projectCount={projects.length}
|
||||
sampleProject={projects[0]?.name}
|
||||
live={live}
|
||||
headerItems={settings.settings.header.items}
|
||||
headerEnabled={settings.settings.header.enabled}
|
||||
headerSpeed={settings.settings.header.speed}
|
||||
authenticated={session?.authenticated === true}
|
||||
username={session?.username ?? undefined}
|
||||
onFocusSearch={() => searchRef.current?.focus()}
|
||||
onLogin={() => window.location.assign('/login')}
|
||||
onLogin={() => setLoginOpen(true)}
|
||||
onLogout={handleLogout}
|
||||
onSettings={() => window.location.assign('/admin')}
|
||||
/>
|
||||
}
|
||||
sider={
|
||||
|
||||
Reference in New Issue
Block a user