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 React, { useState, useEffect, useCallback, useMemo } from 'react';
import { Tabs, Button, Space, Dropdown, Typography, theme } from 'antd'; import { Tabs, Button, Space, Dropdown, theme } from 'antd';
import type { TabsProps } from 'antd'; import type { TabsProps } from 'antd';
import { import {
MoreOutlined, MoreOutlined,
DashboardOutlined, DashboardOutlined,
UserOutlined, ToolOutlined
SettingOutlined,
TeamOutlined,
SafetyOutlined,
ToolOutlined,
PlayCircleOutlined,
DollarOutlined,
BellOutlined,
MobileOutlined,
FileTextOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useNavigate, useLocation } from 'react-router-dom'; 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 { token } = theme.useToken();
/** /**
* 路由到标签配置的映射 * 路由配置的类型定义
*/ */
const routeConfig: Record<string, { label: string; icon: React.ReactNode; closable: boolean }> = { interface RouteConfig {
'/admin/dashboard': { label: '工作台', icon: <DashboardOutlined />, closable: false }, [path: string]: {
'/admin/system/users': { label: '用户管理', icon: <TeamOutlined />, closable: true }, label: string;
'/admin/system/roles': { label: '角色管理', icon: <SafetyOutlined />, closable: true }, icon: React.ReactNode;
'/admin/system/permissions': { label: '权限管理', icon: <UserOutlined />, closable: true }, closable: boolean;
'/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 }
}; };
}
/** /**
* 初始化和更新标签页 * 路由到标签配置的映射
* 只包含实际存在的路由避免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 currentPath = location.pathname;
const config = routeConfig[currentPath]; const config = routeConfig[currentPath];
if (config) { // 创建工作台标签(始终存在)
setTabItems(prevItems => { const dashboardItem: TabItem = {
// 检查是否已存在该标签 key: '/admin/dashboard',
const existingIndex = prevItems.findIndex(item => item.key === currentPath); label: routeConfig['/admin/dashboard'].label,
icon: routeConfig['/admin/dashboard'].icon,
closable: routeConfig['/admin/dashboard'].closable
};
if (existingIndex >= 0) { // 特殊处理:工作台页面始终只显示工作台标签
// 标签已存在,只更新激活状态 if (currentPath === '/admin/dashboard') {
const newItems = [...prevItems]; return [dashboardItem];
setActiveKey(currentPath); }
return newItems;
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 { } else {
// 添加新标签 // 添加新标签,确保工作台在第一位
const newItem: TabItem = { const otherItems = tabItems.filter(item =>
item.key !== '/admin/dashboard' &&
item.key !== currentPath &&
routeConfig[item.key] // 确保是配置的路由
);
const currentItem: TabItem = {
key: currentPath, key: currentPath,
label: config.label, label: config.label,
icon: config.icon, icon: config.icon,
closable: config.closable closable: config.closable
}; };
return [dashboardItem, currentItem, ...otherItems];
}
} else {
// 当前路径不在路由配置中,只显示工作台标签
return [dashboardItem];
}
}, [location.pathname, routeConfig, tabItems]);
const newItems = [...prevItems, newItem]; /**
setActiveKey(currentPath); * 当computedTabItems变化时更新状态
return newItems; * 使用setTimeout确保在渲染完成后执行
} */
}); useEffect(() => {
} const timeoutId = setTimeout(() => {
}, [location.pathname]); setTabItems(computedTabItems);
setActiveKey(location.pathname);
}, 0);
return () => clearTimeout(timeoutId);
}, [computedTabItems, location.pathname]);
/** /**
* 关闭标签页 * 关闭标签页
* @param targetKey - 要关闭的标签key * @param targetKey - 要关闭的标签key
*/ */
const handleClose = (targetKey: string) => { const handleClose = useCallback((targetKey: string) => {
// 工作台标签不能关闭
if (targetKey === '/admin/dashboard') {
return;
}
setTabItems(prevItems => { setTabItems(prevItems => {
const itemIndex = prevItems.findIndex(item => item.key === targetKey); const itemIndex = prevItems.findIndex(item => item.key === targetKey);
if (itemIndex === -1) return prevItems; if (itemIndex === -1) return prevItems;
@@ -114,30 +144,45 @@ const TabNavigation: React.FC = () => {
const newActiveIndex = itemIndex < newItems.length ? itemIndex : itemIndex - 1; const newActiveIndex = itemIndex < newItems.length ? itemIndex : itemIndex - 1;
const newActiveKey = newItems[newActiveIndex]?.key; const newActiveKey = newItems[newActiveIndex]?.key;
if (newActiveKey) { if (newActiveKey) {
// 使用setTimeout确保在渲染完成后执行导航
setTimeout(() => {
setActiveKey(newActiveKey); setActiveKey(newActiveKey);
navigate(newActiveKey); navigate(newActiveKey);
}, 0);
} }
} else { } else {
// 没有标签了,返回工作台 // 没有标签了,返回工作台
setActiveKey(''); 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'); navigate('/admin/dashboard');
}, 0);
}
} }
} }
return newItems; return newItems;
}); });
}; }, [activeKey, navigate]);
/** /**
* 处理标签点击 * 处理标签点击
* @param key - 标签key * @param key - 标签key
*/ */
const handleTabClick = (key: string) => { const handleTabClick = useCallback((key: string) => {
if (key !== activeKey) { if (key !== activeKey) {
setTimeout(() => {
setActiveKey(key); setActiveKey(key);
navigate(key); navigate(key);
}, 0);
} }
}; }, [activeKey, navigate]);
/** /**
* 获取所有标签项的TabsProps配置 * 获取所有标签项的TabsProps配置
@@ -161,28 +206,45 @@ const TabNavigation: React.FC = () => {
/** /**
* 关闭其他标签 * 关闭其他标签
*/ */
const closeOthers = () => { const closeOthers = useCallback(() => {
const currentItem = tabItems.find(item => item.key === activeKey); const currentItem = tabItems.find(item => item.key === activeKey);
if (currentItem && currentItem.closable !== false) { if (currentItem && currentItem.closable !== false) {
// 确保工作台始终保留
const dashboardItem = tabItems.find(item => item.key === '/admin/dashboard');
if (dashboardItem && currentItem.key !== '/admin/dashboard') {
setTabItems([dashboardItem, currentItem]);
} else {
setTabItems([currentItem]); setTabItems([currentItem]);
} }
}; }
}, [activeKey, tabItems]);
/** /**
* 关闭所有标签 * 关闭所有标签
*/ */
const closeAll = () => { const closeAll = useCallback(() => {
const defaultItem = tabItems.find(item => !item.closable); const dashboardItem = tabItems.find(item => item.key === '/admin/dashboard');
if (defaultItem) { if (dashboardItem) {
setTabItems([defaultItem]); setTabItems([dashboardItem]);
setActiveKey(defaultItem.key); setTimeout(() => {
navigate(defaultItem.key); setActiveKey(dashboardItem.key);
navigate(dashboardItem.key);
}, 0);
} else { } else {
setTabItems([]); // 如果没有工作台,创建一个
setActiveKey(''); const newDashboardItem: TabItem = {
navigate('/admin/dashboard'); 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]);
/** /**
* 更多操作下拉菜单 * 更多操作下拉菜单