import { useState, useEffect } from 'react'; import { Link, useLocation, useNavigate } from '@remix-run/react'; import type { UserRole } from '~/root'; interface MenuItem { id: string; title: string; path: string; icon: string; hideBreadcrumb?: boolean; requiredRole?: UserRole; children?: MenuItem[]; } interface SidebarProps { onToggle: () => void; collapsed: boolean; userRole: UserRole; selectedApp?: string; // 添加所选应用模块参数 } // 定义不同应用模块下显示的菜单项ID const APP_MENU_MAP = { 'contract': ['home', 'contract-template', 'file-management', 'rule-management', 'system-settings'], 'record': ['home', 'file-management', 'rule-management', 'system-settings'], 'model': ['chat-with-llm'] }; // 应用模块名称映射 const APP_NAME_MAP: Record = { 'contract': '合同管理', 'record': '案卷智能评查', 'model': '智慧法务大模型' }; // 应用模块图标映射 const APP_ICON_MAP: Record = { 'contract': '/images/icon_hetong.png', 'record': '/images/icon_anjuan.png', 'model': '/images/icon_assistant.png' }; export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: SidebarProps) { const location = useLocation(); const [expandedMenus, setExpandedMenus] = useState>({}); const [currentApp, setCurrentApp] = useState(''); // 初始设置为空字符串而不是selectedApp const [isLoading, setIsLoading] = useState(true); // 添加加载状态 const navigate = useNavigate(); // 组件挂载后从 sessionStorage 读取初始 reviewType useEffect(() => { let mounted = true; setIsLoading(true); // 设置加载状态 try { const reviewType = sessionStorage.getItem('reviewType'); // console.log('初始 reviewType:', reviewType); if (reviewType && mounted) { setCurrentApp(reviewType); } else if (selectedApp && mounted) { // 如果没有reviewType,但有selectedApp,使用selectedApp setCurrentApp(selectedApp); } } catch (error) { console.error('读取 reviewType 失败:', error); } finally { // 使用setTimeout确保状态在DOM更新后再变化,避免闪烁 setTimeout(() => { if (mounted) { setIsLoading(false); // 数据加载完成 } }, 0); } return () => { mounted = false; }; }, [selectedApp]); // 从 sessionStorage 获取 reviewType 并设置当前应用模块 useEffect(() => { // 监听 sessionStorage 变化(主要用于多标签页情况) const handleStorageChange = (e: StorageEvent) => { if (e.key === 'reviewType' && e.newValue) { setCurrentApp(e.newValue); } }; // 添加事件监听器 window.addEventListener('storage', handleStorageChange); return () => { window.removeEventListener('storage', handleStorageChange); }; }, []); // 监听路由变化,重新检查 reviewType useEffect(() => { let mounted = true; try { const reviewType = sessionStorage.getItem('reviewType'); // 只有当reviewType变化时才设置加载状态和更新currentApp if (reviewType && reviewType !== currentApp && mounted) { setIsLoading(true); // 路由变化时设置加载状态 setCurrentApp(reviewType); // 使用setTimeout确保状态在DOM更新后再变化,避免闪烁 setTimeout(() => { if (mounted) { setIsLoading(false); } }, 0); } } catch (error) { console.error('路由变化时读取 reviewType 失败:', error); if (mounted) { setIsLoading(false); } } return () => { mounted = false; }; }, [location.pathname, currentApp]); // 监听 selectedApp 属性变化 useEffect(() => { if (selectedApp && selectedApp !== currentApp) { setIsLoading(true); // 设置加载状态 setCurrentApp(selectedApp); // 使用setTimeout确保状态在DOM更新后再变化,避免闪烁 setTimeout(() => { setIsLoading(false); // 数据加载完成 }, 0); } }, [selectedApp, currentApp]); const menuItems: MenuItem[] = [ { id: 'home', title: '系统概览', path: '/home', icon: 'ri-home-line' }, { id: 'chat-with-llm', title: 'AI对话', path: '/chat-with-llm', icon: 'ri-chat-smile-2-line' }, { id: 'file-management', title: '文件管理', path: '/files', icon: 'ri-folder-line', children: [ { id: 'file-upload', title: '文件上传', path: '/files/upload', icon: 'ri-upload-cloud-line' }, { id: 'documents', title: '文档列表', path: '/documents', icon: 'ri-file-list-3-line' } ] }, { id: 'rule-management', title: '评查规则库', path: '/rules', icon: 'ri-book-3-line', children: [ { id: 'rule-groups', title: '评查点分组', path: '/rule-groups', icon: 'ri-folder-open-line' }, { id: 'rules-list', title: '评查点列表', path: '/rules', icon: 'ri-list-check-3' }, { id: 'rules-file', title: '评查文件列表', path: '/rules-files', icon: 'ri-list-check-2' }, // { // id: 'rule-new', // title: '新增评查点', // path: '/rules-new', // requiredRole: 'developer', // icon: 'ri-add-circle-line' // }, // { // id: 'review-detail', // title: '评查详情', // path: '/reviews', // icon: 'ri-file-chart-line' // } ] }, { id: 'contract-template', title: '合同模板', path: '/contract-template', icon: 'ri-file-search-line', children: [ { id: 'contract-search-ai', title: '智能搜索', path: '/contract-template/search', icon: 'ri-search-line' }, { id: 'contract-list', title: '合同列表', path: '/contract-template/list', icon: 'ri-folder-line' } ] }, { id: 'system-settings', title: '系统设置', path: '/settings', icon: 'ri-settings-4-line', requiredRole: 'developer', children: [ { id: 'config-lists', title: '配置列表', path: '/config-lists', icon: 'ri-list-check-3', requiredRole: 'developer' }, // { // id: 'basic-settings', // title: '基础设置', // path: '/settings', // icon: 'ri-equalizer-line' // }, { id: 'document-types', title: '文档类型', path: '/document-types', icon: 'ri-file-list-line', requiredRole: 'developer' }, { id: 'prompt-management', title: '提示词管理', path: '/prompts', icon: 'ri-chat-1-line', requiredRole: 'developer' } ] } ]; // 初始化展开状态,默认全部展开 useEffect(() => { const initialExpandedState: Record = {}; menuItems.forEach(item => { if (item.children) { initialExpandedState[item.id] = true; } }); setExpandedMenus(initialExpandedState); }, []); const toggleMenu = (id: string, e: React.MouseEvent) => { // 我们只防止事件冒泡,不阻止默认行为 e.stopPropagation(); // console.log('父菜单展开/折叠:', id); setExpandedMenus(prev => ({ ...prev, [id]: !prev[id] })); }; const isActive = (path: string) => { return location.pathname === path || location.pathname.startsWith(`${path}/`); }; // 处理侧边栏切换事件 const handleToggleSidebar = (e: React.MouseEvent) => { // console.log('侧边栏折叠/展开'); // 只防止事件冒泡,不阻止默认行为 e.stopPropagation(); onToggle(); }; // 处理子菜单项点击事件 const handleSubMenuClick = (child: MenuItem, e: React.MouseEvent) => { // 只需要阻止冒泡,不阻止默认行为 e.stopPropagation(); // console.log('子菜单点击:', child.title, '路径:', child.path); }; // 获取当前应用模式下应显示的菜单ID列表 const visibleMenuIds = APP_MENU_MAP[currentApp as keyof typeof APP_MENU_MAP] || APP_MENU_MAP['contract']; // const visibleMenuIds = APP_MENU_MAP[currentApp as keyof typeof APP_MENU_MAP] // console.log('当前应用模式:', currentApp, '可见菜单ID:', visibleMenuIds); // 根据用户角色和当前应用模式过滤菜单项 const filteredMenuItems = menuItems.filter(item => { // 如果菜单项需要特定角色但用户没有 if (item.requiredRole && item.requiredRole !== userRole) { return false; } // 检查当前菜单是否在所选应用模式中显示 if (!visibleMenuIds.includes(item.id)) { return false; } return true; }); return (
{ navigate('/'); }} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); navigate('/'); } }} > 智慧法务 {!collapsed &&

智慧法务

}
{!collapsed && (
{isLoading ? ( // 加载中状态,只显示加载图标,保留布局
加载中...
) : ( <> {APP_NAME_MAP[currentApp] {APP_NAME_MAP[currentApp] || ''} )}
)}
{isLoading ? ( // 加载中状态显示,保留菜单布局结构
{Array(5).fill(0).map((_, index) => (
{!collapsed &&
}
))}
) : ( // 数据加载完成后显示菜单 filteredMenuItems.map((item) => (
{!item.children ? ( { // 只阻止冒泡,不阻止默认行为 e.stopPropagation(); // console.log('单级菜单点击:', item.title, '路径:', item.path); }} > {!collapsed && {item.title}} ) : ( <>
{ // console.log('%c父菜单点击 ===> ', 'background: #722ed1; color: white; padding: 2px 4px; border-radius: 2px;', item.title); toggleMenu(item.id, e); }} role="button" tabIndex={0} aria-expanded={expandedMenus[item.id] || false} aria-controls={`submenu-${item.id}`} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleMenu(item.id, e as unknown as React.MouseEvent); } }} >
{!collapsed && {item.title}}
{!collapsed && ( )}
{(expandedMenus[item.id] || collapsed) && (
{item.children .filter(child => !child.requiredRole || child.requiredRole === userRole) .map((child) => ( handleSubMenuClick(child, e)} > {!collapsed && {child.title}} ))}
)} )}
)) )}
); }