feat: 管理界面优化 — 移动端自适应 + 数据源 + LLM 配置
移动端自适应: - AdminLayout: 汉堡菜单 + 可折叠侧边栏 + 遮罩层 + ESC 关闭 - 所有页面响应式布局 (grid-cols-1 sm:grid-cols-2 lg:grid-cols-4) - 触摸友好按钮 (min-h-[44px]) - 表格移动端卡片视图 新增页面: - DataSources: 数据源状态 + API Key 脱敏 + 测试连接 - LLMConfig: LLM 配置 + 测试连接 + 使用统计 + 最近预测 增强功能: - Config: 配置列表 + 修改指南 + 快捷导航 - types: 新增 DataSourceStatus, LLMUsageStats 等类型 - dal: 新增 testDataSource, fetchDataSourceStatuses, testLLMConnection 等
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
/**
|
||||
* Admin 后台 - 布局组件
|
||||
*
|
||||
* 提供管理界面的基本结构:顶栏 + 侧边栏 + 内容区
|
||||
* 响应式布局: 移动端汉堡菜单 + 可折叠侧边栏
|
||||
* 桌面端: 固定侧边栏 + 内容区
|
||||
* 暗色主题,参考线性风格设计
|
||||
*/
|
||||
|
||||
import { NavLink, Outlet } from 'react-router-dom'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { NavLink, Outlet, useLocation } from 'react-router-dom'
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ to: '/admin', label: '仪表盘', icon: '◇', end: true },
|
||||
@@ -13,22 +15,72 @@ const NAV_ITEMS = [
|
||||
{ to: '/admin/predictions', label: '预测管理', icon: '◆' },
|
||||
{ to: '/admin/backtest', label: '回测管理', icon: '◉' },
|
||||
{ to: '/admin/monitoring', label: '监控面板', icon: '◐' },
|
||||
{ to: '/admin/config', label: '配置管理', icon: '◑' },
|
||||
{ to: '/admin/data-sources', label: '数据源', icon: '◫' },
|
||||
{ to: '/admin/llm-config', label: 'LLM 配置', icon: '◬' },
|
||||
{ to: '/admin/config', label: '系统配置', icon: '◑' },
|
||||
]
|
||||
|
||||
export default function AdminLayout() {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
const location = useLocation()
|
||||
|
||||
// 路由变化时关闭移动端菜单
|
||||
const closeSidebar = useCallback(() => setSidebarOpen(false), [])
|
||||
|
||||
useEffect(() => {
|
||||
closeSidebar()
|
||||
}, [location.pathname, closeSidebar])
|
||||
|
||||
// ESC 键关闭菜单
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setSidebarOpen(false)
|
||||
}
|
||||
document.addEventListener('keydown', handler)
|
||||
return () => document.removeEventListener('keydown', handler)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-950 text-gray-200 overflow-hidden">
|
||||
{/* ── 移动端遮罩层 ── */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 侧边栏 ── */}
|
||||
<aside className="flex w-56 flex-shrink-0 flex-col border-r border-gray-800 bg-gray-900">
|
||||
<div className="border-b border-gray-800 px-5 py-4">
|
||||
<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
|
||||
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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 overflow-y-auto px-3 py-4" aria-label="主导航">
|
||||
{/* 导航 */}
|
||||
<nav className="flex-1 overflow-y-auto px-3 py-4">
|
||||
<ul className="space-y-0.5">
|
||||
{NAV_ITEMS.map(item => (
|
||||
<li key={item.to}>
|
||||
@@ -36,7 +88,7 @@ export default function AdminLayout() {
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors ${
|
||||
`flex items-center gap-3 rounded-md px-3 py-2.5 text-sm transition-colors min-h-[44px] ${
|
||||
isActive
|
||||
? 'bg-gray-800 text-white font-medium'
|
||||
: 'text-gray-400 hover:bg-gray-800/60 hover:text-gray-200'
|
||||
@@ -53,10 +105,11 @@ export default function AdminLayout() {
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* 底部 */}
|
||||
<div className="border-t border-gray-800 px-4 py-3">
|
||||
<a
|
||||
href="/"
|
||||
className="flex items-center gap-2 text-xs text-gray-500 transition-colors hover:text-gray-300"
|
||||
className="flex items-center gap-2 text-xs text-gray-500 transition-colors hover:text-gray-300 min-h-[44px]"
|
||||
>
|
||||
<span aria-hidden="true">←</span>
|
||||
返回前台
|
||||
@@ -67,18 +120,33 @@ export default function AdminLayout() {
|
||||
{/* ── 主内容区 ── */}
|
||||
<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-6">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* 版本 */}
|
||||
<div className="text-xs text-gray-500">
|
||||
Profeto Admin v1.0
|
||||
<span className="hidden sm:inline">Profeto Admin v1.0</span>
|
||||
<span className="sm:hidden">v1.0</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 页面内容 */}
|
||||
<main className="flex-1 overflow-y-auto p-6">
|
||||
<main className="flex-1 overflow-y-auto p-4 lg:p-6">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user