fix(front): restore grouped sidebar with IM Bot section (channels + wechat-bot); normalize brand color #5B5FED

This commit is contained in:
qyyfw committed 2026-07-13 23:35:11 +08:00
1 parent 9c684076f3
commit 2847289921
2 files changed
+139 -50

No files matched your search

+71 -19
View File
@@ -1,16 +1,18 @@
/* ===== Front Layout ===== */ /* ===== Front Layout V3 - Light Theme ===== */
.front-layout { .front-layout {
display: flex; display: flex;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
background: #F5F7FC;
color: #1A1F36;
} }
/* Sidebar */ /* Sidebar */
.front-sidebar { .front-sidebar {
width: var(--sidebar-width); width: var(--sidebar-width);
min-width: var(--sidebar-width); min-width: var(--sidebar-width);
background: var(--color-sidebar-bg); background: linear-gradient(180deg, #FFFFFF 0%, #FAFBFE 100%);
border-right: 1px solid var(--color-border-light); border-right: 1px solid rgba(0, 0, 0, 0.08);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: width var(--transition-slow), min-width var(--transition-slow); transition: width var(--transition-slow), min-width var(--transition-slow);
@@ -29,7 +31,7 @@
justify-content: space-between; justify-content: space-between;
padding: var(--space-lg) var(--space-lg); padding: var(--space-lg) var(--space-lg);
height: var(--header-height); height: var(--header-height);
border-bottom: 1px solid var(--color-border-light); border-bottom: 1px solid rgba(0, 0, 0, 0.08);
} }
.sidebar-logo { .sidebar-logo {
@@ -41,7 +43,7 @@
.sidebar-brand { .sidebar-brand {
font-size: var(--text-lg); font-size: var(--text-lg);
font-weight: 700; font-weight: 700;
color: var(--color-text-primary); color: #1A1F36;
letter-spacing: -0.02em; letter-spacing: -0.02em;
white-space: nowrap; white-space: nowrap;
} }
@@ -53,13 +55,14 @@
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--color-text-tertiary); color: #8892B0;
transition: all var(--transition-fast); transition: all var(--transition-fast);
cursor: pointer;
} }
.collapse-btn:hover { .collapse-btn:hover {
background: var(--color-bg-hover); background: rgba(0, 0, 0, 0.08);
color: var(--color-text-primary); color: #8892B0;
} }
/* Navigation */ /* Navigation */
@@ -68,7 +71,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
padding: var(--space-sm) var(--space-sm); padding: var(--space-sm);
overflow-y: auto; overflow-y: auto;
} }
@@ -77,14 +80,23 @@
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
} }
.nav-group-title {
padding: 14px var(--space-md) 6px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #9CA3AF;
}
.nav-item { .nav-item {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-md); gap: var(--space-md);
padding: var(--space-md) var(--space-md); padding: 10px var(--space-md);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--color-text-secondary); color: #8892B0;
text-decoration: none; text-decoration: none;
font-size: var(--text-base); font-size: var(--text-base);
font-weight: 500; font-weight: 500;
@@ -93,13 +105,13 @@
} }
.nav-item:hover { .nav-item:hover {
background: var(--color-sidebar-hover); background: rgba(91, 95, 237, 0.06);
color: var(--color-text-primary); color: #1A1F36;
} }
.nav-item.active { .nav-item.active {
background: var(--color-sidebar-active); background: rgba(91, 95, 237, 0.12);
color: var(--color-accent); color: #5B5FED;
} }
.nav-icon { .nav-icon {
@@ -115,10 +127,30 @@
flex: 1; flex: 1;
} }
/* Active dot indicator */
.nav-active-dot {
display: none;
}
.nav-item.active .nav-active-dot {
display: block;
position: absolute;
right: 8px;
width: 6px;
height: 6px;
border-radius: 50%;
background: #5B5FED;
box-shadow: 0 0 8px rgba(91, 95, 237, 0.5);
}
/* Sidebar Footer */ /* Sidebar Footer */
.sidebar-footer { .sidebar-footer {
padding: var(--space-md); padding: var(--space-md);
border-top: 1px solid var(--color-border-light); border-top: 1px solid rgba(0, 0, 0, 0.08);
}
.sidebar-footer .nav-item {
margin-top: 2px;
} }
.user-info-mini { .user-info-mini {
@@ -126,13 +158,14 @@
align-items: center; align-items: center;
gap: var(--space-md); gap: var(--space-md);
padding: var(--space-sm); padding: var(--space-sm);
margin-bottom: 4px;
} }
.user-avatar-sm { .user-avatar-sm {
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: var(--radius-full); border-radius: var(--radius-full);
background: linear-gradient(135deg, var(--color-accent), #8185F5); background: linear-gradient(135deg, #5B5FED, #4A4ECC);
color: white; color: white;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -151,16 +184,21 @@
.user-name { .user-name {
font-size: var(--text-sm); font-size: var(--text-sm);
font-weight: 600; font-weight: 600;
color: var(--color-text-primary); color: #1A1F36;
line-height: 1.3; line-height: 1.3;
} }
.user-plan { .user-plan {
font-size: var(--text-xs); font-size: var(--text-xs);
color: var(--color-accent); color: #5B5FED;
line-height: 1.3; line-height: 1.3;
} }
.logout-btn:hover {
color: #EF4444 !important;
background: rgba(239, 68, 68, 0.1) !important;
}
/* Main Content */ /* Main Content */
.front-main { .front-main {
flex: 1; flex: 1;
@@ -168,6 +206,12 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
background: #F5F7FC;
}
/* Forward-slash route for apps - hidden default route */
.front-main > * {
height: 100%;
} }
/* Responsive */ /* Responsive */
@@ -187,3 +231,11 @@
transform: translateX(0); transform: translateX(0);
} }
} }
/* Home & Admin links */
.nav-home-link {
margin-bottom: 4px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
padding-bottom: 8px;
}
+68 -31
View File
@@ -3,15 +3,31 @@ import { Outlet, NavLink, useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import './FrontLayout.css'; import './FrontLayout.css';
const navItems = [ const navGroups = [
{ path: '/chat', icon: 'chat', label: 'AI聊天' }, {
{ path: '/channels', icon: 'channel', label: '频道' }, title: '智能体',
{ path: '/knowledge', icon: 'knowledge', label: '知识库' }, items: [
{ path: '/market', icon: 'market', label: '应用市场' }, { path: '/chat', icon: 'chat', label: 'AI聊天' },
{ path: '/token', icon: 'token', label: 'Token驾驶舱' }, { path: '/knowledge', icon: 'knowledge', label: '知识库' },
{ path: '/pricing', icon: 'pricing', label: '付费中心' }, { path: '/market', icon: 'market', label: '技能市场' },
{ path: '/assets', icon: 'assets', label: '资产' }, { path: '/evolution', icon: 'evolution', label: 'AI自进化' },
{ path: '/evolution', icon: 'evolution', label: 'AI进化' }, ],
},
{
title: 'IM Bot',
items: [
{ path: '/channels', icon: 'channel', label: '频道管理' },
{ path: '/wechat-bot', icon: 'wechat', label: '微信机器人' },
],
},
{
title: '资源中心',
items: [
{ path: '/assets', icon: 'assets', label: '资产文件' },
{ path: '/token', icon: 'token', label: 'Token驾驶舱' },
{ path: '/pricing', icon: 'pricing', label: '付费中心' },
],
},
]; ];
const bottomItems = [ const bottomItems = [
@@ -24,12 +40,14 @@ const iconPaths: Record<string, JSX.Element> = {
channel: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>, channel: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
knowledge: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>, knowledge: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>,
market: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.2"/><rect x="14" y="3" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="7" rx="1.2"/><rect x="14" y="14" width="7" height="7" rx="1.2"/></svg>, market: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.2"/><rect x="14" y="3" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="7" rx="1.2"/><rect x="14" y="14" width="7" height="7" rx="1.2"/></svg>,
apps: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="9" height="9" rx="2"/><rect x="13" y="2" width="9" height="9" rx="2"/><rect x="2" y="13" width="9" height="9" rx="2"/><rect x="13" y="13" width="5" height="5" rx="1.5"/><path d="M18 18v3"/><path d="M19.5 16.5v6"/></svg>,
token: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>, token: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>,
pricing: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 18V6"/></svg>, pricing: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 18V6"/></svg>,
profile: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>, profile: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>,
notify: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>, notify: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>,
assets: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>, assets: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>,
evolution: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>, evolution: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>,
wechat: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/><circle cx="9" cy="11" r="1"/><circle cx="15" cy="11" r="1"/></svg>,
}; };
const FrontLayout: React.FC = () => { const FrontLayout: React.FC = () => {
@@ -46,49 +64,71 @@ const FrontLayout: React.FC = () => {
<div className="front-layout"> <div className="front-layout">
{/* Sidebar */} {/* Sidebar */}
<aside className={`front-sidebar ${collapsed ? 'collapsed' : ''}`}> <aside className={`front-sidebar ${collapsed ? 'collapsed' : ''}`}>
{/* Header */}
<div className="sidebar-header"> <div className="sidebar-header">
<div className="sidebar-logo"> <div className="sidebar-logo">
<svg width="28" height="28" viewBox="0 0 32 32" fill="none"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="8" fill="#5B5FED"/> <defs>
<linearGradient id="flGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#5B5FED"/>
<stop offset="100%" stopColor="#4A4ECC"/>
</linearGradient>
</defs>
<rect width="32" height="32" rx="8" fill="url(#flGrad)"/>
<path d="M8 20V12l4 4 4-4v8" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/> <path d="M8 20V12l4 4 4-4v8" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
<path d="M6 22h20" stroke="white" strokeWidth="1.5" strokeLinecap="round" opacity="0.5"/> <path d="M6 22h20" stroke="white" strokeWidth="1.5" strokeLinecap="round" opacity="0.5"/>
</svg> </svg>
{!collapsed && <span className="sidebar-brand">Qihu-AIFlow</span>} {!collapsed && <span className="sidebar-brand">七虎AI</span>}
</div> </div>
<button className="collapse-btn" onClick={() => setCollapsed(!collapsed)}> <button className="collapse-btn" onClick={() => setCollapsed(!collapsed)}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/> {collapsed ? <><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></> : <><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></>}
</svg> </svg>
</button> </button>
</div> </div>
{/* Navigation */}
<nav className="sidebar-nav"> <nav className="sidebar-nav">
<div className="nav-section"> <NavLink to="/" className={({ isActive }) => `nav-item nav-home-link`} end>
{navItems.map(item => ( <span className="nav-item-icon">
<NavLink <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
key={item.path} </span>
to={item.path} {!collapsed && <span className="nav-item-label">返回首页</span>}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} </NavLink>
> {navGroups.map(group => (
<span className="nav-icon">{iconPaths[item.icon]}</span> <div className="nav-section" key={group.title}>
{!collapsed && <span className="nav-label">{item.label}</span>} {!collapsed && <div className="nav-group-title">{group.title}</div>}
</NavLink> {group.items.map(item => (
))} <NavLink
</div> key={item.path}
to={item.path}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
title={collapsed ? item.label : undefined}
>
<span className="nav-icon">{iconPaths[item.icon]}</span>
{!collapsed && <span className="nav-label">{item.label}</span>}
{!collapsed && <span className="nav-active-dot" />}
</NavLink>
))}
</div>
))}
<div className="nav-section nav-bottom"> <div className="nav-section nav-bottom">
{bottomItems.map(item => ( {bottomItems.map(item => (
<NavLink <NavLink
key={item.path} key={item.path}
to={item.path} to={item.path}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
title={collapsed ? item.label : undefined}
> >
<span className="nav-icon">{iconPaths[item.icon]}</span> <span className="nav-icon">{iconPaths[item.icon]}</span>
{!collapsed && <span className="nav-label">{item.label}</span>} {!collapsed && <span className="nav-label">{item.label}</span>}
{!collapsed && <span className="nav-active-dot" />}
</NavLink> </NavLink>
))} ))}
</div> </div>
</nav> </nav>
{/* Footer */}
<div className="sidebar-footer"> <div className="sidebar-footer">
<div className="user-info-mini"> <div className="user-info-mini">
<div className="user-avatar-sm"> <div className="user-avatar-sm">
@@ -105,17 +145,14 @@ const FrontLayout: React.FC = () => {
<NavLink <NavLink
to="/admin" to="/admin"
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
title="返回后台" title="管理后台"
> >
<span className="nav-icon"> <span className="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1.2"/> <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-2.83 2.83l-.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-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 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 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 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"/>
<rect x="14" y="3" width="7" height="7" rx="1.2"/>
<rect x="3" y="14" width="7" height="7" rx="1.2"/>
<rect x="14" y="14" width="7" height="7" rx="1.2"/>
</svg> </svg>
</span> </span>
{!collapsed && <span className="nav-label">返回后台</span>} {!collapsed && <span className="nav-label">管理后台</span>}
</NavLink> </NavLink>
)} )}
<button <button