fix: 🐛 修复标签式导航存在的一些问题
This commit is contained in:
@@ -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];
|
||||||
|
|
||||||
|
// 创建工作台标签(始终存在)
|
||||||
|
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) {
|
if (config) {
|
||||||
setTabItems(prevItems => {
|
// 检查当前路径是否已存在
|
||||||
// 检查是否已存在该标签
|
const existingItem = tabItems.find(item => item.key === currentPath);
|
||||||
const existingIndex = prevItems.findIndex(item => item.key === currentPath);
|
|
||||||
|
if (existingItem) {
|
||||||
if (existingIndex >= 0) {
|
// 标签已存在,确保工作台在第一位,其他标签保持顺序
|
||||||
// 标签已存在,只更新激活状态
|
const otherItems = tabItems.filter(item =>
|
||||||
const newItems = [...prevItems];
|
item.key !== '/admin/dashboard' &&
|
||||||
setActiveKey(currentPath);
|
item.key !== currentPath &&
|
||||||
return newItems;
|
routeConfig[item.key] // 确保是配置的路由
|
||||||
} else {
|
);
|
||||||
// 添加新标签
|
return [dashboardItem, ...otherItems, existingItem];
|
||||||
const newItem: TabItem = {
|
} else {
|
||||||
key: currentPath,
|
// 添加新标签,确保工作台在第一位
|
||||||
label: config.label,
|
const otherItems = tabItems.filter(item =>
|
||||||
icon: config.icon,
|
item.key !== '/admin/dashboard' &&
|
||||||
closable: config.closable
|
item.key !== currentPath &&
|
||||||
};
|
routeConfig[item.key] // 确保是配置的路由
|
||||||
|
);
|
||||||
const newItems = [...prevItems, newItem];
|
const currentItem: TabItem = {
|
||||||
setActiveKey(currentPath);
|
key: currentPath,
|
||||||
return newItems;
|
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
|
* @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) {
|
||||||
setActiveKey(newActiveKey);
|
// 使用setTimeout确保在渲染完成后执行导航
|
||||||
navigate(newActiveKey);
|
setTimeout(() => {
|
||||||
|
setActiveKey(newActiveKey);
|
||||||
|
navigate(newActiveKey);
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有标签了,返回工作台
|
// 没有标签了,返回工作台
|
||||||
setActiveKey('');
|
const dashboardItem = prevItems.find(item => item.key === '/admin/dashboard');
|
||||||
navigate('/admin/dashboard');
|
if (dashboardItem) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveKey(dashboardItem.key);
|
||||||
|
navigate(dashboardItem.key);
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveKey('/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) {
|
||||||
setActiveKey(key);
|
setTimeout(() => {
|
||||||
navigate(key);
|
setActiveKey(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) {
|
||||||
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 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]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更多操作下拉菜单
|
* 更多操作下拉菜单
|
||||||
|
|||||||
Reference in New Issue
Block a user