feat(adminLayout): refactor route matching logic and improve breadcrumb handling

This commit is contained in:
shiyu
2025-06-06 21:27:20 +08:00
parent 40106095aa
commit 288e10a2d0
8 changed files with 176 additions and 228 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ function App() {
{adminRoutes.map((route) => ( {adminRoutes.map((route) => (
<Route <Route
key={route.key} key={route.key}
path={route.path} path={route.path === '' ? 'index' : route.path}
element={route.element} element={route.element}
/> />
))} ))}
+43 -76
View File
@@ -27,80 +27,63 @@ function AdminLayout() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const routes = useMemo(() => getAdminRoutes(), []); const routes = useMemo(() => getAdminRoutes(), []);
const headerRouteData = useMemo(() => ({ const headerRouteData = useMemo(() => ({
routeInfo: currentRouteData.routeInfo, routeInfo: currentRouteData.routeInfo,
params: currentRouteData.params, params: currentRouteData.params,
title: (currentRouteData.routeInfo?.label || '')
}), [currentRouteData]); }), [currentRouteData]);
const { const { token: { colorBgContainer } } = theme.useToken();
token: { colorBgContainer },
} = theme.useToken();
const findCurrentRoute = useCallback(() => { const findCurrentRoute = useCallback(() => {
const pathname = location.pathname; const pathname = location.pathname;
const adminPath = pathname.replace(/^\/admin\/?/, ''); const adminBasePrefix = '/admin';
if (!pathname.startsWith(adminBasePrefix)) {
return { routeInfo: undefined, params: {} };
}
let adminPath = pathname.substring(adminBasePrefix.length);
if (adminPath.startsWith('/')) {
adminPath = adminPath.substring(1);
}
if (adminPath.length > 0 && adminPath.endsWith('/')) {
adminPath = adminPath.slice(0, -1);
}
if (adminPath === '') { if (adminPath === '') {
const defaultRoute = routes.find(route => route.path === ''); const defaultRoute = routes.find(route => route.path === '');
if (defaultRoute) { if (defaultRoute) {
return { return { routeInfo: defaultRoute, params: {} };
routeInfo: defaultRoute,
params: {}
};
} }
} }
// 查找精确匹配的路由
for (const route of routes) { for (const route of routes) {
const match = matchPath( if (route.path === '' && adminPath !== '') continue;
{ path: route.path, end: true }, if (route.path !== '' && adminPath === '') continue;
adminPath
);
if (match) { if (route.path === adminPath) {
return { return { routeInfo: route, params: {} };
routeInfo: route,
params: Object.fromEntries(
Object.entries(match.params || {}).filter(
([, value]) => value !== undefined
)
) as Record<string, string>
};
} }
}
// 查找包含参数的路由
for (const route of routes) {
if (route.path.includes(':')) { if (route.path.includes(':')) {
const basePath = route.path.split('/:')[0]; const match = matchPath({ path: route.path, end: true }, adminPath);
if (adminPath.startsWith(basePath)) { if (match) {
const match = matchPath( return {
{ path: route.path, end: false }, routeInfo: route,
adminPath params: Object.fromEntries(
); Object.entries(match.params || {}).filter(
([, value]) => value !== undefined && value !== ""
if (match) { ).map(([key, value]) => [key, String(value)])
return { ) as Record<string, string>
routeInfo: route, };
params: Object.fromEntries(
Object.entries(match.params || {}).filter(
([, value]) => value !== undefined
)
) as Record<string, string>
};
}
} }
} }
} }
return { return { routeInfo: undefined, params: {} };
routeInfo: undefined,
params: {}
};
}, [location.pathname, routes]); }, [location.pathname, routes]);
useEffect(() => { useEffect(() => {
@@ -108,7 +91,6 @@ function AdminLayout() {
navigate('/login'); navigate('/login');
return; return;
} }
if (!user) { if (!user) {
refreshUser(); refreshUser();
} }
@@ -120,15 +102,16 @@ function AdminLayout() {
navigate('/'); navigate('/');
} }
}, [user, hasRole, navigate, loading]); }, [user, hasRole, navigate, loading]);
useEffect(() => { useEffect(() => {
const routeData = findCurrentRoute(); const routeData = findCurrentRoute();
setCurrentRouteData(routeData); setCurrentRouteData(routeData);
}, [location.pathname, findCurrentRoute]); }, [location.pathname, findCurrentRoute]);
useEffect(() => { useEffect(() => {
setCollapsed(isMobile); setCollapsed(isMobile);
}, [isMobile]); }, [isMobile]);
// 退出登录处理
const handleLogout = () => { const handleLogout = () => {
clearAuthData(); clearAuthData();
navigate('/login'); navigate('/login');
@@ -138,34 +121,23 @@ function AdminLayout() {
setCollapsed(!collapsed); setCollapsed(!collapsed);
}; };
// 加载状态
if (loading) { if (loading) {
return <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}> return (
... <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
</div>; ...
</div>
);
} }
// 权限检查
if (user && !hasRole(UserRole.Administrator)) { if (user && !hasRole(UserRole.Administrator)) {
return <Navigate to="/" replace />; return <Navigate to="/" replace />;
} }
return ( return (
<Layout style={{ <Layout style={{ height: '100vh', background: '#f0f2f5', fontWeight: 400 }}>
height: '100vh', <Sidebar collapsed={collapsed} isMobile={isMobile} onClose={toggleCollapsed} area="admin" />
background: '#f0f2f5',
fontWeight: 400
}}>
{/* 侧边栏组件 */}
<Sidebar
collapsed={collapsed}
isMobile={isMobile}
onClose={toggleCollapsed}
area="admin"
/>
<Layout> <Layout>
{/* 顶部导航栏组件 */}
<Header <Header
collapsed={collapsed} collapsed={collapsed}
toggleCollapsed={toggleCollapsed} toggleCollapsed={toggleCollapsed}
@@ -174,7 +146,6 @@ function AdminLayout() {
isMobile={isMobile} isMobile={isMobile}
/> />
{/* 主要内容区 */}
<Content style={{ <Content style={{
margin: isMobile ? '10px' : '20px', margin: isMobile ? '10px' : '20px',
background: '#f0f2f5', background: '#f0f2f5',
@@ -191,14 +162,10 @@ function AdminLayout() {
position: 'relative', position: 'relative',
overflow: 'hidden' overflow: 'hidden'
}}> }}>
{/* 渲染子路由组件 */} <Outlet context={{ isMobile, isAdminPanel: true }} />
<Outlet context={{
isMobile,
isAdminPanel: true
}} />
</div> </div>
</Content> </Content>
{/* 页脚组件 */}
<Footer isMobile={isMobile} /> <Footer isMobile={isMobile} />
</Layout> </Layout>
</Layout> </Layout>
+15 -20
View File
@@ -1,18 +1,18 @@
import {useState, useEffect} from 'react'; import { useState, useEffect } from 'react';
import {Outlet, useNavigate, useLocation, matchPath} from 'react-router'; import { Outlet, useNavigate, useLocation, matchPath } from 'react-router';
import {Layout, theme} from 'antd'; import { Layout, theme } from 'antd';
import {clearAuthData, isAuthenticated} from '../api'; import { clearAuthData, isAuthenticated } from '../api';
import useIsMobile from '../hooks/useIsMobile'; import useIsMobile from '../hooks/useIsMobile';
import {useAuth} from '../auth/AuthContext'; import { useAuth } from '../auth/AuthContext';
import Sidebar from './components/Sidebar'; import Sidebar from './components/Sidebar';
import Header from './components/Header'; import Header from './components/Header';
import Footer from './components/Footer'; import Footer from './components/Footer';
import {getMainRoutes, type RouteConfig} from '../routes'; import { getMainRoutes, type RouteConfig } from '../routes';
const {Content} = Layout; const { Content } = Layout;
function MainLayout() { function MainLayout() {
const {refreshUser} = useAuth(); const { refreshUser } = useAuth();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [collapsed, setCollapsed] = useState(isMobile); const [collapsed, setCollapsed] = useState(isMobile);
const [currentRouteData, setCurrentRouteData] = useState<{ const [currentRouteData, setCurrentRouteData] = useState<{
@@ -29,7 +29,7 @@ function MainLayout() {
const routes = getMainRoutes(); const routes = getMainRoutes();
const { const {
token: {colorBgContainer}, token: { colorBgContainer },
} = theme.useToken(); } = theme.useToken();
// 查找当前路由信息 // 查找当前路由信息
@@ -39,7 +39,7 @@ function MainLayout() {
// 测试每个路由是否匹配当前路径 // 测试每个路由是否匹配当前路径
for (const route of routes) { for (const route of routes) {
const match = matchPath( const match = matchPath(
{path: `/${route.path}`, end: true}, { path: `/${route.path}`, end: true },
pathname pathname
); );
@@ -63,7 +63,7 @@ function MainLayout() {
if (pathname.startsWith(pattern)) { if (pathname.startsWith(pattern)) {
const match = matchPath( const match = matchPath(
{path: `/${route.path}`, end: false}, { path: `/${route.path}`, end: false },
pathname pathname
); );
@@ -141,29 +141,24 @@ function MainLayout() {
{/* 主要内容区 */} {/* 主要内容区 */}
<Content style={{ <Content style={{
margin: isMobile ? '10px' : '20px', padding: isMobile ? '10px' : '20px',
background: '#fcfcfc', background: colorBgContainer,
position: 'relative', position: 'relative',
borderRadius: isMobile ? 10 : 20,
overflowY: 'auto' overflowY: 'auto'
}}> }}>
<div style={{ <div style={{
padding: isMobile ? '15px' : '25px',
minHeight: '100%', minHeight: '100%',
background: colorBgContainer,
boxShadow: '0 6px 30px rgba(0,0,0,0.03)',
border: '1px solid #f0f0f0',
position: 'relative', position: 'relative',
overflow: 'hidden' overflow: 'hidden'
}}> }}>
{/* 渲染子路由组件 */} {/* 渲染子路由组件 */}
<Outlet context={{ <Outlet context={{
isMobile isMobile
}}/> }} />
</div> </div>
</Content> </Content>
{/* 页脚组件 */} {/* 页脚组件 */}
<Footer isMobile={isMobile}/> <Footer isMobile={isMobile} />
</Layout> </Layout>
</Layout> </Layout>
); );
+3 -3
View File
@@ -12,15 +12,15 @@ const Footer: React.FC<FooterProps> = ({ isMobile = false }) => {
return ( return (
<AntFooter style={{ <AntFooter style={{
background: 'white', background: 'white',
padding: isMobile ? '10px' : '10px', padding: '3px',
fontSize: isMobile ? '12px' : '12px', fontSize: isMobile ? '12px' : '12px',
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center' alignItems: 'center'
}}> }}>
<div>Foxel ©{new Date().getFullYear()}</div> <div>Foxel ©{new Date().getFullYear()}</div>
<a <a
href="https://github.com/DrizzleTime/Foxel" href="https://github.com/DrizzleTime/Foxel"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ fontSize: isMobile ? '16px' : '18px', color: '#333' }} style={{ fontSize: isMobile ? '16px' : '18px', color: '#333' }}
+102 -123
View File
@@ -6,19 +6,19 @@ import {
UserOutlined, UserOutlined,
LogoutOutlined, LogoutOutlined,
DashboardOutlined, DashboardOutlined,
HomeOutlined,
RightOutlined, RightOutlined,
SearchOutlined SearchOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Link, useNavigate } from 'react-router'; import { Link, useNavigate, useLocation } from 'react-router';
import { useAuth } from '../../auth/AuthContext'; import { useAuth } from '../../auth/AuthContext';
import { type RouteConfig } from '../../routes'; import { getMainRoutes, getAdminRoutes, type RouteConfig } from '../../routes';
import UserAvatar from '../../components/UserAvatar'; import UserAvatar from '../../components/UserAvatar';
import { UserRole } from '../../api/types'; import { UserRole } from '../../api/types';
import SearchDialog from '../../components/search/SearchDialog'; import SearchDialog from '../../components/search/SearchDialog';
import useIsMobile from '../../hooks/useIsMobile'; import useIsMobile from '../../hooks/useIsMobile';
const { Header: AntHeader } = Layout; const { Header: AntHeader } = Layout;
interface HeaderProps { interface HeaderProps {
collapsed: boolean; collapsed: boolean;
toggleCollapsed: () => void; toggleCollapsed: () => void;
@@ -31,13 +31,6 @@ interface HeaderProps {
isMobile?: boolean; isMobile?: boolean;
} }
// 面包屑项目类型定义
interface BreadcrumbItem {
title: string;
href?: string;
icon?: React.ReactNode;
}
const Header: React.FC<HeaderProps> = ({ const Header: React.FC<HeaderProps> = ({
collapsed, collapsed,
toggleCollapsed, toggleCollapsed,
@@ -45,21 +38,16 @@ const Header: React.FC<HeaderProps> = ({
currentRouteData, currentRouteData,
isMobile = false isMobile = false
}) => { }) => {
const { user } = useAuth(); const { user, hasRole } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const headerRef = useRef<HTMLDivElement>(null); const headerRef = useRef<HTMLDivElement>(null);
const { hasRole } = useAuth();
const isMobileDevice = useIsMobile(); const isMobileDevice = useIsMobile();
// 添加搜索对话框状态
const [searchDialogVisible, setSearchDialogVisible] = useState(false); const [searchDialogVisible, setSearchDialogVisible] = useState(false);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const { const { token: { colorBgContainer } } = theme.useToken();
token: { colorBgContainer },
} = theme.useToken();
// 用户菜单项
const userMenuItems = [ const userMenuItems = [
{ {
key: 'profile', key: 'profile',
@@ -72,7 +60,7 @@ const Header: React.FC<HeaderProps> = ({
key: 'admin', key: 'admin',
icon: <DashboardOutlined />, icon: <DashboardOutlined />,
label: '后台管理', label: '后台管理',
onClick: () => navigate('/admin') onClick: () => navigate('/admin/dashboard')
} }
] : []), ] : []),
{ {
@@ -83,128 +71,124 @@ const Header: React.FC<HeaderProps> = ({
} }
]; ];
// 根据路由信息生成面包屑导航
const renderBreadcrumb = () => { const renderBreadcrumb = () => {
// 如果有传入的标题,直接使用标题作为面包屑 const { routeInfo, params, title: explicitTitle } = currentRouteData;
if (currentRouteData.title) { const antdBreadcrumbItems: Array<{ title: React.ReactNode; href?: string }> = [];
return (
<Breadcrumb const currentPath = location.pathname;
separator={<RightOutlined style={{ fontSize: 12 }} />} const isAdminArea = routeInfo?.area === 'admin';
style={{ margin: 0 }} const baseHref = isAdminArea ? '/admin' : '/';
items={[ const baseTitle = isAdminArea ? '管理后台' : '首页';
{
title: '首页', if (currentPath === baseHref && !explicitTitle && (!routeInfo || routeInfo.path === '')) {
href: '/', antdBreadcrumbItems.push({ title: baseTitle });
}, } else {
{ antdBreadcrumbItems.push({ title: <Link to={baseHref}>{baseTitle}</Link> });
title: currentRouteData.title
}
]}
/>
);
} }
// 如果没有路由信息,返回首页面包屑 if (explicitTitle) {
if (!currentRouteData.routeInfo) { if (!(antdBreadcrumbItems.length === 1 && !antdBreadcrumbItems[0].href && antdBreadcrumbItems[0].title === explicitTitle)) {
return ( if (antdBreadcrumbItems.length === 1 && antdBreadcrumbItems[0].href && antdBreadcrumbItems[0].title !== explicitTitle) {
<Breadcrumb antdBreadcrumbItems.push({ title: explicitTitle });
separator={<RightOutlined style={{ fontSize: 12 }} />} } else if (antdBreadcrumbItems.length === 0 || antdBreadcrumbItems[0].title !== explicitTitle) {
style={{ margin: 0 }} antdBreadcrumbItems.push({ title: explicitTitle });
items={[ } else if (antdBreadcrumbItems.length === 1 && !antdBreadcrumbItems[0].href && antdBreadcrumbItems[0].title !== explicitTitle) {
{ antdBreadcrumbItems[0].title = <Link to={baseHref}>{baseTitle}</Link>;
title: '首页', antdBreadcrumbItems.push({ title: explicitTitle });
href: '/', }
}
]}
/>
);
}
// 获取当前路由信息
const { routeInfo, params } = currentRouteData;
const breadcrumb = routeInfo.breadcrumb;
if (!breadcrumb) {
return (
<Breadcrumb
separator={<RightOutlined style={{ fontSize: 12 }} />}
style={{ margin: 0 }}
items={[
{
title: '首页',
href: '/',
},
{
title: routeInfo.label
}
]}
/>
);
}
// 准备面包屑项目
const breadcrumbItems: BreadcrumbItem[] = [
{
title: routeInfo.area === 'admin' ? '管理后台' : '首页',
href: routeInfo.area === 'admin' ? '/admin' : '/',
icon: routeInfo.area === 'admin' ? <DashboardOutlined /> : <HomeOutlined />
} }
]; } else if (routeInfo) {
const allRoutesForArea = isAdminArea ? getAdminRoutes() : getMainRoutes();
const { breadcrumb: breadcrumbConfig, label: routeLabel, path: routeConfigPath } = routeInfo;
// 如果有父级,添加父级面包屑 if (breadcrumbConfig?.parent) {
if (breadcrumb.parent) { const parentRoute = allRoutesForArea.find(r => r.key === breadcrumbConfig.parent);
const parentPath = routeInfo.area === 'admin' if (parentRoute) {
? `/admin/${breadcrumb.parent}` const parentTitle = parentRoute.breadcrumb?.title || parentRoute.label;
: `/${breadcrumb.parent}`; let parentHref: string;
if (isAdminArea) {
parentHref = parentRoute.path ? `/admin/${parentRoute.path}` : '/admin';
} else {
if (parentRoute.path === '') {
parentHref = '/';
} else if (parentRoute.path.startsWith('/')) {
parentHref = parentRoute.path;
} else {
parentHref = `/${parentRoute.path}`;
}
}
breadcrumbItems.push({ if (parentHref !== baseHref) {
title: breadcrumb.parent.charAt(0).toUpperCase() + breadcrumb.parent.slice(1), if (currentPath === parentHref) {
href: parentPath antdBreadcrumbItems.push({ title: parentTitle });
}); } else {
antdBreadcrumbItems.push({ title: <Link to={parentHref}>{parentTitle}</Link> });
}
} else if (antdBreadcrumbItems.length > 0 && antdBreadcrumbItems[0].title !== parentTitle && currentPath !== baseHref) {
antdBreadcrumbItems[0].title = <Link to={baseHref}>{parentTitle}</Link>;
} else if (antdBreadcrumbItems.length > 0 && antdBreadcrumbItems[0].title !== parentTitle && currentPath === baseHref) {
antdBreadcrumbItems[0].title = parentTitle;
}
}
}
let currentPageTitle = breadcrumbConfig?.title || routeLabel;
if (breadcrumbConfig?.title && params) {
Object.entries(params).forEach(([key, value]) => {
currentPageTitle = currentPageTitle.replace(`:${key}`, String(value));
});
}
const lastItem = antdBreadcrumbItems.length > 0 ? antdBreadcrumbItems[antdBreadcrumbItems.length - 1] : null;
const lastItemTitle = lastItem ? ((lastItem.title as any)?.props?.children || lastItem.title) : null;
if (lastItemTitle !== currentPageTitle || lastItem?.href) {
if (!(routeConfigPath === '' && antdBreadcrumbItems.length === 1 && !antdBreadcrumbItems[0].href && lastItemTitle === currentPageTitle)) {
antdBreadcrumbItems.push({ title: currentPageTitle });
} else if (routeConfigPath === '' && antdBreadcrumbItems.length === 1 && !antdBreadcrumbItems[0].href && lastItemTitle !== currentPageTitle) {
antdBreadcrumbItems[0].title = currentPageTitle;
}
}
} }
const uniqueItems = antdBreadcrumbItems.reduce((acc, item) => {
if (acc.length === 0) {
acc.push(item);
} else {
const prevItem = acc[acc.length - 1];
const prevTitleContent = (prevItem.title as any)?.props?.children ?? prevItem.title;
const currentTitleContent = (item.title as any)?.props?.children ?? item.title;
// 获取动态标题 if (prevTitleContent !== currentTitleContent) {
let title = breadcrumb.title; acc.push(item);
if (params && Object.keys(params).length > 0) { } else {
// 用参数替换标题中的占位符,如 ":id" if (!item.href) {
Object.entries(params).forEach(([key, value]) => { acc[acc.length - 1] = item;
title = title.replace(`:${key}`, value); }
}); }
} }
return acc;
// 添加当前页面面包屑 }, [] as Array<{ title: React.ReactNode; href?: string }>);
breadcrumbItems.push({
title: title
});
return ( return (
<Breadcrumb <Breadcrumb
separator={<RightOutlined style={{ fontSize: 12 }} />} separator={<RightOutlined style={{ fontSize: 12 }} />}
style={{ margin: 0 }} style={{ margin: 0 }}
items={breadcrumbItems.map(item => ({ items={uniqueItems.map(item => ({
title: item.href ? ( title: item.title,
<Link to={item.href} style={{ color: '#666', fontSize: isMobile ? 13 : 14 }}> href: item.href,
{item.icon && <span style={{ marginRight: 4 }}>{item.icon}</span>}
{isMobile && !item.icon ? '' : item.title}
</Link>
) : (
<span style={{ fontSize: isMobile ? 14 : 16, fontWeight: 500 }}>
{item.icon && <span style={{ marginRight: 4 }}>{item.icon}</span>}
{item.title}
</span>
),
}))} }))}
/> />
); );
}; };
// 处理搜索
const handleSearch = (value: string) => { const handleSearch = (value: string) => {
setSearchText(value); setSearchText(value);
setSearchDialogVisible(true); setSearchDialogVisible(true);
}; };
// 关闭搜索对话框
const handleSearchDialogClose = () => { const handleSearchDialogClose = () => {
setSearchDialogVisible(false); setSearchDialogVisible(false);
}; };
@@ -225,7 +209,6 @@ const Header: React.FC<HeaderProps> = ({
top: 0 top: 0
}} }}
> >
{/* 左侧区域:折叠按钮和面包屑 */}
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<Button <Button
type="text" type="text"
@@ -241,9 +224,7 @@ const Header: React.FC<HeaderProps> = ({
{!isMobileDevice && renderBreadcrumb()} {!isMobileDevice && renderBreadcrumb()}
</div> </div>
{/* 右侧区域:搜索框和用户菜单 */}
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
{/* 搜索框 */}
<div style={{ <div style={{
marginRight: 16, marginRight: 16,
display: 'flex', display: 'flex',
@@ -264,7 +245,6 @@ const Header: React.FC<HeaderProps> = ({
/> />
</div> </div>
{/* 用户菜单 */}
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight"> <Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
<Space style={{ cursor: 'pointer' }}> <Space style={{ cursor: 'pointer' }}>
<UserAvatar <UserAvatar
@@ -276,7 +256,6 @@ const Header: React.FC<HeaderProps> = ({
</Dropdown> </Dropdown>
</div> </div>
{/* 搜索对话框 */}
<SearchDialog <SearchDialog
visible={searchDialogVisible} visible={searchDialogVisible}
onClose={handleSearchDialogClose} onClose={handleSearchDialogClose}
+10 -3
View File
@@ -79,7 +79,7 @@ const Sidebar: React.FC<SidebarProps> = ({ collapsed, isMobile = false, onClose,
// 管理后台路径处理 // 管理后台路径处理
if (area === 'admin') { if (area === 'admin') {
// 提取 /admin/ 后面的部分 // 提取 /admin/ 后面的部分
const adminPath = pathname.replace(/^\/admin\/?/, ''); let adminPath = pathname.replace(/^\/admin\/?/, '');
// 如果是管理后台首页 // 如果是管理后台首页
if (adminPath === '') { if (adminPath === '') {
@@ -87,18 +87,25 @@ const Sidebar: React.FC<SidebarProps> = ({ collapsed, isMobile = false, onClose,
return defaultRoute ? defaultRoute.path : ''; return defaultRoute ? defaultRoute.path : '';
} }
// 先尝试精确匹配
const exactMatch = routes.find(route => route.path === adminPath);
if (exactMatch) {
return exactMatch.path;
}
// 再尝试参数路由匹配
const matchedRoute = routes.find(route => { const matchedRoute = routes.find(route => {
if (route.path.includes(':')) { if (route.path.includes(':')) {
const basePath = route.path.split(':')[0].replace(/\/$/, ''); const basePath = route.path.split(':')[0].replace(/\/$/, '');
return adminPath.startsWith(basePath); return adminPath.startsWith(basePath);
} }
return adminPath === route.path; return false;
}); });
return matchedRoute ? matchedRoute.path : ''; return matchedRoute ? matchedRoute.path : '';
} }
// 主应用路径处理 // 主应用路径处理保持不变
const matchedRoute = routes.find(route => { const matchedRoute = routes.find(route => {
if (route.path.includes(':')) { if (route.path.includes(':')) {
const basePath = route.path.split(':')[0].replace(/\/$/, ''); const basePath = route.path.split(':')[0].replace(/\/$/, '');
+1 -1
View File
@@ -12,7 +12,7 @@ function AllImages() {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [, setImages] = useState<PictureResponse[]>([]); const [, setImages] = useState<PictureResponse[]>([]);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20); const [pageSize, setPageSize] = useState(50);
const [sortBy, setSortBy] = useState<string>('uploadDate_desc'); const [sortBy, setSortBy] = useState<string>('uploadDate_desc');
const [isUploadDialogVisible, setIsUploadDialogVisible] = useState(false); const [isUploadDialogVisible, setIsUploadDialogVisible] = useState(false);
const [refreshTrigger, setRefreshTrigger] = useState(0); const [refreshTrigger, setRefreshTrigger] = useState(0);
+1 -1
View File
@@ -157,7 +157,7 @@ const routes: RouteConfig[] = [
hideInMenu: true, hideInMenu: true,
breadcrumb: { breadcrumb: {
title: '用户详情', title: '用户详情',
parent: 'users' parent: 'admin-user' // 修改: 指向父路由的 key
} }
}, },
{ {