侧边栏图标统一为线条SVG + hover品牌色过渡

- 新增 Icon 组件:8种线条风格 SVG 图标(collection/chart/target/repeat等)
- 统一 stroke-width:1.5,hover 加粗到 2
- hover 时颜色平滑过渡到品牌色(press red)
- 添加 .nav-icon 和 .nav-icon-wrap CSS 类
- 活跃/悬停态:opacity 0.5→1,stroke-width 1.5→2

Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
shangfangjian
2026-09-21 01:01:01 +08:00
co-authored by new-provider/LongCat-2.0 <
parent ad184e1b82
commit 857a21910b
2 changed files with 107 additions and 18 deletions
+91 -18
View File
@@ -12,6 +12,82 @@ import { fetchHealth } from './dal'
import Login from './Login' import Login from './Login'
import { useCommandPalette, CommandPalette } from './useCommandPalette' import { useCommandPalette, CommandPalette } from './useCommandPalette'
// 线条风格 SVG 图标组件
function Icon({ name }: { name: string }) {
const common = 'nav-icon'
switch (name) {
case 'collection':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
</svg>
)
case 'chart':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="20" x2="18" y2="10" />
<line x1="12" y1="20" x2="12" y2="4" />
<line x1="6" y1="20" x2="6" y2="14" />
</svg>
)
case 'target':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="6" />
<circle cx="12" cy="12" r="2" />
</svg>
)
case 'repeat':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<polyline points="17 1 21 5 17 9" />
<path d="M3 11V9a4 4 0 0 1 4-4h14" />
<polyline points="7 23 3 19 7 15" />
<path d="M21 13v2a4 4 0 0 1-4 4H3" />
</svg>
)
case 'eval':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10 9 9 9 8 9" />
</svg>
)
case 'monitor':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
<line x1="8" y1="21" x2="16" y2="21" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
)
case 'settings':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68 1.65 1.65 0 0 0 10 3.17V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0-4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
)
case 'logs':
return (
<svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="8" y1="13" x2="16" y2="13" />
<line x1="8" y1="17" x2="16" y2="17" />
</svg>
)
default:
return null
}
}
const NAV_PAGES: Array<{ to: string; label: string; group: string }> = [ const NAV_PAGES: Array<{ to: string; label: string; group: string }> = [
{ to: '/admin', label: '仪表盘', group: '概览' }, { to: '/admin', label: '仪表盘', group: '概览' },
{ to: '/admin/collection', label: '数据采集', group: '数据流水线' }, { to: '/admin/collection', label: '数据采集', group: '数据流水线' },
@@ -37,29 +113,28 @@ const ROUTE_LABELS: Record<string, string> = {
'/admin/eval': '评估', '/admin/eval': '评估',
} }
const NAV_SECTIONS: { title: string; items: { to: string; label: string; icon: string; end?: boolean }[] }[] = [ const NAV_SECTIONS: { title: string; items: Array<{ to: string; label: string; icon: string }> }[] = [
{ {
title: '数据流水线', title: '数据流水线',
items: [ items: [
{ to: '/admin/collection', label: '数据采集', icon: '' }, { to: '/admin/collection', label: '数据采集', icon: 'collection' },
{ to: '/admin/data-completeness', label: '数据完整性', icon: '' }, { to: '/admin/data-completeness', label: '数据完整性', icon: 'chart' },
{ to: '/admin/predictions', label: '预测管理', icon: '' }, { to: '/admin/predictions', label: '预测管理', icon: 'target' },
{ to: '/admin/backtest', label: '回测', icon: '' }, { to: '/admin/backtest', label: '回测', icon: 'repeat' },
], ],
}, },
{ {
title: '评估与监控', title: '评估与监控',
items: [ items: [
{ to: '/admin/eval', label: '评估', icon: '' }, { to: '/admin/eval', label: '评估', icon: 'eval' },
{ to: '/admin/monitoring', label: '监控', icon: '' }, { to: '/admin/monitoring', label: '监控', icon: 'monitor' },
{ to: '/admin/logs', label: '日志', icon: '▤' },
], ],
}, },
{ {
title: '系统设置', title: '系统设置',
items: [ items: [
{ to: '/admin/settings', label: '设置', icon: '' }, { to: '/admin/settings', label: '设置', icon: 'settings' },
{ to: '/admin/logs', label: '日志', icon: '' }, { to: '/admin/logs', label: '日志', icon: 'logs' },
], ],
}, },
] ]
@@ -227,14 +302,14 @@ export default function AdminLayout() {
to="/admin" to="/admin"
end end
className={({ isActive }) => className={({ isActive }) =>
`flex min-h-[40px] items-center gap-2.5 border-l-4 px-3 text-sm transition-all duration-300 ${ `nav-icon-wrap flex min-h-[40px] items-center gap-2.5 border-l-4 px-3 text-sm transition-all duration-300 ${
isActive isActive
? 'border-press bg-press-wash/60 font-medium text-press' ? 'border-press bg-press-wash/60 font-medium text-press active'
: 'border-transparent text-ink-500 hover:bg-paper-100 hover:text-ink-900' : 'border-transparent text-ink-500 hover:bg-paper-100 hover:text-ink-900'
}` }`
} }
> >
<span className="text-base leading-none opacity-50" aria-hidden="true"></span> <Icon name="chart" />
</NavLink> </NavLink>
@@ -249,16 +324,14 @@ export default function AdminLayout() {
<NavLink <NavLink
to={item.to} to={item.to}
className={({ isActive }) => className={({ isActive }) =>
`flex min-h-[40px] items-center gap-2.5 border-l-4 px-3 pb-1 text-sm transition-all duration-300 ${ `nav-icon-wrap flex min-h-[40px] items-center gap-2.5 border-l-4 px-3 text-sm transition-all duration-300 ${
isActive isActive
? 'border-press bg-press-wash/60 font-medium text-press nav-tab-active' ? 'border-press bg-press-wash/60 font-medium text-press active'
: 'border-transparent text-ink-500 hover:bg-paper-100 hover:text-ink-900' : 'border-transparent text-ink-500 hover:bg-paper-100 hover:text-ink-900'
}` }`
} }
> >
<span className="text-base leading-none opacity-50" aria-hidden="true"> <Icon name={item.icon} />
{item.icon}
</span>
{item.label} {item.label}
</NavLink> </NavLink>
</li> </li>
+16
View File
@@ -157,6 +157,22 @@
.skeleton { .skeleton {
@apply animate-pulse rounded-none bg-ink-200; @apply animate-pulse rounded-none bg-ink-200;
} }
/* ── 侧边栏图标:统一样式 + hover过渡到品牌色 ── */
.nav-icon {
width: 1.125rem;
height: 1.125rem;
stroke-width: 1.5;
flex-shrink: 0;
opacity: 0.5;
transition: all 0.2s ease;
}
.nav-icon-wrap:hover .nav-icon,
.nav-icon-wrap.active .nav-icon {
opacity: 1;
color: #9E1B1B;
stroke-width: 2;
}
} }
/* ── Admin 后台 ── /* ── Admin 后台 ──