fix: 🐛 修复标签式导航存在的一些问题

This commit is contained in:
Stev_Wang
2025-12-12 21:22:23 +08:00
parent c4ec174828
commit 5b2c2d35bc

View File

@@ -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];
// 创建工作台标签(始终存在)
const dashboardItem: TabItem = {
key: '/admin/dashboard',
label: routeConfig['/admin/dashboard'].label,
icon: routeConfig['/admin/dashboard'].icon,
closable: routeConfig['/admin/dashboard'].closable
};
// 特殊处理:工作台页面始终只显示工作台标签
if (currentPath === '/admin/dashboard') {
return [dashboardItem];
}
if (config) {
setTabItems(prevItems => {
// 检查是否已存在该标签
const existingIndex = prevItems.findIndex(item => item.key === currentPath);
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;
}
});
// 检查当前路径是否已存在
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]);
}, [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]);
/**
* 更多操作下拉菜单