生产管理系统前端 菜单箭头显示fix

This commit is contained in:
2025-10-23 15:13:33 +08:00
parent ce2510d526
commit 4f3beb2568
4 changed files with 180 additions and 107 deletions

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation'; import { useRouter, usePathname } from 'next/navigation';
import { LeftSidebar } from './components/LeftSidebar'; import { LeftSidebar } from './components/LeftSidebar';
import { MainContent } from './components/MainContent'; import { MainContent } from './components/MainContent';
import { cn } from '@/lib/utils';
// 菜单项数据结构定义 // 菜单项数据结构定义
interface NavItem { interface NavItem {
@@ -230,18 +231,23 @@ export function SideBarOld({
router.push(path); router.push(path);
}; };
return ( return (
<div className="flex h-full bg-gray-100"> <div className={cn(
"flex h-full bg-background",
"min-h-screen"
)}>
{/* 左侧导航栏 - 独立滚动 */} {/* 左侧导航栏 - 独立滚动 */}
<div className="sidebarScroll"> {!isMobile && (
<LeftSidebar <div className="sidebarScroll">
menus={menus} <LeftSidebar
activePath={currentPath} menus={menus}
onNavigate={handleNavigate} activePath={currentPath}
isMobile={isMobile} onNavigate={handleNavigate}
isCollapsed={!isMobile && isCollapsed} isMobile={isMobile}
onToggleCollapse={() => setIsCollapsed(!isCollapsed)} isCollapsed={isCollapsed}
/> onToggleCollapse={() => setIsCollapsed(!isCollapsed)}
</div> />
</div>
)}
{/* 右侧主内容 - 独立滚动 */} {/* 右侧主内容 - 独立滚动 */}
<div className="flex-1 contentScroll"> <div className="flex-1 contentScroll">
@@ -253,6 +259,20 @@ export function SideBarOld({
{children} {children}
</MainContent> </MainContent>
</div> </div>
{/* 移动端侧边栏 */}
{isMobile && (
<div className="fixed inset-y-0 left-0 z-50 w-64 sidebarScroll">
<LeftSidebar
menus={menus}
activePath={currentPath}
onNavigate={handleNavigate}
isMobile={isMobile}
isCollapsed={false}
onToggleCollapse={() => {}}
/>
</div>
)}
</div> </div>
); );
} }

View File

@@ -3,6 +3,13 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { ChevronDown, ChevronLeft, ChevronRight, Menu, X } from 'lucide-react'; import { ChevronDown, ChevronLeft, ChevronRight, Menu, X } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
interface MenuItem { interface MenuItem {
id: string; id: string;
@@ -32,35 +39,19 @@ export function LeftSidebar({
isCollapsed = false, isCollapsed = false,
onToggleCollapse onToggleCollapse
}: LeftSidebarProps) { }: LeftSidebarProps) {
// 根据activePath自动展开包含该路径的菜单 // 初始状态下所有菜单都折叠
const getInitialExpandedMenus = () => { const getInitialExpandedMenus = () => {
const expanded = new Set<string>(); return new Set<string>();
menus.forEach(menu => {
if (menu.children?.some(child => child.path === activePath)) {
expanded.add(menu.id);
}
});
// 如果没有匹配的,默认展开第一个
if (expanded.size === 0 && menus.length > 0) {
expanded.add(menus[0].id);
}
return expanded;
}; };
const [expandedMenus, setExpandedMenus] = useState<Set<string>>(getInitialExpandedMenus()); const [expandedMenus, setExpandedMenus] = useState<Set<string>>(getInitialExpandedMenus());
// 当activePath或menus变化时自动展开对应的菜单 // 不自动展开菜单,由用户手动控制
// 当侧边栏状态改变时,折叠所有菜单
useEffect(() => { useEffect(() => {
menus.forEach(menu => { setExpandedMenus(new Set());
if (menu.children?.some(child => child.path === activePath)) { }, [isCollapsed]);
setExpandedMenus(prev => {
const newSet = new Set(prev);
newSet.add(menu.id);
return newSet;
});
}
});
}, [activePath, menus]);
const toggleMenu = (menuId: string) => { const toggleMenu = (menuId: string) => {
setExpandedMenus(prev => { setExpandedMenus(prev => {
@@ -77,35 +68,39 @@ export function LeftSidebar({
return ( return (
<div <div
className={cn( className={cn(
"bg-white border-r border-gray-200 transition-all duration-300 flex flex-col h-full", "bg-background border-r transition-all duration-300 flex flex-col h-full",
isMobile ? "fixed inset-y-0 left-0 z-50" : "relative", isMobile ? "fixed inset-y-0 left-0 z-50" : "relative",
isCollapsed ? "w-16" : "w-64" isCollapsed ? "w-16" : "w-64"
)} )}
> >
{/* 头部 - 缩小高度 */} {/* 头部 - 缩小高度 */}
<div className="px-2 py-1 border-b border-gray-200"> <div className="px-2 py-1 border-b">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className={cn( <h2 className={cn(
"font-medium text-gray-900 text-sm transition-all duration-300", "font-medium text-sm transition-all duration-300",
isCollapsed ? "hidden" : "block" isCollapsed ? "hidden" : "block"
)}> )}>
</h2> </h2>
{isMobile ? ( {isMobile ? (
<X className="w-4 h-4 text-gray-600" /> <Button variant="ghost" size="icon" className="h-8 w-8">
<X className="w-4 h-4" />
</Button>
) : ( ) : (
/* 根据侧边栏状态显示不同按钮 */ /* 根据侧边栏状态显示不同按钮 */
<button <Button
variant="ghost"
size="icon"
onClick={onToggleCollapse} onClick={onToggleCollapse}
className="p-1 rounded hover:bg-gray-100 transition-colors" className="h-8 w-8"
title={isCollapsed ? "展开菜单" : "收起菜单"} title={isCollapsed ? "展开菜单" : "收起菜单"}
> >
{isCollapsed ? ( {isCollapsed ? (
<ChevronRight className="w-4 h-4 text-gray-600" /> <ChevronRight className="w-4 h-4" />
) : ( ) : (
<ChevronLeft className="w-4 h-4 text-gray-600" /> <ChevronLeft className="w-4 h-4" />
)} )}
</button> </Button>
)} )}
</div> </div>
</div> </div>
@@ -119,52 +114,93 @@ export function LeftSidebar({
{menus.map((menu) => ( {menus.map((menu) => (
<div key={menu.id}> <div key={menu.id}>
{/* 一级菜单 */} {/* 一级菜单 */}
<button {menu.children ? (
onClick={() => toggleMenu(menu.id)} <Collapsible
className={cn( open={expandedMenus.has(menu.id)}
"w-full flex items-center justify-between px-3 py-2 rounded-md transition-colors text-sm", onOpenChange={(open) => {
"hover:bg-gray-100 hover:text-gray-900", if (open) {
isCollapsed ? "justify-center px-2 py-3" : "px-3 py-2" setExpandedMenus(prev => new Set(prev).add(menu.id));
)} } else {
title={isCollapsed ? menu.label : undefined} setExpandedMenus(prev => {
> const newSet = new Set(prev);
<div className="flex items-center gap-2"> newSet.delete(menu.id);
{menu.icon && ( return newSet;
<span className="flex-shrink-0"> });
{menu.icon} }
</span> }}
)} >
{!isCollapsed && ( <CollapsibleTrigger asChild>
<span className="text-gray-700">{menu.label}</span> <Button
)} variant="ghost"
</div>
{!isCollapsed && menu.children && (
expandedMenus.has(menu.id) ? (
<ChevronDown className="w-4 h-4 text-gray-500 flex-shrink-0" />
) : (
<ChevronRight className="w-4 h-4 text-gray-500 flex-shrink-0" />
)
)}
</button>
{/* 二级菜单 */}
{!isCollapsed && menu.children && expandedMenus.has(menu.id) && (
<div className="ml-4 mt-1 space-y-1">
{menu.children.map((child) => (
<button
key={child.id}
onClick={() => child.path && onNavigate(child.path)}
className={cn( className={cn(
"w-full text-left px-3 py-2 rounded-md transition-colors text-xs", "w-full justify-between text-sm font-normal",
activePath === child.path isCollapsed ? "justify-center px-2 py-3" : "px-3 py-2"
? "bg-green-50 text-green-700 font-medium border-l-2 border-green-600"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
)} )}
title={isCollapsed ? menu.label : undefined}
> >
{child.label} <div className="flex items-center gap-2">
</button> {menu.icon && (
))} <span className="flex-shrink-0">
</div> {menu.icon}
</span>
)}
{!isCollapsed && (
<span>{menu.label}</span>
)}
</div>
{menu.children && (
isCollapsed ? null : (
<ChevronRight
className={cn(
"h-4 w-4 shrink-0 transition-transform duration-200",
expandedMenus.has(menu.id) && "rotate-90"
)}
/>
)
)}
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
{/* 二级菜单 */}
{!isCollapsed && (
<div className="ml-4 mt-1 space-y-1">
{menu.children.map((child) => (
<Button
key={child.id}
variant={activePath === child.path ? "secondary" : "ghost"}
className={cn(
"w-full justify-start text-xs font-normal h-8",
activePath === child.path && "bg-muted text-muted-foreground font-medium border-l-2 border-primary"
)}
onClick={() => child.path && onNavigate(child.path)}
>
{child.label}
</Button>
))}
</div>
)}
</CollapsibleContent>
</Collapsible>
) : (
<Button
variant="ghost"
className={cn(
"w-full justify-start text-sm font-normal",
isCollapsed ? "justify-center px-2 py-3" : "px-3 py-2"
)}
title={isCollapsed ? menu.label : undefined}
>
<div className="flex items-center gap-2">
{menu.icon && (
<span className="flex-shrink-0">
{menu.icon}
</span>
)}
{!isCollapsed && (
<span>{menu.label}</span>
)}
</div>
</Button>
)} )}
</div> </div>
))} ))}
@@ -172,9 +208,9 @@ export function LeftSidebar({
</div> </div>
{/* 底部 */} {/* 底部 */}
<div className="p-4 border-t border-gray-200"> <div className="p-4 border-t">
<div className={cn( <div className={cn(
"text-xs text-gray-500", "text-xs text-muted-foreground",
isCollapsed ? "text-center" : "text-left" isCollapsed ? "text-center" : "text-left"
)}> )}>
{isCollapsed ? "管理" : "管理系统"} {isCollapsed ? "管理" : "管理系统"}

View File

@@ -3,6 +3,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { ChevronLeft, ChevronRight, X } from 'lucide-react'; import { ChevronLeft, ChevronRight, X } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
interface MainContentProps { interface MainContentProps {
children: React.ReactNode; children: React.ReactNode;
@@ -27,22 +29,43 @@ export function MainContent({
} }
}; };
return ( if (isMobile) {
<> return (
{/* 移动端侧边栏遮罩 */} <>
{isMobile && showMobileSidebar && ( {/* 移动端菜单按钮 */}
<div <div className="flex items-center justify-between p-4 border-b bg-background">
className="fixed inset-0 bg-black bg-opacity-50 z-40" <Button
onClick={() => setShowMobileSidebar(false)} variant="ghost"
/> size="icon"
)} onClick={() => setShowMobileSidebar(true)}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
{/* 主内容区域 - 去掉顶部白色横条 */} {/* 移动端侧边栏 */}
<div className="flex-1 flex flex-col"> <Sheet open={showMobileSidebar} onOpenChange={setShowMobileSidebar}>
<div className="p-6"> <SheetContent side="left" className="p-0 w-64">
{/* 这里应该渲染LeftSidebar内容但需要通过props传递 */}
<div className="p-4">
<p className="text-muted-foreground"></p>
</div>
</SheetContent>
</Sheet>
{/* 主内容区域 */}
<div className="flex-1 p-6 bg-background">
{children} {children}
</div> </div>
</>
);
}
return (
<div className="flex-1 flex flex-col bg-background">
<div className="p-6">
{children}
</div> </div>
</> </div>
); );
} }

View File

@@ -1,6 +0,0 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}