fix: 🐛 修复标签式导航存在的一些问题
This commit is contained in:
@@ -1,23 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Tabs, Button, Space, Dropdown, Typography, theme } from 'antd';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { Tabs, Button, Space, Dropdown, theme } from 'antd';
|
||||
import type { TabsProps } from 'antd';
|
||||
import {
|
||||
MoreOutlined,
|
||||
DashboardOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined,
|
||||
TeamOutlined,
|
||||
SafetyOutlined,
|
||||
ToolOutlined,
|
||||
PlayCircleOutlined,
|
||||
DollarOutlined,
|
||||
BellOutlined,
|
||||
MobileOutlined,
|
||||
FileTextOutlined
|
||||
ToolOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
const { Text } = Typography;
|
||||
// const { Text } = Typography;
|
||||
|
||||
/**
|
||||
* 标签页项接口
|
||||
@@ -42,65 +33,104 @@ const TabNavigation: React.FC = () => {
|
||||
const { token } = theme.useToken();
|
||||
|
||||
/**
|
||||
* 路由到标签配置的映射
|
||||
* 路由配置的类型定义
|
||||
*/
|
||||
const routeConfig: Record<string, { label: string; icon: React.ReactNode; closable: boolean }> = {
|
||||
'/admin/dashboard': { label: '工作台', icon: <DashboardOutlined />, closable: false },
|
||||
'/admin/system/users': { label: '用户管理', icon: <TeamOutlined />, closable: true },
|
||||
'/admin/system/roles': { label: '角色管理', icon: <SafetyOutlined />, closable: true },
|
||||
'/admin/system/permissions': { label: '权限管理', icon: <UserOutlined />, closable: true },
|
||||
'/admin/system/config': { label: '系统配置', icon: <ToolOutlined />, closable: true },
|
||||
'/admin/game/servers': { label: '服务器管理', icon: <MobileOutlined />, closable: true },
|
||||
'/admin/game/goods': { label: '道具管理', icon: <DollarOutlined />, closable: true },
|
||||
'/admin/game/announcement': { label: '公告管理', icon: <BellOutlined />, closable: true },
|
||||
'/admin/finance/recharge': { label: '充值记录', icon: <DollarOutlined />, closable: true },
|
||||
'/admin/finance/order': { label: '订单管理', icon: <FileTextOutlined />, closable: true },
|
||||
'/admin/report/user': { label: '用户统计', icon: <TeamOutlined />, closable: true },
|
||||
'/admin/report/finance': { label: '财务统计', icon: <DollarOutlined />, closable: true },
|
||||
'/admin/report/game': { label: '游戏统计', icon: <PlayCircleOutlined />, closable: true },
|
||||
'/admin/profile': { label: '个人资料', icon: <UserOutlined />, closable: true },
|
||||
'/admin/settings': { label: '系统设置', icon: <SettingOutlined />, closable: true }
|
||||
};
|
||||
interface RouteConfig {
|
||||
[path: string]: {
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
closable: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化和更新标签页
|
||||
* 路由到标签配置的映射
|
||||
* 只包含实际存在的路由,避免404错误
|
||||
* 使用useMemo避免每次渲染都重新创建对象
|
||||
*/
|
||||
useEffect(() => {
|
||||
const routeConfig: RouteConfig = useMemo(() => ({
|
||||
'/admin/dashboard': { label: '工作台', icon: <DashboardOutlined />, closable: false },
|
||||
'/admin/system/config': { label: '系统配置', icon: <ToolOutlined />, closable: true }
|
||||
}), []);
|
||||
|
||||
/**
|
||||
* 计算当前应该显示的标签项
|
||||
* 基于当前路径和现有标签计算新标签列表
|
||||
* 修复React Compiler警告:添加tabItems依赖项
|
||||
*/
|
||||
const computedTabItems = useMemo(() => {
|
||||
const currentPath = location.pathname;
|
||||
const config = routeConfig[currentPath];
|
||||
|
||||
if (config) {
|
||||
setTabItems(prevItems => {
|
||||
// 检查是否已存在该标签
|
||||
const existingIndex = prevItems.findIndex(item => item.key === currentPath);
|
||||
// 创建工作台标签(始终存在)
|
||||
const dashboardItem: TabItem = {
|
||||
key: '/admin/dashboard',
|
||||
label: routeConfig['/admin/dashboard'].label,
|
||||
icon: routeConfig['/admin/dashboard'].icon,
|
||||
closable: routeConfig['/admin/dashboard'].closable
|
||||
};
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// 标签已存在,只更新激活状态
|
||||
const newItems = [...prevItems];
|
||||
setActiveKey(currentPath);
|
||||
return newItems;
|
||||
} else {
|
||||
// 添加新标签
|
||||
const newItem: TabItem = {
|
||||
key: currentPath,
|
||||
label: config.label,
|
||||
icon: config.icon,
|
||||
closable: config.closable
|
||||
};
|
||||
|
||||
const newItems = [...prevItems, newItem];
|
||||
setActiveKey(currentPath);
|
||||
return newItems;
|
||||
}
|
||||
});
|
||||
// 特殊处理:工作台页面始终只显示工作台标签
|
||||
if (currentPath === '/admin/dashboard') {
|
||||
return [dashboardItem];
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
if (config) {
|
||||
// 检查当前路径是否已存在
|
||||
const existingItem = tabItems.find(item => item.key === currentPath);
|
||||
|
||||
if (existingItem) {
|
||||
// 标签已存在,确保工作台在第一位,其他标签保持顺序
|
||||
const otherItems = tabItems.filter(item =>
|
||||
item.key !== '/admin/dashboard' &&
|
||||
item.key !== currentPath &&
|
||||
routeConfig[item.key] // 确保是配置的路由
|
||||
);
|
||||
return [dashboardItem, ...otherItems, existingItem];
|
||||
} else {
|
||||
// 添加新标签,确保工作台在第一位
|
||||
const otherItems = tabItems.filter(item =>
|
||||
item.key !== '/admin/dashboard' &&
|
||||
item.key !== currentPath &&
|
||||
routeConfig[item.key] // 确保是配置的路由
|
||||
);
|
||||
const currentItem: TabItem = {
|
||||
key: currentPath,
|
||||
label: config.label,
|
||||
icon: config.icon,
|
||||
closable: config.closable
|
||||
};
|
||||
return [dashboardItem, currentItem, ...otherItems];
|
||||
}
|
||||
} else {
|
||||
// 当前路径不在路由配置中,只显示工作台标签
|
||||
return [dashboardItem];
|
||||
}
|
||||
}, [location.pathname, routeConfig, tabItems]);
|
||||
|
||||
/**
|
||||
* 当computedTabItems变化时更新状态
|
||||
* 使用setTimeout确保在渲染完成后执行
|
||||
*/
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
setTabItems(computedTabItems);
|
||||
setActiveKey(location.pathname);
|
||||
}, 0);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [computedTabItems, location.pathname]);
|
||||
|
||||
/**
|
||||
* 关闭标签页
|
||||
* @param targetKey - 要关闭的标签key
|
||||
*/
|
||||
const handleClose = (targetKey: string) => {
|
||||
const handleClose = useCallback((targetKey: string) => {
|
||||
// 工作台标签不能关闭
|
||||
if (targetKey === '/admin/dashboard') {
|
||||
return;
|
||||
}
|
||||
|
||||
setTabItems(prevItems => {
|
||||
const itemIndex = prevItems.findIndex(item => item.key === targetKey);
|
||||
if (itemIndex === -1) return prevItems;
|
||||
@@ -114,30 +144,45 @@ const TabNavigation: React.FC = () => {
|
||||
const newActiveIndex = itemIndex < newItems.length ? itemIndex : itemIndex - 1;
|
||||
const newActiveKey = newItems[newActiveIndex]?.key;
|
||||
if (newActiveKey) {
|
||||
setActiveKey(newActiveKey);
|
||||
navigate(newActiveKey);
|
||||
// 使用setTimeout确保在渲染完成后执行导航
|
||||
setTimeout(() => {
|
||||
setActiveKey(newActiveKey);
|
||||
navigate(newActiveKey);
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
// 没有标签了,返回工作台
|
||||
setActiveKey('');
|
||||
navigate('/admin/dashboard');
|
||||
const dashboardItem = prevItems.find(item => item.key === '/admin/dashboard');
|
||||
if (dashboardItem) {
|
||||
setTimeout(() => {
|
||||
setActiveKey(dashboardItem.key);
|
||||
navigate(dashboardItem.key);
|
||||
}, 0);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setActiveKey('/admin/dashboard');
|
||||
navigate('/admin/dashboard');
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newItems;
|
||||
});
|
||||
};
|
||||
}, [activeKey, navigate]);
|
||||
|
||||
/**
|
||||
* 处理标签点击
|
||||
* @param key - 标签key
|
||||
*/
|
||||
const handleTabClick = (key: string) => {
|
||||
const handleTabClick = useCallback((key: string) => {
|
||||
if (key !== activeKey) {
|
||||
setActiveKey(key);
|
||||
navigate(key);
|
||||
setTimeout(() => {
|
||||
setActiveKey(key);
|
||||
navigate(key);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
}, [activeKey, navigate]);
|
||||
|
||||
/**
|
||||
* 获取所有标签项的TabsProps配置
|
||||
@@ -161,28 +206,45 @@ const TabNavigation: React.FC = () => {
|
||||
/**
|
||||
* 关闭其他标签
|
||||
*/
|
||||
const closeOthers = () => {
|
||||
const closeOthers = useCallback(() => {
|
||||
const currentItem = tabItems.find(item => item.key === activeKey);
|
||||
if (currentItem && currentItem.closable !== false) {
|
||||
setTabItems([currentItem]);
|
||||
// 确保工作台始终保留
|
||||
const dashboardItem = tabItems.find(item => item.key === '/admin/dashboard');
|
||||
if (dashboardItem && currentItem.key !== '/admin/dashboard') {
|
||||
setTabItems([dashboardItem, currentItem]);
|
||||
} else {
|
||||
setTabItems([currentItem]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [activeKey, tabItems]);
|
||||
|
||||
/**
|
||||
* 关闭所有标签
|
||||
*/
|
||||
const closeAll = () => {
|
||||
const defaultItem = tabItems.find(item => !item.closable);
|
||||
if (defaultItem) {
|
||||
setTabItems([defaultItem]);
|
||||
setActiveKey(defaultItem.key);
|
||||
navigate(defaultItem.key);
|
||||
const closeAll = useCallback(() => {
|
||||
const dashboardItem = tabItems.find(item => item.key === '/admin/dashboard');
|
||||
if (dashboardItem) {
|
||||
setTabItems([dashboardItem]);
|
||||
setTimeout(() => {
|
||||
setActiveKey(dashboardItem.key);
|
||||
navigate(dashboardItem.key);
|
||||
}, 0);
|
||||
} else {
|
||||
setTabItems([]);
|
||||
setActiveKey('');
|
||||
navigate('/admin/dashboard');
|
||||
// 如果没有工作台,创建一个
|
||||
const newDashboardItem: TabItem = {
|
||||
key: '/admin/dashboard',
|
||||
label: routeConfig['/admin/dashboard'].label,
|
||||
icon: routeConfig['/admin/dashboard'].icon,
|
||||
closable: routeConfig['/admin/dashboard'].closable
|
||||
};
|
||||
setTabItems([newDashboardItem]);
|
||||
setTimeout(() => {
|
||||
setActiveKey('/admin/dashboard');
|
||||
navigate('/admin/dashboard');
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
}, [navigate, tabItems, routeConfig]);
|
||||
|
||||
/**
|
||||
* 更多操作下拉菜单
|
||||
|
||||
Reference in New Issue
Block a user