From db964d7471dcc3d6d8aad48777389f6c8001ea2c Mon Sep 17 00:00:00 2001 From: 170-carry Date: Fri, 1 May 2026 00:24:28 +0800 Subject: [PATCH] Improve admin loading and menu interactions --- src/App.jsx | 103 ++++++++++++++++++------------ src/components/layout/Sidebar.jsx | 34 +++++----- src/pages/UserManagementPage.jsx | 2 +- src/styles/app.css | 47 +++++++++++++- src/theme.js | 53 +++++++++++++++ 5 files changed, 180 insertions(+), 59 deletions(-) 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: {