import { useState, useEffect } from 'react'; import { Link, useLocation, useNavigate } from '@remix-run/react'; import type { UserRole } from '~/root'; import { getUserRoutesByRole, mapUserRoleToRoleKey, type MenuItem } from '~/api/auth/user-routes'; interface SidebarProps { onToggle: () => void; collapsed: boolean; userRole: UserRole; frontendJWT?: string; selectedApp?: string; // 添加所选应用模块参数 } // 定义不同应用模块下显示的菜单项ID const APP_MENU_MAP = { 'contract': ['home', 'contract-template', 'file-management', 'rule-management', 'cross-checking', 'system-settings'], 'record': ['home', 'file-management', 'rule-management', 'cross-checking', '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, frontendJWT = '', selectedApp = '' }: SidebarProps) { const location = useLocation(); const [expandedMenus, setExpandedMenus] = useState>({}); const [currentApp, setCurrentApp] = useState(''); // 初始设置为空字符串而不是selectedApp const [isLoading, setIsLoading] = useState(true); // 添加加载状态 const [menuItems, setMenuItems] = useState([]); // 动态菜单项 const [isLoadingRoutes, setIsLoadingRoutes] = useState(true); // 路由加载状态 const navigate = useNavigate(); // 获取用户路由权限 useEffect(() => { const fetchUserRoutes = async () => { setIsLoadingRoutes(true); try { console.log('userRole', userRole); const roleKey = mapUserRoleToRoleKey(userRole); const result = await getUserRoutesByRole(roleKey, frontendJWT); if (result.success && result.data) { setMenuItems(result.data); console.log('用户路由权限加载成功:', result.data); } else { console.error('获取用户路由权限失败:', result.error); // 如果需要重定向到首页 if (result.shouldRedirectToHome) { console.log('重定向到首页'); navigate('/'); return; } // 其他错误情况,使用空数组 setMenuItems([]); } } catch (error) { console.error('获取用户路由权限时发生错误:', error); // 发生异常时也重定向到首页 navigate('/'); return; } finally { setIsLoadingRoutes(false); } }; fetchUserRoutes(); }, [userRole, navigate]); // 组件挂载后从 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]); // 初始化展开状态,默认全部展开 useEffect(() => { const initialExpandedState: Record = {}; menuItems.forEach(item => { if (item.children) { initialExpandedState[item.id] = true; } }); setExpandedMenus(initialExpandedState); }, [menuItems]); 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); // 检查是否通过51708端口访问 // const isPort51708 = typeof window !== 'undefined' && window.location.port === '51708'; const isPort51708 = typeof window !== 'undefined' && window.location.port === '5178'; // 根据当前应用模式过滤菜单项 const filteredMenuItems = menuItems.filter(item => { // 如果是51708端口,只显示交叉评查相关菜单 if (isPort51708) { // 如果当前应用是智慧法务大模型,只显示AI对话菜单 if (currentApp === 'model') { return item.id === 'chat-with-llm' || (item.path && item.path.startsWith('/chat-with-llm')); }else{ return item.id === 'cross-checking' || (item.path && item.path.startsWith('/cross-checking')) } } // 检查当前菜单是否在所选应用模式中显示 if (!visibleMenuIds.includes(item.id)) { return false; } return true; }); // filteredMenuItems = filteredMenuItems.map(item => { // if(item.children && item.children.length > 0){ // const children = item.children.filter(child => { // const isUploadByPath = child.path === '/files/upload' || child.path?.startsWith('/files/upload') // const isUploadByTitle = child.title === '文件上传' // return !(isUploadByPath || isUploadByTitle) // }) // return { ...item, children} // } // return item // }) 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 || isLoadingRoutes ? ( // 加载中状态显示,保留菜单布局结构
{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.map((child) => ( handleSubMenuClick(child, e)} > {!collapsed && {child.title}} ))}
)} )}
)) )}
{/* 操作手册下载按钮 */}
); }