import { useEffect, lazy, Suspense, useMemo } from 'react'; import { HashRouter, Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom'; import { ConfigProvider, theme as antdTheme, Spin, Result } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import MainLayout from './layouts/MainLayout'; import Login from './pages/Login'; import { ErrorBoundary } from './components/ErrorBoundary'; import { useAuthStore } from './stores/auth'; import { useAppStore } from './stores/app'; import type { ThemeName } from './stores/app'; import { ROUTE_PERMISSIONS, FROZEN_ROUTES, validateRouteCoverage } from './routeConfig'; const Home = lazy(() => import('./pages/Home')); const Users = lazy(() => import('./pages/Users')); const Roles = lazy(() => import('./pages/Roles')); const Organizations = lazy(() => import('./pages/Organizations')); const Workflow = lazy(() => import('./pages/Workflow')); const Messages = lazy(() => import('./pages/Messages')); const Settings = lazy(() => import('./pages/Settings')); const PluginAdmin = lazy(() => import('./pages/PluginAdmin')); const PluginMarket = lazy(() => import('./pages/PluginMarket')); const PluginCRUDPage = lazy(() => import('./pages/PluginCRUDPage')); const PluginTabsPage = lazy(() => import('./pages/PluginTabsPage').then((m) => ({ default: m.PluginTabsPage }))); const PluginTreePage = lazy(() => import('./pages/PluginTreePage').then((m) => ({ default: m.PluginTreePage }))); const PluginGraphPage = lazy(() => import('./pages/PluginGraphPage').then((m) => ({ default: m.PluginGraphPage }))); const PluginDashboardPage = lazy(() => import('./pages/PluginDashboardPage').then((m) => ({ default: m.PluginDashboardPage }))); const PluginKanbanPage = lazy(() => import('./pages/PluginKanbanPage')); // 健康管理模块 const PatientList = lazy(() => import('./pages/health/PatientList')); const PatientDetail = lazy(() => import('./pages/health/PatientDetail')); const PatientTagManage = lazy(() => import('./pages/health/PatientTagManage')); const DoctorList = lazy(() => import('./pages/health/DoctorList')); const AppointmentList = lazy(() => import('./pages/health/AppointmentList')); const DoctorSchedule = lazy(() => import('./pages/health/DoctorSchedule')); const FollowUpTaskList = lazy(() => import('./pages/health/FollowUpTaskList')); const FollowUpRecordList = lazy(() => import('./pages/health/FollowUpRecordList')); const ConsultationList = lazy(() => import('./pages/health/ConsultationList')); const ConsultationDetail = lazy(() => import('./pages/health/ConsultationDetail')); const PointsRuleList = lazy(() => import('./pages/health/PointsRuleList')); const PointsProductList = lazy(() => import('./pages/health/PointsProductList')); const PointsOrderList = lazy(() => import('./pages/health/PointsOrderList')); const OfflineEventList = lazy(() => import('./pages/health/OfflineEventList')); const StatisticsDashboard = lazy(() => import('./pages/health/StatisticsDashboard')); const AiPromptList = lazy(() => import('./pages/health/AiPromptList')); const AiAnalysisList = lazy(() => import('./pages/health/AiAnalysisList')); const AiUsageDashboard = lazy(() => import('./pages/health/AiUsageDashboard')); const AiConfigPage = lazy(() => import('./pages/health/AiConfigPage')); const AlertList = lazy(() => import('./pages/health/AlertList')); const AlertDashboard = lazy(() => import('./pages/health/AlertDashboard')); const AlertRuleList = lazy(() => import('./pages/health/AlertRuleList')); const DeviceManage = lazy(() => import('./pages/health/DeviceManage')); const RealtimeMonitor = lazy(() => import('./pages/health/RealtimeMonitor')); const OAuthClientList = lazy(() => import('./pages/health/OAuthClientList')); const DialysisManageList = lazy(() => import('./pages/health/DialysisManageList')); const ActionInbox = lazy(() => import('./pages/health/ActionInbox')); const FollowUpTemplateList = lazy(() => import('./pages/health/FollowUpTemplateList')); const CarePlanList = lazy(() => import('./pages/health/CarePlanList')); const CarePlanDetail = lazy(() => import('./pages/health/CarePlanDetail')); const ShiftList = lazy(() => import('./pages/health/ShiftList')); const ShiftDetail = lazy(() => import('./pages/health/ShiftDetail')); const MedicationRecordList = lazy(() => import('./pages/health/MedicationRecordList')); const BleGatewayList = lazy(() => import('./pages/health/BleGatewayList')); const BleGatewayDetail = lazy(() => import('./pages/health/BleGatewayDetail')); const CriticalValueThresholdList = lazy(() => import('./pages/health/CriticalValueThresholdList')); const DiagnosisList = lazy(() => import('./pages/health/DiagnosisList')); const FamilyProxyPage = lazy(() => import('./pages/health/FamilyProxyPage')); const ConsentList = lazy(() => import('./pages/health/ConsentList')); // 内容管理 const ArticleManageList = lazy(() => import('./pages/health/ArticleManageList')); const ArticleEditor = lazy(() => import('./pages/health/articleEditor/ArticleEditor')); const ArticleCategoryManage = lazy(() => import('./pages/health/ArticleCategoryManage')); const ArticleTagManage = lazy(() => import('./pages/health/ArticleTagManage')); const BannerManage = lazy(() => import('./pages/health/BannerManage')); const MediaLibrary = lazy(() => import('./pages/health/MediaLibrary')); function FrozenRoute() { return ; } function ForbiddenPage() { const navigate = useNavigate(); return (
navigate('/')} style={{ cursor: 'pointer', color: 'var(--ant-color-primary)', background: 'none', border: 'none', fontSize: 14 }}>返回首页} />
); } function PrivateRoute({ children }: { children: React.ReactNode }) { const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const permissions = useAuthStore((s) => s.permissions); const location = useLocation(); if (!isAuthenticated) return ; const path = location.pathname; // 冻结路由检查 if (FROZEN_ROUTES.some((frozen) => path.startsWith(frozen))) { return ; } // 首页/工作台始终放行 if (path === '/' || path === '') return <>{children}; const matchedPrefix = Object.keys(ROUTE_PERMISSIONS).find( (prefix) => path === prefix || path.startsWith(prefix + '/'), ); if (matchedPrefix) { const required = ROUTE_PERMISSIONS[matchedPrefix]; const hasAccess = required.some((r) => permissions.includes(r)); if (!hasAccess) return ; } else { return ; } return <>{children}; } const baseToken = { borderRadius: 10, borderRadiusLG: 12, borderRadiusSM: 6, fontFamily: "'Noto Sans SC', -apple-system, system-ui, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', Helvetica, Arial, sans-serif", fontSize: 14, fontSizeHeading4: 20, controlHeight: 40, controlHeightLG: 44, controlHeightSM: 32, boxShadow: 'none', boxShadowSecondary: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)', }; const baseComponents = { Button: { primaryShadow: 'none', fontWeight: 500 }, Card: { paddingLG: 20 }, Menu: { itemBorderRadius: 10, itemMarginInline: 8, itemHeight: 40 }, Modal: { borderRadiusLG: 16 }, Tag: { borderRadiusSM: 6 }, }; const themeConfigs: Record; components: Record> }> = { blue: { token: { ...baseToken, colorPrimary: '#2563eb', colorSuccess: '#059669', colorWarning: '#d97706', colorError: '#dc2626', colorInfo: '#0284c7', colorBgLayout: '#f8fafc', colorBgContainer: '#ffffff', colorBgElevated: '#ffffff', colorBorder: '#e2e8f0', colorBorderSecondary: '#f1f5f9', }, components: { ...baseComponents, Table: { headerBg: '#f1f5f9', headerColor: '#475569', rowHoverBg: '#f1f5f9', fontSize: 14 }, }, }, warm: { token: { ...baseToken, borderRadius: 12, borderRadiusLG: 14, borderRadiusSM: 8, colorPrimary: '#C4623A', colorSuccess: '#5B7A5E', colorWarning: '#C4873A', colorError: '#B54A4A', colorInfo: '#8B7A5E', colorBgLayout: '#F5F0EB', colorBgContainer: '#ffffff', colorBgElevated: '#ffffff', colorBorder: '#E8E2DC', colorBorderSecondary: '#F0EBE5', }, components: { ...baseComponents, Table: { headerBg: '#EDE8E2', headerColor: '#7A756E', rowHoverBg: '#F5F0EB', fontSize: 14 }, }, }, dark: { token: { ...baseToken, colorPrimary: '#60A5FA', colorSuccess: '#34D399', colorWarning: '#FBBF24', colorError: '#F87171', colorInfo: '#38BDF8', colorBgLayout: '#0F172A', colorBgContainer: '#1E293B', colorBgElevated: '#334155', colorBorder: '#334155', colorBorderSecondary: 'rgba(255,255,255,0.06)', boxShadow: 'none', boxShadowSecondary: '0 2px 8px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2)', }, components: { ...baseComponents, Table: { headerBg: '#1E293B', headerColor: '#94A3B8', rowHoverBg: '#1E293B', fontSize: 14 }, }, }, emerald: { token: { ...baseToken, borderRadius: 10, borderRadiusLG: 14, borderRadiusSM: 8, colorPrimary: '#5B7A5E', colorSuccess: '#3D7A42', colorWarning: '#B8863A', colorError: '#A54A4A', colorInfo: '#4A7A8B', colorBgLayout: '#F4F7F4', colorBgContainer: '#ffffff', colorBgElevated: '#ffffff', colorBorder: '#D5DED5', colorBorderSecondary: '#E5ECE5', }, components: { ...baseComponents, Table: { headerBg: '#EDF2ED', headerColor: '#5A6E5A', rowHoverBg: '#F4F7F4', fontSize: 14 }, }, }, }; export default function App() { const loadFromStorage = useAuthStore((s) => s.loadFromStorage); const themeName = useAppStore((s) => s.theme); useEffect(() => { loadFromStorage(); }, [loadFromStorage]); useEffect(() => { document.documentElement.setAttribute('data-theme', themeName); }, [themeName]); // DEV mode: validate all routes have permission declarations useEffect(() => { validateRouteCoverage([ "/users", "/roles", "/organizations", "/workflow", "/messages", "/settings", "/plugins/admin", "/plugins/market", "/health/statistics", "/health/patients", "/health/tags", "/health/doctors", "/health/appointments", "/health/schedules", "/health/follow-up-tasks", "/health/follow-up-records", "/health/consultations", "/health/points-rules", "/health/points-products", "/health/points-orders", "/health/offline-events", "/health/ai-prompts", "/health/ai-analysis", "/health/ai-usage", "/health/ai-config", "/health/alerts", "/health/alert-dashboard", "/health/alert-rules", "/health/devices", "/health/realtime-monitor", "/health/oauth-clients", "/health/dialysis", "/health/action-inbox", "/health/follow-up-templates", "/health/care-plans", "/health/shifts", "/health/medications", "/health/ble-gateways", "/health/critical-value-thresholds", "/health/diagnoses", "/health/family-proxy", "/health/consents", "/health/articles", "/health/article-categories", "/health/article-tags", "/health/banners", "/health/media-library", "/health/medication-records", ]); }, []); const isDark = themeName === 'dark'; const antTheme = useMemo(() => themeConfigs[themeName] ?? themeConfigs.blue, [themeName]); return ( <> 跳转到主要内容 } /> }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* 健康管理 */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* 内容管理 */} } /> } /> } /> } /> } /> } /> } /> } /> ); }