kra/web/src/layouts/components/Tabs.tsx

110 lines
3.1 KiB
TypeScript

/**
* KRA - Tabs Component
* Tab-based page management
*/
import React, { useState, useEffect } from 'react';
import { Tabs as AntTabs, Dropdown } from 'antd';
import type { MenuProps } from 'antd';
import { useLocation, history } from '@umijs/max';
import { CloseOutlined, ReloadOutlined, CloseCircleOutlined } from '@ant-design/icons';
interface TabItem {
key: string;
label: string;
closable?: boolean;
}
const Tabs: React.FC = () => {
const location = useLocation();
const [activeKey, setActiveKey] = useState(location.pathname);
const [tabs, setTabs] = useState<TabItem[]>([
{ key: '/dashboard', label: '首页', closable: false },
]);
useEffect(() => {
const path = location.pathname;
setActiveKey(path);
// Add new tab if not exists
if (!tabs.find((tab) => tab.key === path) && path !== '/') {
const title = document.title?.replace(' - Kratos Admin', '') || path;
setTabs((prev) => [...prev, { key: path, label: title, closable: true }]);
}
}, [location.pathname]);
const handleTabChange = (key: string) => {
setActiveKey(key);
history.push(key);
};
const handleTabEdit = (targetKey: string | React.MouseEvent | React.KeyboardEvent, action: 'add' | 'remove') => {
if (action === 'remove' && typeof targetKey === 'string') {
const newTabs = tabs.filter((tab) => tab.key !== targetKey);
setTabs(newTabs);
// Navigate to previous tab if current tab is closed
if (targetKey === activeKey && newTabs.length > 0) {
const lastTab = newTabs[newTabs.length - 1];
setActiveKey(lastTab.key);
history.push(lastTab.key);
}
}
};
const closeOthers = () => {
const newTabs = tabs.filter((tab) => !tab.closable || tab.key === activeKey);
setTabs(newTabs);
};
const closeAll = () => {
const newTabs = tabs.filter((tab) => !tab.closable);
setTabs(newTabs);
if (newTabs.length > 0) {
setActiveKey(newTabs[0].key);
history.push(newTabs[0].key);
}
};
const contextMenuItems: MenuProps['items'] = [
{ key: 'reload', icon: <ReloadOutlined />, label: '刷新当前' },
{ key: 'closeOthers', icon: <CloseCircleOutlined />, label: '关闭其他' },
{ key: 'closeAll', icon: <CloseOutlined />, label: '关闭所有' },
];
const handleContextMenu: MenuProps['onClick'] = ({ key }) => {
switch (key) {
case 'reload':
history.push(activeKey);
break;
case 'closeOthers':
closeOthers();
break;
case 'closeAll':
closeAll();
break;
}
};
return (
<Dropdown menu={{ items: contextMenuItems, onClick: handleContextMenu }} trigger={['contextMenu']}>
<div style={{ padding: '0 16px', background: '#fff', borderBottom: '1px solid #f0f0f0' }}>
<AntTabs
type="editable-card"
hideAdd
activeKey={activeKey}
onChange={handleTabChange}
onEdit={handleTabEdit}
items={tabs.map((tab) => ({
key: tab.key,
label: tab.label,
closable: tab.closable,
}))}
size="small"
/>
</div>
</Dropdown>
);
};
export default Tabs;