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 AiKnowledgePage = lazy(() => import('./pages/health/AiKnowledgePage'));
const AiChatPage = lazy(() => import('./pages/ai/ChatPage'));
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/ai-knowledge", "/health/alerts", "/health/alert-dashboard",
"/ai/chat",
"/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 (
<>
跳转到主要内容
} />
}>
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
{/* 健康管理 */}
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
{/* 内容管理 */}
} />
} />
} />
} />
} />
} />
} />
}
/>
>
);
}