Skip to content

Commit aa15bfc

Browse files
committed
feat: 添加全局tab
1 parent 15a5995 commit aa15bfc

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
const GlobalTab = () => {
4+
return <div>GlobalTab</div>;
5+
};
6+
7+
export default GlobalTab;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { Dropdown } from 'antd';
2+
import type { MenuProps } from 'antd';
3+
4+
import { clearLeftTabs, clearRightTabs, clearTabs, removeTab } from '@/store/slice/tab';
5+
6+
interface ContextMenuProps {
7+
active: boolean;
8+
children: React.ReactNode;
9+
darkMode: boolean;
10+
disabledKeys?: App.Global.DropdownKey[];
11+
excludeKeys?: App.Global.DropdownKey[];
12+
i18nKey: App.Global.Tab['i18nKey'];
13+
locale: App.I18n.LangType;
14+
mode: UnionKey.ThemeTabMode;
15+
tabId: string;
16+
}
17+
18+
interface DropdownOption {
19+
disabled?: boolean;
20+
icon: string;
21+
key: App.Global.DropdownKey;
22+
label: string;
23+
}
24+
25+
function getMenu(options: DropdownOption[]) {
26+
const items: MenuProps['items'] = options.map(opt => ({
27+
disabled: opt.disabled,
28+
icon: (
29+
<SvgIcon
30+
className="text-icon"
31+
icon={opt.icon}
32+
/>
33+
),
34+
key: opt.key,
35+
label: opt.label
36+
}));
37+
38+
return items;
39+
}
40+
41+
function arePropsEqual(oldProps: Readonly<ContextMenuProps>, newProps: Readonly<ContextMenuProps>) {
42+
if (oldProps.active !== newProps.active) return false;
43+
if (oldProps.darkMode !== newProps.darkMode) return false;
44+
if (oldProps.i18nKey !== newProps.i18nKey) return false;
45+
if (oldProps.mode !== newProps.mode) return false;
46+
if (oldProps.locale !== newProps.locale) return false;
47+
if (oldProps.tabId !== newProps.tabId) return false;
48+
if (oldProps.excludeKeys?.length !== newProps.excludeKeys?.length) return false;
49+
50+
const result = newProps.disabledKeys?.every((key, index) => key === oldProps.disabledKeys?.[index]);
51+
52+
return result || false;
53+
}
54+
55+
const ContextMenu: FC<ContextMenuProps> = memo(({ children, disabledKeys = [], excludeKeys = [], tabId }) => {
56+
const { t } = useTranslation();
57+
58+
const dispatch = useAppDispatch();
59+
60+
const options = () => {
61+
const opts: DropdownOption[] = [
62+
{
63+
icon: 'ant-design:close-outlined',
64+
key: 'closeCurrent',
65+
label: t('dropdown.closeCurrent')
66+
},
67+
{
68+
icon: 'ant-design:column-width-outlined',
69+
key: 'closeOther',
70+
label: t('dropdown.closeOther')
71+
},
72+
{
73+
icon: 'mdi:format-horizontal-align-left',
74+
key: 'closeLeft',
75+
label: t('dropdown.closeLeft')
76+
},
77+
{
78+
icon: 'mdi:format-horizontal-align-right',
79+
key: 'closeRight',
80+
label: t('dropdown.closeRight')
81+
},
82+
{
83+
icon: 'ant-design:line-outlined',
84+
key: 'closeAll',
85+
label: t('dropdown.closeAll')
86+
}
87+
];
88+
89+
return opts
90+
.filter(opt => !excludeKeys.includes(opt.key))
91+
.map(opt => {
92+
if (disabledKeys.includes(opt.key)) {
93+
opt.disabled = true;
94+
}
95+
return opt;
96+
});
97+
};
98+
99+
const menu = getMenu(options());
100+
101+
const dropdownAction: Record<App.Global.DropdownKey, () => void> = {
102+
closeAll() {
103+
dispatch(clearTabs());
104+
},
105+
closeCurrent() {
106+
dispatch(removeTab(tabId));
107+
},
108+
closeLeft() {
109+
dispatch(clearLeftTabs(tabId));
110+
},
111+
closeOther() {
112+
dispatch(clearTabs([tabId]));
113+
},
114+
closeRight() {
115+
dispatch(clearRightTabs(tabId));
116+
}
117+
};
118+
119+
const handleClick: MenuProps['onClick'] = e => {
120+
dropdownAction[e.key as App.Global.DropdownKey]();
121+
};
122+
123+
return (
124+
<Dropdown
125+
menu={{ items: menu, onClick: handleClick }}
126+
trigger={['contextMenu']}
127+
>
128+
{children}
129+
</Dropdown>
130+
);
131+
}, arePropsEqual);
132+
133+
export default ContextMenu;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Filter tabs by id
3+
*
4+
* @param tabId
5+
* @param tabs
6+
*/
7+
export function filterTabsById(tabId: string, tabs: App.Global.Tab[]) {
8+
return tabs.filter(tab => tab.id !== tabId);
9+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { useRouter } from '@/features/router';
2+
import { selectActiveTabId, selectHomeTab, selectTabs, setActiveTabId, setTabs } from '@/store/slice/tab';
3+
4+
import { filterTabsById } from './shared';
5+
6+
export function useTabActions() {
7+
const dispatch = useAppDispatch();
8+
9+
const tabs = useAppSelector(selectTabs);
10+
11+
const { navigate } = useRouter();
12+
13+
const activeTabId = useAppSelector(selectActiveTabId);
14+
15+
const homeTab = useAppSelector(selectHomeTab);
16+
17+
/**
18+
* 更新标签页
19+
*
20+
* @param newTabs
21+
*/
22+
function updateTabs(newTabs: App.Global.Tab[]) {
23+
dispatch(setTabs(newTabs));
24+
}
25+
26+
/**
27+
* 切换激活的标签页
28+
*
29+
* @param tabId
30+
*/
31+
function changeActiveTabId(tabId: string) {
32+
dispatch(setActiveTabId(tabId));
33+
}
34+
35+
/**
36+
* 根据标签页切换路由
37+
*
38+
* @param tab
39+
*/
40+
async function switchRouteByTab(tab: App.Global.Tab) {
41+
navigate(tab.fullPath);
42+
43+
changeActiveTabId(tab.id);
44+
}
45+
46+
/**
47+
* 清除左侧标签页
48+
*
49+
* @param tabId
50+
*/
51+
function clearLeftTabs(tabId: string) {
52+
const tabIndex = tabs.findIndex(tab => tab.id === tabId);
53+
54+
if (tabIndex === -1) return;
55+
56+
const restTabs = tabs.slice(tabIndex);
57+
58+
dispatch(setTabs(restTabs));
59+
}
60+
61+
/**
62+
* 删除标签页
63+
*
64+
* @param tabId
65+
*/
66+
function removeTab(tabId: string) {
67+
const isRemoveActiveTab = activeTabId === tabId;
68+
69+
const updatedTabs = filterTabsById(tabId, tabs);
70+
71+
if (!isRemoveActiveTab) {
72+
// 如果删除的不是激活的标签页,则更新标签页
73+
updateTabs(updatedTabs);
74+
} else {
75+
// 如果删除的是激活的标签页,则切换到最后一个标签页或者首页标签页
76+
const activeTab = updatedTabs.at(-1) || homeTab;
77+
78+
if (activeTab) {
79+
switchRouteByTab(activeTab);
80+
81+
updateTabs(updatedTabs);
82+
}
83+
}
84+
}
85+
86+
return {
87+
clearLeftTabs,
88+
removeTab,
89+
updateTabs
90+
};
91+
}

0 commit comments

Comments
 (0)