feat: 管理界面报刊风统一改造 — 移除独立暗色主题,预测/回测/监控/配置页增强
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Admin 后台 - 布局组件
|
||||
* Admin 后台 - 布局组件(报刊风)
|
||||
*
|
||||
* 响应式布局: 移动端汉堡菜单 + 可折叠侧边栏
|
||||
* 桌面端: 固定侧边栏 + 内容区
|
||||
* 暗色主题,参考线性风格设计
|
||||
* 与前台同一套纸色语言:报头式侧栏 + 报眉顶栏 + 细线分区。
|
||||
* 响应式: 移动端汉堡菜单 + 抽屉侧栏,桌面端固定侧栏。
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { NavLink, Outlet, useLocation } from 'react-router-dom'
|
||||
import { fetchHealth } from './dal'
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ to: '/admin', label: '仪表盘', icon: '◇', end: true },
|
||||
@@ -20,13 +20,38 @@ const NAV_ITEMS = [
|
||||
{ to: '/admin/config', label: '系统配置', icon: '◑' },
|
||||
]
|
||||
|
||||
/** 报眉日期行,与前台同款式 */
|
||||
function dateLine(): string {
|
||||
return new Date().toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'long',
|
||||
})
|
||||
}
|
||||
|
||||
export default function AdminLayout() {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
const [healthOk, setHealthOk] = useState<boolean | null>(null)
|
||||
const location = useLocation()
|
||||
|
||||
const checkHealth = useCallback(async () => {
|
||||
try {
|
||||
const h = await fetchHealth()
|
||||
setHealthOk(h?.status === 'healthy' || h?.status === 'ok')
|
||||
} catch {
|
||||
setHealthOk(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
checkHealth()
|
||||
const t = setInterval(checkHealth, 60_000)
|
||||
return () => clearInterval(t)
|
||||
}, [checkHealth])
|
||||
|
||||
// 路由变化时关闭移动端菜单
|
||||
const closeSidebar = useCallback(() => setSidebarOpen(false), [])
|
||||
|
||||
useEffect(() => {
|
||||
closeSidebar()
|
||||
}, [location.pathname, closeSidebar])
|
||||
@@ -41,11 +66,11 @@ export default function AdminLayout() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-950 text-gray-200 overflow-hidden">
|
||||
<div className="flex h-screen overflow-hidden bg-paper-50 text-ink-800">
|
||||
{/* ── 移动端遮罩层 ── */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm lg:hidden"
|
||||
className="fixed inset-0 z-40 bg-ink-900/40 lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
@@ -54,33 +79,26 @@ export default function AdminLayout() {
|
||||
{/* ── 侧边栏 ── */}
|
||||
<aside
|
||||
className={`
|
||||
fixed inset-y-0 left-0 z-50 flex w-64 flex-shrink-0 flex-col border-r border-gray-800 bg-gray-900
|
||||
fixed inset-y-0 left-0 z-50 flex w-60 flex-shrink-0 flex-col border-r border-ink-900 bg-paper-50
|
||||
transform transition-transform duration-200 ease-in-out
|
||||
lg:relative lg:z-auto lg:translate-x-0
|
||||
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||
`}
|
||||
aria-label="主导航"
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-between border-b border-gray-800 px-5 py-4">
|
||||
<h1 className="font-serif text-lg font-bold tracking-wider text-gray-100">
|
||||
Profeto
|
||||
<span className="ml-2 text-xs font-normal tracking-normal text-gray-500">Admin</span>
|
||||
{/* 报头 */}
|
||||
<div className="flex items-center justify-between border-b border-ink-900 px-5 py-4">
|
||||
<h1 className="font-serif text-lg font-bold tracking-widest text-ink-900">
|
||||
先知
|
||||
<span className="ml-2 align-baseline font-serif text-xs font-normal italic tracking-normal text-ink-500">
|
||||
Profeto
|
||||
</span>
|
||||
</h1>
|
||||
{/* 移动端关闭按钮 */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="rounded-md p-2 text-gray-400 hover:bg-gray-800 hover:text-gray-200 lg:hidden min-h-[44px] min-w-[44px]"
|
||||
aria-label="关闭菜单"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<span className="text-2xs tracking-[0.25em] text-ink-400">ADMIN</span>
|
||||
</div>
|
||||
|
||||
{/* 导航 */}
|
||||
<nav className="flex-1 overflow-y-auto px-3 py-4">
|
||||
{/* 导航:选中项以印报红方块标记,同前台胜平负选中样式 */}
|
||||
<nav className="flex-1 overflow-y-auto px-3 py-4" aria-label="管理导航">
|
||||
<ul className="space-y-0.5">
|
||||
{NAV_ITEMS.map(item => (
|
||||
<li key={item.to}>
|
||||
@@ -88,17 +106,25 @@ export default function AdminLayout() {
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 rounded-md px-3 py-2.5 text-sm transition-colors min-h-[44px] ${
|
||||
`flex min-h-[44px] items-center gap-2.5 px-3 py-2.5 text-sm transition-colors ${
|
||||
isActive
|
||||
? 'bg-gray-800 text-white font-medium'
|
||||
: 'text-gray-400 hover:bg-gray-800/60 hover:text-gray-200'
|
||||
? 'bg-press-wash/60 font-medium text-press'
|
||||
: 'text-ink-500 hover:bg-paper-100 hover:text-ink-900'
|
||||
}`
|
||||
}
|
||||
>
|
||||
<span className="text-base leading-none opacity-60" aria-hidden="true">
|
||||
{item.icon}
|
||||
</span>
|
||||
{item.label}
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<span
|
||||
className={`inline-block h-1.5 w-1.5 flex-shrink-0 ${isActive ? 'bg-press' : 'bg-transparent'}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="text-base leading-none opacity-50" aria-hidden="true">
|
||||
{item.icon}
|
||||
</span>
|
||||
{item.label}
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
@@ -106,48 +132,59 @@ export default function AdminLayout() {
|
||||
</nav>
|
||||
|
||||
{/* 底部 */}
|
||||
<div className="border-t border-gray-800 px-4 py-3">
|
||||
<div className="border-t border-ink-200 px-4 py-3">
|
||||
<a
|
||||
href="/"
|
||||
className="flex items-center gap-2 text-xs text-gray-500 transition-colors hover:text-gray-300 min-h-[44px]"
|
||||
className="flex min-h-[44px] items-center gap-2 text-xs text-ink-500 transition-colors hover:text-press"
|
||||
>
|
||||
<span aria-hidden="true">←</span>
|
||||
返回前台
|
||||
返回前台版面
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* ── 主内容区 ── */}
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* 顶栏 */}
|
||||
<header className="flex h-12 flex-shrink-0 items-center justify-between border-b border-gray-800 bg-gray-900 px-4 lg:px-6">
|
||||
{/* 移动端菜单按钮 */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="rounded-md p-2 text-gray-400 hover:bg-gray-800 hover:text-gray-200 lg:hidden min-h-[44px] min-w-[44px]"
|
||||
aria-label="打开菜单"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* 状态指示 */}
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-emerald-500" aria-hidden="true" />
|
||||
<span className="hidden sm:inline">系统运行中</span>
|
||||
{/* 报眉:日期 + 系统状态 */}
|
||||
<header className="flex h-11 flex-shrink-0 items-center justify-between border-b border-ink-200 px-4 lg:px-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="-ml-1 p-2 text-ink-500 hover:text-ink-900 lg:hidden"
|
||||
aria-label="打开菜单"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<span className="hidden text-2xs text-ink-500 sm:inline">{dateLine()}</span>
|
||||
</div>
|
||||
|
||||
{/* 版本 */}
|
||||
<div className="text-xs text-gray-500">
|
||||
<span className="hidden sm:inline">Profeto Admin v1.0</span>
|
||||
<span className="sm:hidden">v1.0</span>
|
||||
<div className="flex items-center gap-4 text-2xs">
|
||||
<span className="inline-flex items-center gap-1.5 text-ink-500">
|
||||
<span
|
||||
className={`inline-block h-1.5 w-1.5 ${
|
||||
healthOk === null ? 'bg-ink-300' : healthOk ? 'bg-ink-900' : 'bg-press'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{healthOk === null ? '检测中' : healthOk ? '系统正常' : '系统异常'}
|
||||
</span>
|
||||
<a
|
||||
href="/"
|
||||
className="text-press transition-colors hover:text-press-dark sm:hidden"
|
||||
aria-label="返回前台"
|
||||
>
|
||||
前台
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 页面内容 */}
|
||||
<main className="flex-1 overflow-y-auto p-4 lg:p-6">
|
||||
<Outlet />
|
||||
<main className="flex-1 overflow-y-auto p-4 lg:p-8">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user