From 5b2c2d35bcda825bf620a10216f442ea961c2d4d Mon Sep 17 00:00:00 2001 From: Stev_Wang <304865932@qq.com> Date: Fri, 12 Dec 2025 21:22:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E5=BC=8F=E5=AF=BC=E8=88=AA=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E4=BA=9B=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/TabNavigation.tsx | 222 ++++++++++++++++++++++------------ 1 file changed, 142 insertions(+), 80 deletions(-) diff --git a/src/layouts/TabNavigation.tsx b/src/layouts/TabNavigation.tsx index cc3cd47..78a7371 100644 --- a/src/layouts/TabNavigation.tsx +++ b/src/layouts/TabNavigation.tsx @@ -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 = { - '/admin/dashboard': { label: '工作台', icon: , closable: false }, - '/admin/system/users': { label: '用户管理', icon: , closable: true }, - '/admin/system/roles': { label: '角色管理', icon: , closable: true }, - '/admin/system/permissions': { label: '权限管理', icon: , closable: true }, - '/admin/system/config': { label: '系统配置', icon: , closable: true }, - '/admin/game/servers': { label: '服务器管理', icon: , closable: true }, - '/admin/game/goods': { label: '道具管理', icon: , closable: true }, - '/admin/game/announcement': { label: '公告管理', icon: , closable: true }, - '/admin/finance/recharge': { label: '充值记录', icon: , closable: true }, - '/admin/finance/order': { label: '订单管理', icon: , closable: true }, - '/admin/report/user': { label: '用户统计', icon: , closable: true }, - '/admin/report/finance': { label: '财务统计', icon: , closable: true }, - '/admin/report/game': { label: '游戏统计', icon: , closable: true }, - '/admin/profile': { label: '个人资料', icon: , closable: true }, - '/admin/settings': { label: '系统设置', icon: , closable: true } - }; + interface RouteConfig { + [path: string]: { + label: string; + icon: React.ReactNode; + closable: boolean; + }; + } /** - * 初始化和更新标签页 + * 路由到标签配置的映射 + * 只包含实际存在的路由,避免404错误 + * 使用useMemo避免每次渲染都重新创建对象 */ - useEffect(() => { + const routeConfig: RouteConfig = useMemo(() => ({ + '/admin/dashboard': { label: '工作台', icon: , closable: false }, + '/admin/system/config': { label: '系统配置', icon: , 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]); /** * 更多操作下拉菜单