Fix prelaunch navigation and log scale regressions

This commit is contained in:
2026-05-12 00:28:38 +08:00
parent 7c2f073cbf
commit 77d096cdc9
11 changed files with 670 additions and 259 deletions

View File

@@ -1,91 +1,95 @@
/**
* AdminLayout - 管理后台布局
*
*
* 布局:侧栏 248px + 顶栏 64px + 内容区
*/
import { useState, useEffect } from 'react'
import { Layout, Menu, Avatar, Dropdown, Spin, Drawer, Button, type MenuProps } from 'antd'
import { useEffect, useState } from 'react'
import { Avatar, Button, Drawer, Dropdown, Layout, Menu, Spin, type MenuProps } from 'antd'
import {
DashboardOutlined,
SafetyOutlined,
FileTextOutlined,
ApiOutlined,
UserOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
MenuOutlined,
DashboardOutlined,
FileTextOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuOutlined,
MenuUnfoldOutlined,
SafetyOutlined,
SettingOutlined,
UserOutlined,
} from '@ant-design/icons'
import type { ReactNode } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import { useAuth } from '@/app/providers/auth-context'
import { useBreadcrumbs } from '@/lib/hooks/useBreadcrumbs'
import styles from './AdminLayout.module.css'
const { Sider, Header, Content } = Layout
const { Content, Header, Sider } = Layout
const menuLabel = (testId: string, text: string) => (
<span data-testid={testId}>{text}</span>
)
// 管理员菜单配置
const adminMenuItems: MenuProps['items'] = [
{
key: '/dashboard',
icon: <DashboardOutlined />,
label: '总览',
label: menuLabel('nav-dashboard', '总览'),
},
{
key: 'access-control',
icon: <SafetyOutlined />,
label: '访问控制',
label: menuLabel('nav-group-access-control', '访问控制'),
children: [
{ key: '/users', label: '用户管理' },
{ key: '/roles', label: '角色管理' },
{ key: '/permissions', label: '权限管理' },
{ key: '/users', label: menuLabel('nav-users', '用户管理') },
{ key: '/roles', label: menuLabel('nav-roles', '角色管理') },
{ key: '/permissions', label: menuLabel('nav-permissions', '权限管理') },
],
},
{
key: 'logs',
icon: <FileTextOutlined />,
label: '审计日志',
label: menuLabel('nav-group-logs', '审计日志'),
children: [
{ key: '/logs/login', label: '登录日志' },
{ key: '/logs/operation', label: '操作日志' },
{ key: '/logs/login', label: menuLabel('nav-login-logs', '登录日志') },
{ key: '/logs/operation', label: menuLabel('nav-operation-logs', '操作日志') },
],
},
{
key: 'integration',
icon: <ApiOutlined />,
label: '集成能力',
label: menuLabel('nav-group-integration', '集成能力'),
children: [
{ key: '/webhooks', label: 'Webhooks' },
{ key: '/import-export', label: '导入导出' },
{ key: '/webhooks', label: menuLabel('nav-webhooks', 'Webhooks') },
{ key: '/import-export', label: menuLabel('nav-import-export', '导入导出') },
],
},
{
key: 'profile',
icon: <UserOutlined />,
label: '我的账户',
label: menuLabel('nav-group-profile', '我的账户'),
children: [
{ key: '/profile', label: '个人资料' },
{ key: '/profile/security', label: '安全设置' },
{ key: '/profile', label: menuLabel('nav-profile', '个人资料') },
{ key: '/profile/security', label: menuLabel('nav-profile-security', '安全设置') },
],
},
]
// 非管理员菜单配置(只有 Webhooks 和个人中心)
const userMenuItems: MenuProps['items'] = [
{
key: '/webhooks',
icon: <ApiOutlined />,
label: 'Webhooks',
label: menuLabel('nav-webhooks', 'Webhooks'),
},
{
key: 'profile',
icon: <UserOutlined />,
label: '我的账户',
label: menuLabel('nav-group-profile', '我的账户'),
children: [
{ key: '/profile', label: '个人资料' },
{ key: '/profile/security', label: '安全设置' },
{ key: '/profile', label: menuLabel('nav-profile', '个人资料') },
{ key: '/profile/security', label: menuLabel('nav-profile-security', '安全设置') },
],
},
]
@@ -103,45 +107,47 @@ export function AdminLayout({ children }: AdminLayoutProps) {
const { user, isAdmin, logout, isLoading } = useAuth()
const breadcrumbItems = useBreadcrumbs()
// 检测移动端
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768)
const nextIsMobile = window.innerWidth < 768
setIsMobile(nextIsMobile)
if (!nextIsMobile) {
setMobileDrawerOpen(false)
}
}
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
// 移动端切换侧边栏
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen)
const openMobileDrawer = () => {
setMobileDrawerOpen(true)
}
// 移动端菜单点击后关闭抽屉
const handleMobileMenuClick: MenuProps['onClick'] = (info) => {
navigate(info.key)
const closeMobileDrawer = () => {
setMobileDrawerOpen(false)
}
// 根据是否为管理员选择菜单
const menuItems = isAdmin ? adminMenuItems : userMenuItems
const handleMobileMenuClick: MenuProps['onClick'] = (info) => {
navigate(info.key)
closeMobileDrawer()
}
// 当前选中的菜单
const menuItems = isAdmin ? adminMenuItems : userMenuItems
const selectedKeys = [location.pathname]
// 当前展开的菜单组(根据路径决定哪个分组展开)
const openKeys = collapsed
? []
: [
...(location.pathname.startsWith('/users') ||
location.pathname.startsWith('/roles') ||
location.pathname.startsWith('/permissions')
...(location.pathname.startsWith('/users')
|| location.pathname.startsWith('/roles')
|| location.pathname.startsWith('/permissions')
? ['access-control']
: []),
...(location.pathname.startsWith('/logs') ? ['logs'] : []),
...(location.pathname.startsWith('/webhooks') ||
location.pathname.startsWith('/import-export')
...(location.pathname.startsWith('/webhooks')
|| location.pathname.startsWith('/import-export')
? ['integration']
: []),
...(location.pathname.startsWith('/profile') ? ['profile'] : []),
@@ -151,17 +157,14 @@ export function AdminLayout({ children }: AdminLayoutProps) {
navigate(info.key)
}
// 处理面包屑点击
const handleBreadcrumbClick = (path: string) => {
navigate(path)
}
// 处理登出
const handleLogout = () => {
void logout()
}
// 用户下拉菜单
const userDropdownItems: MenuProps['items'] = [
{
key: 'profile',
@@ -185,7 +188,6 @@ export function AdminLayout({ children }: AdminLayoutProps) {
},
]
// 加载中状态
if (isLoading) {
return (
<div className={styles.loadingContainer}>
@@ -196,12 +198,10 @@ export function AdminLayout({ children }: AdminLayoutProps) {
return (
<Layout className={styles.layout}>
{/* 跳过链接 - 便于键盘用户快速跳转到主要内容 */}
<a href="#main-content" className={styles.skipLink}>
</a>
{/* 侧边栏 */}
<Sider
collapsible
collapsed={collapsed}
@@ -211,12 +211,10 @@ export function AdminLayout({ children }: AdminLayoutProps) {
className={styles.sider}
trigger={null}
>
{/* Logo 区域 */}
<div className={styles.logo}>
{collapsed ? 'UMS' : '用户管理系统'}
</div>
{/* 导航菜单 */}
<Menu
mode="inline"
selectedKeys={selectedKeys}
@@ -228,21 +226,19 @@ export function AdminLayout({ children }: AdminLayoutProps) {
/>
</Sider>
{/* 右侧主体 */}
<Layout>
{/* 顶栏 */}
<Header className={styles.header}>
<div className={styles.headerLeft}>
{/* 折叠/菜单按钮 - 移动端显示菜单图标,桌面端显示折叠图标 */}
{isMobile ? (
<Button
type="text"
icon={<MenuOutlined />}
onClick={toggleMobileDrawer}
onClick={openMobileDrawer}
className={styles.collapseBtn}
data-testid="mobile-nav-trigger"
/>
) : (
<button
<button
className={styles.collapseBtn}
onClick={() => setCollapsed(!collapsed)}
>
@@ -250,13 +246,12 @@ export function AdminLayout({ children }: AdminLayoutProps) {
</button>
)}
{/* 面包屑 */}
{breadcrumbItems && breadcrumbItems.length > 0 && (
{breadcrumbItems && breadcrumbItems.length > 0 ? (
<div className={styles.breadcrumb}>
{breadcrumbItems.map((item, index) => (
<span key={index}>
{item.path ? (
<a
<a
className={styles.breadcrumbLink}
onClick={() => handleBreadcrumbClick(item.path as string)}
>
@@ -267,21 +262,20 @@ export function AdminLayout({ children }: AdminLayoutProps) {
{item.title}
</span>
)}
{index < breadcrumbItems.length - 1 && (
{index < breadcrumbItems.length - 1 ? (
<span className={styles.breadcrumbSeparator}>/</span>
)}
) : null}
</span>
))}
</div>
)}
) : null}
</div>
<div className={styles.headerRight}>
{/* 用户信息 */}
<Dropdown menu={{ items: userDropdownItems }} placement="bottomRight">
<div className={styles.userTrigger}>
<Avatar
size={32}
<Avatar
size={32}
icon={<UserOutlined />}
src={user?.avatar || null}
style={{ backgroundColor: user?.avatar ? undefined : 'var(--color-primary)' }}
@@ -294,21 +288,15 @@ export function AdminLayout({ children }: AdminLayoutProps) {
</div>
</Header>
{/* 内容区 */}
<Content id="main-content" className={styles.content}>
{children || <Outlet />}
</Content>
</Layout>
{/* 移动端抽屉式导航 */}
<Drawer
title={
<div className={styles.logo}>
{collapsed ? 'UMS' : '用户管理系统'}
</div>
}
title={<div className={styles.logo}>{collapsed ? 'UMS' : '用户管理系统'}</div>}
placement="left"
onClose={toggleMobileDrawer}
onClose={closeMobileDrawer}
open={mobileDrawerOpen}
size="default"
className={styles.mobileDrawer}