diff --git a/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx b/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx index 36f0c4b0..5fa4e741 100644 --- a/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx +++ b/Wavelet/frontend/components/layout/openflare-sidebar-menu.tsx @@ -14,98 +14,103 @@ import { SidebarMenuSubButton, SidebarMenuSubItem, } from '@/components/ui/sidebar'; +import type {OpenFlareNavGroup} from '@/lib/navigation/openflare-nav'; import { matchesNavPath, - openflareNavItems, - openflareWebsiteNavGroup, + openflareSidebarNav, isNavGroupActive, } from '@/lib/navigation/openflare-nav'; +function SidebarNavGroupMenuItem({ + group, + pathname, + onNavigate, +}: { + group: OpenFlareNavGroup; + pathname: string; + onNavigate?: () => void; +}) { + const groupActive = isNavGroupActive(pathname, group); + const [open, setOpen] = useState(groupActive); + + useEffect(() => { + if (groupActive) { + setOpen(true); + } + }, [groupActive]); + + return ( + + + + + + {group.title} + + + + + + {group.items.map((item) => ( + + + + {item.title} + + + + ))} + + + + + ); +} + export function OpenFlareSidebarMenu({ onNavigate, }: { onNavigate?: () => void; }) { const pathname = usePathname(); - const websiteGroupActive = isNavGroupActive(pathname, openflareWebsiteNavGroup); - const [websiteGroupOpen, setWebsiteGroupOpen] = useState(websiteGroupActive); - - useEffect(() => { - if (websiteGroupActive) { - setWebsiteGroupOpen(true); - } - }, [websiteGroupActive]); - - const renderFlatItemsBeforeGroup = openflareNavItems.slice(0, 4); - const renderFlatItemsAfterGroup = openflareNavItems.slice(4); return ( - {renderFlatItemsBeforeGroup.map((item) => ( - - - - - {item.title} - - - - ))} + {openflareSidebarNav.map((entry) => { + if (entry.kind === 'group') { + return ( + + ); + } - - - + return ( + - - {openflareWebsiteNavGroup.title} - + + + {entry.title} + - - - - {openflareWebsiteNavGroup.items.map((item) => ( - - - - {item.title} - - - - ))} - - - - - - {renderFlatItemsAfterGroup.map((item) => ( - - - - - {item.title} - - - - ))} + + ); + })} ); } \ No newline at end of file diff --git a/Wavelet/frontend/components/layout/sidebar.tsx b/Wavelet/frontend/components/layout/sidebar.tsx index e927b3de..ae3d1648 100644 --- a/Wavelet/frontend/components/layout/sidebar.tsx +++ b/Wavelet/frontend/components/layout/sidebar.tsx @@ -64,7 +64,7 @@ import { import {useUser} from "@/contexts/user-context" import {usePublicConfig} from "@/hooks/use-public-config" import {OpenFlareSidebarMenu} from "@/components/layout/openflare-sidebar-menu" -import {openflareNavItems} from "@/lib/navigation/openflare-nav" +import {openflareSidebarNav} from "@/lib/navigation/openflare-nav" /* 导航数据 */ const data = { @@ -295,7 +295,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { - {openflareNavItems.length > 0 && ( + {openflareSidebarNav.length > 0 && ( diff --git a/Wavelet/frontend/lib/navigation/openflare-nav.ts b/Wavelet/frontend/lib/navigation/openflare-nav.ts index 6dac5414..8d227406 100644 --- a/Wavelet/frontend/lib/navigation/openflare-nav.ts +++ b/Wavelet/frontend/lib/navigation/openflare-nav.ts @@ -31,17 +31,9 @@ export interface OpenFlareNavGroup { items: OpenFlareNavSubItem[]; } -/** OpenFlare 业务控制台侧栏导航(子页面通过父级入口或页内链接访问) */ -export const openflareNavItems: OpenFlareNavItem[] = [ - {title: '数据看板', url: '/', icon: LayoutDashboard}, - {title: '节点管理', url: '/nodes', icon: Server, childUrls: ['/nodes/detail']}, - {title: '规则管理', url: '/proxy-routes', icon: Route, childUrls: ['/proxy-routes/detail']}, - {title: 'Pages', url: '/pages', icon: FileText, childUrls: ['/pages/detail']}, - {title: 'WAF', url: '/waf', icon: Shield, childUrls: ['/waf/ip-groups']}, - {title: '版本发布', url: '/config-versions', icon: GitBranch}, - {title: '访问日志', url: '/access-logs', icon: ScrollText}, - {title: '性能调优', url: '/performance', icon: Gauge}, -]; +export type OpenFlareSidebarNavEntry = + | ({kind: 'item'} & OpenFlareNavItem) + | ({kind: 'group'} & OpenFlareNavGroup); /** 网站管理折叠组 */ export const openflareWebsiteNavGroup: OpenFlareNavGroup = { @@ -55,6 +47,27 @@ export const openflareWebsiteNavGroup: OpenFlareNavGroup = { ], }; +/** + * OpenFlare 侧栏导航顺序(单一配置源)。 + * 调整菜单顺序或折叠组位置时,只需修改此数组。 + */ +export const openflareSidebarNav: OpenFlareSidebarNavEntry[] = [ + {kind: 'item', title: '数据看板', url: '/', icon: LayoutDashboard}, + {kind: 'item', title: '节点管理', url: '/nodes', icon: Server, childUrls: ['/nodes/detail']}, + {kind: 'item', title: '规则管理', url: '/proxy-routes', icon: Route, childUrls: ['/proxy-routes/detail']}, + {kind: 'item', title: 'WAF', url: '/waf', icon: Shield, childUrls: ['/waf/ip-groups']}, + {kind: 'group', ...openflareWebsiteNavGroup}, + {kind: 'item', title: 'Pages', url: '/pages', icon: FileText, childUrls: ['/pages/detail']}, + {kind: 'item', title: '版本发布', url: '/config-versions', icon: GitBranch}, + {kind: 'item', title: '访问日志', url: '/access-logs', icon: ScrollText}, + {kind: 'item', title: '性能调优', url: '/performance', icon: Gauge}, +]; + +/** 扁平菜单项(供路由判断等逻辑复用) */ +export const openflareNavItems: OpenFlareNavItem[] = openflareSidebarNav + .filter((entry): entry is {kind: 'item'} & OpenFlareNavItem => entry.kind === 'item') + .map(({kind: _kind, ...item}) => item); + /** 网站模块页内二级导航 */ export const openflareWebsiteSubNav = [ {title: '网站列表', url: '/websites'}, @@ -92,9 +105,11 @@ export function isOpenFlareConsoleRoute(pathname: string): boolean { return false; } - if (isNavGroupActive(pathname, openflareWebsiteNavGroup)) { - return true; - } + return openflareSidebarNav.some((entry) => { + if (entry.kind === 'group') { + return isNavGroupActive(pathname, entry); + } - return openflareNavItems.some((item) => matchesNavPath(pathname, item.url, item.childUrls)); -} + return matchesNavPath(pathname, entry.url, entry.childUrls); + }); +} \ No newline at end of file diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 89c328d6..1908fa95 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -57,6 +57,7 @@ sidebar: false - 节点详情 24 小时网络/磁盘 IO 趋势图对齐原版 ECharts 样式,恢复系列摘要卡与平滑面积折线图。 - 侧栏新增「网站管理」折叠组(shadcn Collapsible + SidebarMenuSub),收纳网站、证书、DNS、源站入口。 - 移除网站、证书、DNS 页面右上角互相跳转按钮,改由侧栏折叠组统一导航。 +- 侧栏导航改为 `openflareSidebarNav` 单一配置源,折叠组位置由数组顺序决定,移除 `slice(0, 4)` 硬编码切分。 - OpenFlare 前端路由去除 `/openflare` 前缀,业务页面直接挂载于 `/`(如 `/nodes`、`/websites`);侧栏移除「首页」「我的文件」;旧路径 `/openflare/*`、`/home` 永久重定向至新路径。 ## [v2.3.4] - 2026-06-17