diff --git a/src/App.jsx b/src/App.jsx
index 5a11b48..0c39e36 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,4 +1,4 @@
-import { lazy, Suspense, useEffect, useMemo, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import { Navigate, Outlet, Route, Routes, useLocation, useNavigate } from "react-router-dom";
import { getMenus } from "./api/navigation.js";
import { useAuth } from "./auth/AuthProvider.jsx";
@@ -7,47 +7,72 @@ import { PageSkeleton } from "./components/layout/PageSkeleton.jsx";
import { Sidebar } from "./components/layout/Sidebar.jsx";
import { findNavItemByPath, mapBackendMenus, navItems } from "./constants/navigation.js";
-const LoginPage = lazy(() => import("./pages/LoginPage.jsx").then((module) => ({ default: module.LoginPage })));
-const OverviewPage = lazy(() => import("./pages/OverviewPage.jsx").then((module) => ({ default: module.OverviewPage })));
-const ServicesPage = lazy(() => import("./pages/ServicesPage.jsx").then((module) => ({ default: module.ServicesPage })));
-const UserManagementPage = lazy(() =>
- import("./pages/UserManagementPage.jsx").then((module) => ({ default: module.UserManagementPage }))
-);
-const RoleManagementPage = lazy(() =>
- import("./pages/RoleManagementPage.jsx").then((module) => ({ default: module.RoleManagementPage }))
-);
-const MenuManagementPage = lazy(() =>
- import("./pages/MenuManagementPage.jsx").then((module) => ({ default: module.MenuManagementPage }))
-);
-const LogsPage = lazy(() => import("./pages/LogsPage.jsx").then((module) => ({ default: module.LogsPage })));
-const NotificationsPage = lazy(() =>
- import("./pages/NotificationsPage.jsx").then((module) => ({ default: module.NotificationsPage }))
-);
+const pageModules = {
+ login: () => import("./pages/LoginPage.jsx").then((module) => module.LoginPage),
+ overview: () => import("./pages/OverviewPage.jsx").then((module) => module.OverviewPage),
+ services: () => import("./pages/ServicesPage.jsx").then((module) => module.ServicesPage),
+ users: () => import("./pages/UserManagementPage.jsx").then((module) => module.UserManagementPage),
+ roles: () => import("./pages/RoleManagementPage.jsx").then((module) => module.RoleManagementPage),
+ menus: () => import("./pages/MenuManagementPage.jsx").then((module) => module.MenuManagementPage),
+ logs: () => import("./pages/LogsPage.jsx").then((module) => module.LogsPage),
+ notifications: () => import("./pages/NotificationsPage.jsx").then((module) => module.NotificationsPage)
+};
+
+const pageCache = new Map();
function App() {
return (
- }>
-
- } />
- }>
- }>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
+
+ } />
+ }>
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- } />
-
-
+
+ } />
+
);
}
+function DeferredPage({ pageKey, ...props }) {
+ const [Page, setPage] = useState(() => pageCache.get(pageKey) || null);
+
+ useEffect(() => {
+ let mounted = true;
+
+ if (pageCache.has(pageKey)) {
+ setPage(() => pageCache.get(pageKey));
+ return undefined;
+ }
+
+ setPage(null);
+ pageModules[pageKey]().then((component) => {
+ pageCache.set(pageKey, component);
+ if (mounted) {
+ setPage(() => component);
+ }
+ });
+
+ return () => {
+ mounted = false;
+ };
+ }, [pageKey]);
+
+ if (!Page) {
+ return ;
+ }
+
+ return ;
+}
+
function RequireAuth() {
const { loading, user } = useAuth();
const location = useLocation();
@@ -118,11 +143,9 @@ function AdminLayout() {
- }>
-
-
-
-
+
+
+
diff --git a/src/components/layout/Sidebar.jsx b/src/components/layout/Sidebar.jsx
index 53e7a2d..e501d8c 100644
--- a/src/components/layout/Sidebar.jsx
+++ b/src/components/layout/Sidebar.jsx
@@ -125,23 +125,25 @@ function NavGroup({
{hasChildren ? : null}
- {hasChildren && isOpen && !isCollapsed ? (
-
- {children.map((child) => {
- const ChildIcon = child.icon;
+ {hasChildren && !isCollapsed ? (
+
+
+ {children.map((child) => {
+ const ChildIcon = child.icon;
- return (
-
- );
- })}
+ return (
+
+ );
+ })}
+
) : null}
diff --git a/src/pages/UserManagementPage.jsx b/src/pages/UserManagementPage.jsx
index 2b4330b..5cea24b 100644
--- a/src/pages/UserManagementPage.jsx
+++ b/src/pages/UserManagementPage.jsx
@@ -322,7 +322,7 @@ export function UserManagementPage() {
setForm({ ...form, mfaEnabled: event.target.checked })} />}
- label="MFA 已开启"
+ label={form.mfaEnabled ? "MFA 已开启" : "MFA 未开启"}
/>
diff --git a/src/styles/app.css b/src/styles/app.css
index c6410a2..dabc836 100644
--- a/src/styles/app.css
+++ b/src/styles/app.css
@@ -374,13 +374,31 @@
}
.app-shell--sidebar-collapsed .brand {
+ gap: 0;
justify-content: center;
padding: 0;
}
+.brand-text,
+.nav-label {
+ display: inline-grid;
+ min-width: 0;
+ max-width: 160px;
+ opacity: 1;
+ overflow: hidden;
+ transform: translateX(0);
+ transition:
+ max-width var(--motion-slow) var(--ease-emphasized),
+ opacity var(--motion-base) var(--ease-standard),
+ transform var(--motion-base) var(--ease-emphasized);
+}
+
.app-shell--sidebar-collapsed .brand-text,
.sidebar--collapsed .nav-label {
- display: none;
+ max-width: 0;
+ opacity: 0;
+ pointer-events: none;
+ transform: translateX(-6px);
}
.sidebar--collapsed {
@@ -421,6 +439,9 @@
transition:
background var(--motion-base) var(--ease-standard),
color var(--motion-base) var(--ease-standard),
+ gap var(--motion-slow) var(--ease-emphasized),
+ padding var(--motion-slow) var(--ease-emphasized),
+ width var(--motion-slow) var(--ease-emphasized),
transform var(--motion-base) var(--ease-emphasized);
}
@@ -452,8 +473,30 @@
.nav-children {
display: grid;
- gap: 4px;
+ grid-template-rows: 0fr;
+ opacity: 0;
+ overflow: hidden;
padding-left: 20px;
+ pointer-events: none;
+ transform: translateY(-4px);
+ transition:
+ grid-template-rows var(--motion-slow) var(--ease-emphasized),
+ opacity var(--motion-base) var(--ease-standard),
+ transform var(--motion-slow) var(--ease-emphasized);
+}
+
+.nav-children--open {
+ grid-template-rows: 1fr;
+ opacity: 1;
+ pointer-events: auto;
+ transform: translateY(0);
+}
+
+.nav-children__inner {
+ display: grid;
+ gap: 4px;
+ min-height: 0;
+ overflow: hidden;
}
.nav-item--child {
diff --git a/src/theme.js b/src/theme.js
index 4d1b10a..124f791 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -144,6 +144,59 @@ export const theme = createTheme({
}
}
},
+ MuiSwitch: {
+ defaultProps: {
+ disableRipple: true
+ },
+ styleOverrides: {
+ root: {
+ width: 46,
+ height: 26,
+ padding: 0,
+ margin: "0 8px",
+ overflow: "visible"
+ },
+ switchBase: {
+ padding: 3,
+ color: "#ffffff",
+ transition: "transform 220ms cubic-bezier(0.16, 1, 0.3, 1)",
+ "&.Mui-checked": {
+ color: "#ffffff",
+ transform: "translateX(20px)",
+ "& + .MuiSwitch-track": {
+ borderColor: "rgba(37, 99, 235, 0.36)",
+ background: "linear-gradient(135deg, #2563eb, #0ea5e9)",
+ boxShadow: "0 8px 20px rgba(37, 99, 235, 0.22)",
+ opacity: 1
+ },
+ "& .MuiSwitch-thumb": {
+ boxShadow: "0 4px 12px rgba(15, 23, 42, 0.18)"
+ }
+ },
+ "&.Mui-disabled": {
+ color: "#ffffff",
+ "& + .MuiSwitch-track": {
+ opacity: 0.5
+ }
+ }
+ },
+ thumb: {
+ width: 20,
+ height: 20,
+ backgroundColor: "#ffffff",
+ boxShadow: "0 3px 10px rgba(15, 23, 42, 0.18)"
+ },
+ track: {
+ border: "1px solid #d8e0ec",
+ borderRadius: 999,
+ backgroundColor: "#e2e8f0",
+ boxShadow: "inset 0 1px 2px rgba(15, 23, 42, 0.06)",
+ opacity: 1,
+ transition:
+ "background 220ms cubic-bezier(0.2, 0, 0, 1), border-color 220ms cubic-bezier(0.2, 0, 0, 1), box-shadow 220ms cubic-bezier(0.2, 0, 0, 1)"
+ }
+ }
+ },
MuiOutlinedInput: {
styleOverrides: {
notchedOutline: {