import React, { useState, useEffect } from 'react'; import { useNavigate, Form, useLoaderData } from '@remix-run/react'; import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; import styles from "~/styles/pages/home.css?url"; import dayjs from 'dayjs'; import type { EntryModule } from '~/api/home/home'; import { getUserSession, logout } from "~/api/login/auth.server"; import { toastService } from '~/components/ui'; import { DOCUMENT_URL, CROSS_CHECKING_ONLY_MODE, CROSS_CHECKING_ONLY_PORT, getCurrentPort } from '~/config/api-config'; export const links = () => [ { rel: "stylesheet", href: styles } ]; export const meta: MetaFunction = () => { return [ { title: "中国烟草AI合同及卷宗审核系统 - 首页" }, { name: "description", content: "中国烟草AI合同及卷宗审核系统首页" }, ]; }; // 处理登出请求 export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const intent = formData.get("intent"); if (intent === "logout") { return logout(request); } return null; } // 获取用户信息 export async function loader({ request }: LoaderFunctionArgs) { // 🔒 认证检查已在 getUserSession() 中统一处理 // 如果未认证,会自动重定向到登录页,不会执行到这里 const { userRole, userInfo, frontendJWT } = await getUserSession(request); // 🔑 获取用户地区并查询入口模块 let entryModules: EntryModule[] = []; if (frontendJWT) { const { getEntryModules } = await import('~/api/home/home'); entryModules = await getEntryModules(frontendJWT); console.log(`📦 [Index Loader] 获取到 ${entryModules.length} 个入口模块`); } else { console.warn('⚠️ [Index Loader] 缺少 JWT,返回空模块列表'); } // 🔑 检查用户是否有系统设置权限 let hasSettingsAccess = false; let hasCrossCheckingAccess = false; let hasChatLLMAccess = false; let settingsChildren: { path: string; title: string }[] = []; if (userRole && frontendJWT) { const { getUserRoutesByRole } = await import('~/api/auth/user-routes'); const routesResult = await getUserRoutesByRole(userRole, frontendJWT, true); // includeHidden=true // console.log('🔍 [Index Loader] 顶级路由paths:', routesResult.data?.map(r => r.path)); if (routesResult.success && routesResult.data) { // 查找 '/settings' 路由及其子路由 const settingsRoute = routesResult.data.find(route => route.path === '/settings'); if (settingsRoute) { hasSettingsAccess = true; // 提取子路由信息(仅 path 和 title) if (settingsRoute.children && settingsRoute.children.length > 0) { settingsChildren = settingsRoute.children.map(child => ({ path: child.path, title: child.title })); } } // 检查是否存在顶级路由 '/cross-checking' // 🔒 交叉评查访问控制: // - CROSS_CHECKING_ONLY_MODE=false 时,所有端口都可访问(根据后端权限) // - CROSS_CHECKING_ONLY_MODE=true 时,只有 51707 端口可访问 const currentPort = getCurrentPort(); if (!CROSS_CHECKING_ONLY_MODE || currentPort === CROSS_CHECKING_ONLY_PORT) { hasCrossCheckingAccess = routesResult.data.some(route => route.path === '/cross-checking'); } else { hasCrossCheckingAccess = false; // CROSS_CHECKING_ONLY_MODE=true 且非51707端口不显示交叉评查入口 } // 检查是否存在顶级路由 '/chat-with-llm' hasChatLLMAccess = routesResult.data.some(route => route.path === '/chat-with-llm'); // console.log(`🔑 [Index Loader] 用户${hasSettingsAccess ? '有' : '没有'}系统设置权限`); // console.log(`🔑 [Index Loader] 系统设置子路由数量: ${settingsChildren.length}`); // console.log(`🔑 [Index Loader] 用户${hasCrossCheckingAccess ? '有' : '没有'}交叉评查权限`); // console.log(`🔑 [Index Loader] 用户${hasChatLLMAccess ? '有' : '没有'}智慧法务助手权限`); } } // 🔑 判断是否启用交叉评查专属模式 // 条件:CROSS_CHECKING_ONLY_MODE=true 且 当前端口为 51707 // 注意:currentPort 已在上面的权限检查中获取,这里复用(如果在 if 块外需要则重新获取) const currentPortForMode = getCurrentPort(); const isCrossCheckingOnlyMode = CROSS_CHECKING_ONLY_MODE && currentPortForMode === CROSS_CHECKING_ONLY_PORT; if (isCrossCheckingOnlyMode) { console.log(`🔒 [Index Loader] 交叉评查专属模式已启用 (端口: ${currentPortForMode})`); } // 返回用户信息、入口模块和权限给客户端 return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess, hasCrossCheckingAccess, hasChatLLMAccess, settingsChildren, isCrossCheckingOnlyMode // 新增:交叉评查专属模式标志 }); } export default function Index() { const navigate = useNavigate(); const loaderData = useLoaderData(); const [currentDateTime, setCurrentDateTime] = useState({ date: '', time: '' }); // 检查是否通过51707端口访问 // const [isPort51707, setIsPort51707] = useState(false); // 用户信息:优先使用服务端返回的,否则从 localStorage 读取 const [userInfo, setUserInfo] = useState(loaderData.userInfo); const [userRole, setUserRole] = useState(loaderData.userRole); useEffect(() => { if (typeof window !== 'undefined') { // setIsPort51707(window.location.port === '51707'); // 如果服务端没有返回用户信息,从 localStorage 读取 if (!loaderData.userInfo || !loaderData.userRole) { const storedUserInfoStr = localStorage.getItem('user_info'); if (storedUserInfoStr) { try { const storedUserInfo = JSON.parse(storedUserInfoStr); console.log('📖 [Index] 从 localStorage 读取用户信息:', storedUserInfo); setUserInfo(storedUserInfo); setUserRole(storedUserInfo.user_role || ''); } catch (error) { console.error('❌ [Index] 解析 localStorage 用户信息失败:', error); } } } } }, [loaderData.userInfo, loaderData.userRole]); // 打印用户角色 useEffect(() => { // console.log('📋 [Index] 当前用户角色:', userRole); // console.log('👤 [Index] 当前用户信息:', userInfo); }, [userRole, userInfo]); // 🔑 清除系统设置模式和交叉评查模式标志(当用户返回首页时) useEffect(() => { if (typeof window !== 'undefined') { const settingsMode = sessionStorage.getItem('settingsMode'); const crossCheckingMode = sessionStorage.getItem('crossCheckingMode'); if (settingsMode === 'true') { sessionStorage.removeItem('settingsMode'); // console.log('🔄 [Index] 清除系统设置模式标志'); } if (crossCheckingMode === 'true') { sessionStorage.removeItem('crossCheckingMode'); // console.log('🔄 [Index] 清除交叉评查模式标志'); } } }, []); // 只在组件挂载时执行一次 // 更新日期时间 useEffect(() => { const updateDateTime = () => { const now = dayjs(); // 格式化日期: YYYY/MM/DD setCurrentDateTime({ date: now.format('YYYY/MM/DD'), time: now.format('HH:mm:ss') }); }; // 初始化时间 updateDateTime(); // 每秒更新一次 const timerID = setInterval(updateDateTime, 1000); return () => clearInterval(timerID); }, []); // 处理模块点击 const handleModuleClick = (module: EntryModule) => { // 提取文档类型 IDs const typeIds = module.documentTypes.map((dt) => dt.id) || []; if (module.requiresDocumentTypes && typeIds.length === 0) { toastService.error('该入口尚未关联文档类型,无法进入'); console.warn('⚠️ [Index] 模块未关联文档类型:', module.name); return; } if (typeof window !== 'undefined') { // 🔑 清除各页面的筛选条件缓存(切换入口模块时重置) sessionStorage.removeItem('rules.searchParams'); // 🔑 存储到 sessionStorage(用于客户端请求) if (typeIds.length > 0) { sessionStorage.setItem('documentTypeIds', JSON.stringify(typeIds)); // console.log('📝 [Index] 存储到客户端 sessionStorage:', typeIds); } else { // 清空文档类型数据 sessionStorage.removeItem('documentTypeIds'); } // 存储模块信息 sessionStorage.setItem('selectedModuleId', String(module.id)); sessionStorage.setItem('selectedModuleName', module.name); sessionStorage.setItem('selectedModulePicPath', module.iconPath || '') } const targetPath = module.targetPath || '/home'; if (targetPath === '/chat-with-llm/chat' && typeof window !== 'undefined') { sessionStorage.setItem('selectedModulePicPath', module.iconPath || '/images/icon_assistant.png'); } navigate(targetPath); }; // 处理键盘事件 const handleKeyDown = (module: EntryModule, e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { handleModuleClick(module); } }; // 获取模块图标(根据模块 path 或 id) const getModuleIcon = (module: EntryModule) => { if (module.iconPath){ if (module.iconPath.startsWith('/images/')) { return module.iconPath; } return `${DOCUMENT_URL}${module.iconPath}`; } return '/images/icon_assistant.png'; }; // 处理登出 const handleLogout = () => { // 清除sessionStorage中的所有数据 if (typeof window !== 'undefined') { sessionStorage.clear(); } // 使用Form组件提交登出请求 const form = document.getElementById('logout-form') as HTMLFormElement; if (form) { form.submit(); } else { // 如果找不到表单,直接导航到登录页 navigate('/login'); } }; // 处理进入系统设置 const handleEnterSettings = () => { // 🔑 检查是否有系统设置的子路由 if (!loaderData.settingsChildren || loaderData.settingsChildren.length === 0) { // 没有子路由,显示错误提示 toastService.error('您无权限访问或页面丢失'); console.warn('⚠️ [Index] 系统设置没有可访问的子路由'); return; } if (typeof window !== 'undefined') { // 🔑 设置标志:表示用户通过系统设置入口进入 sessionStorage.setItem('settingsMode', 'true'); // 清除模块相关的标志(因为不是从入口模块进入) sessionStorage.removeItem('selectedModuleId'); sessionStorage.removeItem('selectedModuleName'); sessionStorage.removeItem('selectedModulePicPath'); // 清除交叉评查模式标志 sessionStorage.removeItem('crossCheckingMode'); } // 跳转到第一个子路由 const firstChildPath = loaderData.settingsChildren[0].path; console.log(`📌 [Index] 系统设置:跳转到第一个子路由 ${firstChildPath}`); navigate(firstChildPath); }; // 处理进入交叉评查 const handleEnterCrossChecking = () => { if (typeof window !== 'undefined') { // 🔑 设置标志:表示用户通过交叉评查入口进入 sessionStorage.setItem('crossCheckingMode', 'true'); sessionStorage.setItem('selectedModuleName', '交叉评查') sessionStorage.setItem('selectedModulePicPath', '/images/icon_cross@2x.png') // 清除模块相关的标志(因为不是从入口模块进入) sessionStorage.removeItem('selectedModuleId'); // sessionStorage.removeItem('selectedModuleName'); // sessionStorage.removeItem('selectedModulePicPath'); // 清除系统设置模式标志 sessionStorage.removeItem('settingsMode'); } // 跳转到交叉评查的默认页面 navigate('/cross-checking'); }; return (
{/* 登出表单 - 隐藏 */}
{/* 头部 */}
中国烟草
中国烟草 CHINA TOBACCO
{/* 系统设置按钮 - 只在有权限且非交叉评查专属模式时显示 */} {loaderData.hasSettingsAccess && !loaderData.isCrossCheckingOnlyMode && ( )} {currentDateTime.date} {currentDateTime.time}
{(() => { const displayName = (userInfo?.nick_name || (userInfo as { nickname?: string })?.nickname || (userInfo as { name?: string })?.name || '') as string; const lastChar = displayName ? displayName.charAt(displayName.length - 1) : '用'; return ( <>
{lastChar}
{displayName || '未知用户'} ); })()}
{/* 主要内容 */}

- 欢迎来到智慧法务平台 -

{/* 模块网格区域 */}
{/* 🔒 交叉评查专属模式:只显示交叉评查入口 */} {loaderData.isCrossCheckingOnlyMode ? ( loaderData.hasCrossCheckingAccess ? (
{ if (e.key === 'Enter' || e.key === ' ') { handleEnterCrossChecking(); } }} role="button" tabIndex={0} aria-label="交叉评查" > 交叉评查 { (e.target as HTMLImageElement).style.display = 'none'; const parent = (e.target as HTMLImageElement).parentElement; if (parent) { const icon = document.createElement('i'); icon.className = 'ri-shuffle-line'; icon.style.fontSize = '48px'; icon.style.color = 'var(--color-primary)'; parent.insertBefore(icon, parent.firstChild); } }} /> 交叉评查
) : (
暂无可用模块
) ) : ( /* 正常模式:显示所有入口模块 */ loaderData.entryModules && loaderData.entryModules.length > 0 ? ( <> {loaderData.entryModules.map((module: EntryModule) => { const isLLMModule = module.targetPath === '/chat-with-llm/chat'; const isCrossCheckingModule = module.targetPath === '/cross-checking'; // 🔑 如果是智慧法务助手且用户没有访问权限,则不渲染该模块 if (isLLMModule && !loaderData.hasChatLLMAccess) { return null; } // 交叉评查已在下面独立渲染,避免首页重复出现两张卡片 if (isCrossCheckingModule) { return null; } return (
handleModuleClick(module)} onKeyDown={(e) => handleKeyDown(module, e)} role="button" tabIndex={0} aria-label={module.name} > {module.name} {module.name}
); })} {/* 交叉评查入口 - 独立渲染,不依赖智慧法务助手模块 */} {loaderData.hasCrossCheckingAccess && (
{ if (e.key === 'Enter' || e.key === ' ') { handleEnterCrossChecking(); } }} role="button" tabIndex={0} aria-label="交叉评查" > 交叉评查 { (e.target as HTMLImageElement).style.display = 'none'; const parent = (e.target as HTMLImageElement).parentElement; if (parent) { const icon = document.createElement('i'); icon.className = 'ri-shuffle-line'; icon.style.fontSize = '48px'; icon.style.color = 'var(--color-primary)'; parent.insertBefore(icon, parent.firstChild); } }} /> 交叉评查
)} ) : (
暂无可用模块
) )}
); }