fix(admin): auto-expand sidebar navigation group for active child page

- Add getExpandedMenusForPath to auto-expand the parent menu group
  when navigating to a child page (e.g., /admin/users expands "系统管理")
- Add hasActiveChild highlight on parent menu group button (bg-gray-100)
- Use lazy initialization for expandedMenus state based on current path
- Use useEffect to track pathname changes and auto-expand accordingly
- Replace `path` unused param with `_path` to satisfy TypeScript strict
This commit is contained in:
2026-08-03 11:49:43 +08:00
parent 848f4b51d2
commit b262bf0836
+49 -7
View File
@@ -1,6 +1,6 @@
'use client';
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
import { useAuth } from './auth-context';
@@ -82,11 +82,48 @@ const menuItems = [
export function AdminLayout({ children }: { children: React.ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [expandedMenus, setExpandedMenus] = useState<Set<string>>(new Set(['内容管理']));
const { user, logout } = useAuth();
const pathname = usePathname();
const router = useRouter();
const isActive = (href: string) => pathname === href || pathname?.startsWith(href + '/');
/**
* 根据当前路径自动展开对应的父菜单组
*/
const getExpandedMenusForPath = (_path: string): Set<string> => {
const result = new Set<string>();
for (const group of menuItems) {
for (const item of group.children) {
if (isActive(item.href)) {
result.add(group.label);
break;
}
}
}
// 若当前路径在某个组内,保留已有展开状态并追加;否则保留默认展开
if (result.size > 0) {
return result;
}
return new Set(['内容管理']);
};
const [expandedMenus, setExpandedMenus] = useState<Set<string>>(() => getExpandedMenusForPath(pathname));
// 路径变化时自动展开对应父菜单组
const prevPathname = useRef(pathname);
useEffect(() => {
if (prevPathname.current !== pathname) {
prevPathname.current = pathname;
const menusForPath = getExpandedMenusForPath(pathname);
setExpandedMenus((prev) => {
const next = new Set(prev);
menusForPath.forEach((label) => next.add(label));
return next;
});
}
}, [pathname]);
const toggleMenu = (label: string) => {
setExpandedMenus((prev) => {
const next = new Set(prev);
@@ -101,8 +138,6 @@ export function AdminLayout({ children }: { children: React.ReactNode }) {
router.push('/admin/login');
};
const isActive = (href: string) => pathname === href || pathname?.startsWith(href + '/');
return (
<div className="min-h-screen bg-gray-50">
{/* Mobile overlay */}
@@ -146,11 +181,17 @@ export function AdminLayout({ children }: { children: React.ReactNode }) {
</Link>
{/* Menu groups */}
{menuItems.map((group) => (
{menuItems.map((group) => {
const hasActiveChild = group.children.some((item) => isActive(item.href));
return (
<div key={group.label}>
<button
onClick={() => toggleMenu(group.label)}
className="flex items-center justify-between w-full px-3 py-2.5 rounded-lg text-sm font-medium text-gray-600 hover:bg-gray-100 transition-colors"
className={`flex items-center justify-between w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors ${
hasActiveChild
? 'bg-gray-100 text-gray-900'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<span className="flex items-center gap-3">
<group.icon className="w-4 h-4" />
@@ -182,7 +223,8 @@ export function AdminLayout({ children }: { children: React.ReactNode }) {
</div>
)}
</div>
))}
);
})}
</nav>
{/* User info */}