@zat-design/sisyphus-react 4.2.0 → 4.3.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.css +1 -1
- package/dist/less.esm.css +1 -1
- package/es/ProEditTable/components/RenderField/index.js +1 -23
- package/es/ProEditTable/index.js +2 -6
- package/es/ProEditTable/propsType.d.ts +1 -3
- package/es/ProEditTable/style/index.less +0 -14
- package/es/ProEditTable/utils/index.js +0 -18
- package/es/ProForm/components/combination/Group/component/AddonWrapper/index.js +11 -2
- package/es/ProForm/components/combination/Group/style/index.less +42 -31
- package/es/ProForm/components/combination/Group/utils/index.d.ts +18 -18
- package/es/ProForm/utils/useForm.js +12 -10
- package/es/ProLayout/components/Layout/Menu/FoldMenu/index.js +11 -3
- package/es/ProLayout/components/Layout/Menu/OpenMenu/index.js +3 -3
- package/es/ProLayout/components/TabsManager/components/TabsHeader.d.ts +3 -5
- package/es/ProLayout/components/TabsManager/components/TabsHeader.js +65 -24
- package/es/ProLayout/components/TabsManager/hooks/useTabsState.js +59 -65
- package/es/ProLayout/components/TabsManager/index.js +68 -36
- package/es/ProLayout/components/TabsManager/propTypes.d.ts +8 -0
- package/es/ProLayout/components/TabsManager/style/index.less +60 -68
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +14 -0
- package/es/ProLayout/components/TabsManager/utils/index.js +38 -6
- package/es/ProLayout/index.js +45 -15
- package/es/ProLayout/propTypes.d.ts +15 -0
- package/es/ProLayout/style/index.less +49 -1
- package/es/assets/edit.svg +1 -0
- package/package.json +1 -3
|
@@ -1,29 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import React, { createElement as _createElement } from 'react';
|
|
2
2
|
import { Tabs } from 'antd';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { closestCenter, DndContext, PointerSensor, useSensor } from '@dnd-kit/core';
|
|
4
|
+
import { horizontalListSortingStrategy, SortableContext, useSortable } from '@dnd-kit/sortable';
|
|
5
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
const DraggableTabNode = props => {
|
|
8
|
+
const {
|
|
9
|
+
attributes,
|
|
10
|
+
listeners,
|
|
11
|
+
setNodeRef,
|
|
12
|
+
transform,
|
|
13
|
+
transition
|
|
14
|
+
} = useSortable({
|
|
15
|
+
id: props['data-node-key']
|
|
16
|
+
});
|
|
17
|
+
const style = {
|
|
18
|
+
...props.style,
|
|
19
|
+
transform: CSS.Translate.toString(transform),
|
|
20
|
+
transition,
|
|
21
|
+
cursor: 'move'
|
|
22
|
+
};
|
|
23
|
+
return /*#__PURE__*/React.cloneElement(props.children, {
|
|
24
|
+
ref: setNodeRef,
|
|
25
|
+
style,
|
|
26
|
+
...attributes,
|
|
27
|
+
...listeners
|
|
28
|
+
});
|
|
29
|
+
};
|
|
8
30
|
export function TabsHeader({
|
|
9
31
|
activeKey,
|
|
10
32
|
tabsItems,
|
|
11
33
|
onTabChange,
|
|
12
|
-
onTabEdit
|
|
34
|
+
onTabEdit,
|
|
35
|
+
draggable = true,
|
|
36
|
+
onReorder
|
|
13
37
|
}) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
const sensor = useSensor(PointerSensor, {
|
|
39
|
+
activationConstraint: {
|
|
40
|
+
distance: 10
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const onDragEnd = ({
|
|
44
|
+
active,
|
|
45
|
+
over
|
|
46
|
+
}) => {
|
|
47
|
+
if (over && active.id !== over.id) {
|
|
48
|
+
onReorder?.(String(active.id), String(over.id));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const tabKeys = (tabsItems ?? []).map(item => item.key);
|
|
52
|
+
return /*#__PURE__*/_jsx("div", {
|
|
25
53
|
className: "pro-layout-tabs-header",
|
|
26
|
-
children:
|
|
54
|
+
children: /*#__PURE__*/_jsx(Tabs, {
|
|
27
55
|
activeKey: activeKey,
|
|
28
56
|
onChange: onTabChange,
|
|
29
57
|
onEdit: onTabEdit,
|
|
@@ -35,10 +63,23 @@ export function TabsHeader({
|
|
|
35
63
|
popup: {
|
|
36
64
|
root: 'pro-layout-tabs-dropdown-menu'
|
|
37
65
|
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
},
|
|
67
|
+
renderTabBar: draggable ? (tabBarProps, DefaultTabBar) => /*#__PURE__*/_jsx(DndContext, {
|
|
68
|
+
sensors: [sensor],
|
|
69
|
+
onDragEnd: onDragEnd,
|
|
70
|
+
collisionDetection: closestCenter,
|
|
71
|
+
children: /*#__PURE__*/_jsx(SortableContext, {
|
|
72
|
+
items: tabKeys,
|
|
73
|
+
strategy: horizontalListSortingStrategy,
|
|
74
|
+
children: /*#__PURE__*/_jsx(DefaultTabBar, {
|
|
75
|
+
...tabBarProps,
|
|
76
|
+
children: node => /*#__PURE__*/_createElement(DraggableTabNode, {
|
|
77
|
+
...node.props,
|
|
78
|
+
key: node.key
|
|
79
|
+
}, node)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
}) : undefined
|
|
83
|
+
})
|
|
43
84
|
});
|
|
44
85
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { message } from 'antd';
|
|
3
|
+
import { arrayMove } from '@dnd-kit/sortable';
|
|
3
4
|
import { DEFAULT_TABS_CONFIG } from "../propTypes";
|
|
4
5
|
import { useTabsCache } from "./useTabsCache";
|
|
5
6
|
import locale, { formatMessage } from "../../../../locale";
|
|
6
|
-
import { createTabFromMenu, generateTabId, shouldOpenExternal, handleExternalOpen, checkTabLimit, getRightTabs, flattenMenuData,
|
|
7
|
+
import { createTabFromMenu, generateTabId, shouldOpenExternal, handleExternalOpen, checkTabLimit, getRightTabs, flattenMenuData, canOpenAsTab, getMenuRoute } from "../utils";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* 标签页状态管理Hook
|
|
@@ -106,8 +107,8 @@ export const useTabsState = options => {
|
|
|
106
107
|
forceNew = false
|
|
107
108
|
} = options || {};
|
|
108
109
|
|
|
109
|
-
//
|
|
110
|
-
if (!
|
|
110
|
+
// 非叶子节点默认不入签,仅根节点特例可入签
|
|
111
|
+
if (!canOpenAsTab(menuItem)) {
|
|
111
112
|
return false;
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -118,7 +119,8 @@ export const useTabsState = options => {
|
|
|
118
119
|
|
|
119
120
|
// 如果 forceNew = false(默认),检查是否已存在相同的标签页
|
|
120
121
|
if (!forceNew) {
|
|
121
|
-
const
|
|
122
|
+
const menuRoute = getMenuRoute(menuItem);
|
|
123
|
+
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === menuItem.code || tab.url === menuRoute);
|
|
122
124
|
if (existingTab) {
|
|
123
125
|
// 如果已存在,可以添加(会切换到已存在的标签页)
|
|
124
126
|
return true;
|
|
@@ -141,48 +143,41 @@ export const useTabsState = options => {
|
|
|
141
143
|
forceNew = false
|
|
142
144
|
} = options || {};
|
|
143
145
|
setState(prevState => {
|
|
144
|
-
|
|
145
|
-
if (!isLeafMenuItem(menuItem)) {
|
|
146
|
-
return prevState;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 检查是否需要外部跳转
|
|
146
|
+
if (!canOpenAsTab(menuItem)) return prevState;
|
|
150
147
|
if (shouldOpenExternal(menuItem)) {
|
|
151
148
|
handleExternalOpen(menuItem);
|
|
152
149
|
return prevState;
|
|
153
150
|
}
|
|
154
|
-
const existingIds = prevState.tabsList.map(tab => tab.id);
|
|
155
|
-
const tabId = generateTabId(menuItem, existingIds);
|
|
156
151
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
152
|
+
// 非强制新建时,查找已存在的标签页并切换
|
|
153
|
+
const menuRoute = getMenuRoute(menuItem);
|
|
154
|
+
const existingTab = !forceNew ? prevState.tabsList.find(tab => tab.menuItem?.code === menuItem.code || tab.url === menuRoute) : undefined;
|
|
155
|
+
if (existingTab) {
|
|
156
|
+
// 若来源菜单明确标记 closable: false 而已有 tab 未继承,则修正
|
|
157
|
+
const correctedTab = menuItem.closable === false && existingTab.closable !== false ? {
|
|
158
|
+
...existingTab,
|
|
159
|
+
closable: false
|
|
160
|
+
} : existingTab;
|
|
161
|
+
const updatedTabsList = correctedTab !== existingTab ? prevState.tabsList.map(t => t.id === existingTab.id ? correctedTab : t) : prevState.tabsList;
|
|
162
|
+
return {
|
|
163
|
+
...prevState,
|
|
164
|
+
tabsList: updatedTabsList,
|
|
165
|
+
activeKey: existingTab.id,
|
|
166
|
+
activeTabInfo: correctedTab,
|
|
167
|
+
activeComponent: existingTab.menuItem?.code || existingTab.id
|
|
168
|
+
};
|
|
170
169
|
}
|
|
171
|
-
|
|
172
|
-
// 检查是否超出限制
|
|
173
170
|
if (checkTabLimit(prevState.tabsList, finalConfig.max)) {
|
|
174
|
-
|
|
175
|
-
const messageText = formatMessage(locale.ProLayout.tabMaxLimitMessage, {
|
|
171
|
+
message.info(formatMessage(locale.ProLayout.tabMaxLimitMessage, {
|
|
176
172
|
max: finalConfig.max
|
|
177
|
-
});
|
|
178
|
-
message.info(messageText);
|
|
173
|
+
}));
|
|
179
174
|
return prevState;
|
|
180
175
|
}
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
const existingIds = prevState.tabsList.map(tab => tab.id);
|
|
177
|
+
const tabId = generateTabId(menuItem, existingIds);
|
|
183
178
|
const newTab = createTabFromMenu(menuItem, prevState.newTabIndex);
|
|
184
179
|
newTab.id = tabId;
|
|
185
|
-
|
|
180
|
+
return {
|
|
186
181
|
...prevState,
|
|
187
182
|
tabsList: [...prevState.tabsList, newTab],
|
|
188
183
|
activeKey: tabId,
|
|
@@ -190,7 +185,6 @@ export const useTabsState = options => {
|
|
|
190
185
|
newTabIndex: prevState.newTabIndex + 1,
|
|
191
186
|
activeComponent: menuItem.code || tabId
|
|
192
187
|
};
|
|
193
|
-
return newState;
|
|
194
188
|
});
|
|
195
189
|
}, [finalConfig]);
|
|
196
190
|
|
|
@@ -316,22 +310,38 @@ export const useTabsState = options => {
|
|
|
316
310
|
shouldSkipSaveRef.current = true;
|
|
317
311
|
clearCache();
|
|
318
312
|
|
|
319
|
-
//
|
|
320
|
-
if (
|
|
313
|
+
// 直接调用 onTabChange(useEffect 因 shouldSkipSaveRef 会跳过)
|
|
314
|
+
if (finalConfig.onTabChange) {
|
|
315
|
+
const firstTab = newTabs.length > 0 ? newTabs[0] : undefined;
|
|
321
316
|
setTimeout(() => {
|
|
322
|
-
finalConfig.onTabChange('',
|
|
317
|
+
finalConfig.onTabChange(firstTab?.id ?? '', firstTab, newTabs);
|
|
323
318
|
}, 0);
|
|
324
319
|
}
|
|
325
320
|
return {
|
|
326
321
|
...prevState,
|
|
327
322
|
tabsList: newTabs,
|
|
328
323
|
activeKey: newActiveKey,
|
|
329
|
-
activeTabInfo: undefined,
|
|
324
|
+
activeTabInfo: newTabs.length > 0 ? newTabs[0] : undefined,
|
|
330
325
|
activeComponent: newActiveComponent
|
|
331
326
|
};
|
|
332
327
|
});
|
|
333
328
|
}, [clearCache, finalConfig]);
|
|
334
329
|
|
|
330
|
+
/**
|
|
331
|
+
* 重排标签页顺序(拖拽结束后调用)
|
|
332
|
+
*/
|
|
333
|
+
const reorderTabs = useCallback((activeId, overId) => {
|
|
334
|
+
setState(prevState => {
|
|
335
|
+
const activeIndex = prevState.tabsList.findIndex(tab => tab.id === activeId);
|
|
336
|
+
const overIndex = prevState.tabsList.findIndex(tab => tab.id === overId);
|
|
337
|
+
if (activeIndex === -1 || overIndex === -1 || activeIndex === overIndex) return prevState;
|
|
338
|
+
return {
|
|
339
|
+
...prevState,
|
|
340
|
+
tabsList: arrayMove(prevState.tabsList, activeIndex, overIndex)
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}, []);
|
|
344
|
+
|
|
335
345
|
/**
|
|
336
346
|
* 重置状态
|
|
337
347
|
*/
|
|
@@ -347,40 +357,23 @@ export const useTabsState = options => {
|
|
|
347
357
|
// 监听 URL 变化并同步 Tabs (仅在初始化和 location 变化时)
|
|
348
358
|
// 放在最后以确保可以使用 addTab 和 switchTab
|
|
349
359
|
useEffect(() => {
|
|
350
|
-
// 确保数据源存在
|
|
351
360
|
if (!dataSource || !('menus' in dataSource)) return;
|
|
352
361
|
const currentPath = window.location.pathname;
|
|
353
|
-
|
|
354
|
-
// 如果当前没有 activeKey 或者 activeKey 对应的 URL 与当前 URL 不一致
|
|
355
362
|
const activeTab = state.tabsList.find(tab => tab.id === state.activeKey);
|
|
356
|
-
|
|
357
|
-
// 如果当前已经在正确的 Tab 上,无需处理
|
|
358
363
|
if (activeTab && activeTab.url === currentPath) return;
|
|
359
|
-
|
|
360
|
-
// 在菜单中查找当前 URL 对应的菜单项
|
|
361
364
|
const flatMenus = flattenMenuData(dataSource.menus || []);
|
|
362
|
-
const targetMenu = flatMenus.find(item => item
|
|
363
|
-
if (targetMenu)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// 检查是否已在 Tabs 中
|
|
370
|
-
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === targetMenu.url);
|
|
371
|
-
if (existingTab) {
|
|
372
|
-
// 如果已存在,切换到该 Tab
|
|
373
|
-
if (state.activeKey !== existingTab.id) {
|
|
374
|
-
switchTab(existingTab.id);
|
|
375
|
-
}
|
|
376
|
-
} else {
|
|
377
|
-
// 如果不存在,添加新 Tab
|
|
378
|
-
addTab(targetMenu);
|
|
379
|
-
}
|
|
365
|
+
const targetMenu = flatMenus.find(item => getMenuRoute(item) === currentPath);
|
|
366
|
+
if (!targetMenu || !canOpenAsTab(targetMenu)) return;
|
|
367
|
+
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === getMenuRoute(targetMenu));
|
|
368
|
+
if (existingTab) {
|
|
369
|
+
if (state.activeKey !== existingTab.id) switchTab(existingTab.id);
|
|
370
|
+
return;
|
|
380
371
|
}
|
|
372
|
+
addTab(targetMenu);
|
|
381
373
|
}, [dataSource, state.tabsList, state.activeKey, addTab, switchTab]);
|
|
382
374
|
return {
|
|
383
375
|
state,
|
|
376
|
+
isInitialized,
|
|
384
377
|
addTab,
|
|
385
378
|
canAddTab,
|
|
386
379
|
removeTab,
|
|
@@ -388,6 +381,7 @@ export const useTabsState = options => {
|
|
|
388
381
|
closeOtherTabs,
|
|
389
382
|
closeRightTabs,
|
|
390
383
|
closeAllTabs,
|
|
391
|
-
resetTabs
|
|
384
|
+
resetTabs,
|
|
385
|
+
reorderTabs
|
|
392
386
|
};
|
|
393
387
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
|
1
|
+
import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
import { useTabsState } from "./hooks/useTabsState";
|
|
4
|
+
import { flattenMenuData } from "./utils";
|
|
3
5
|
import { TabItemComponent } from "./components/TabItem";
|
|
4
6
|
import { TabsHeader } from "./components/TabsHeader";
|
|
5
7
|
import { TabsContext } from "./components/TabsContext";
|
|
@@ -12,18 +14,21 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
12
14
|
config,
|
|
13
15
|
children,
|
|
14
16
|
dataSource,
|
|
15
|
-
originalOnMenuClick
|
|
17
|
+
originalOnMenuClick,
|
|
18
|
+
tabsBarContainer,
|
|
19
|
+
onTabsChange
|
|
16
20
|
}, ref) => {
|
|
17
|
-
// 使用标签页状态管理Hook
|
|
18
21
|
const {
|
|
19
22
|
state,
|
|
23
|
+
isInitialized,
|
|
20
24
|
addTab,
|
|
21
25
|
canAddTab,
|
|
22
26
|
removeTab,
|
|
23
27
|
switchTab,
|
|
24
28
|
closeOtherTabs,
|
|
25
29
|
closeRightTabs,
|
|
26
|
-
closeAllTabs
|
|
30
|
+
closeAllTabs,
|
|
31
|
+
reorderTabs
|
|
27
32
|
} = useTabsState({
|
|
28
33
|
config,
|
|
29
34
|
dataSource
|
|
@@ -44,14 +49,42 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
44
49
|
}
|
|
45
50
|
}, [state.activeKey]);
|
|
46
51
|
|
|
52
|
+
// 通知父级(ProLayout)当前是否有 Tab
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
onTabsChange?.(state.tabsList.length > 0);
|
|
55
|
+
}, [state.tabsList.length, onTabsChange]);
|
|
56
|
+
|
|
57
|
+
// 处理声明式固定标签页:config.fixed 中的 code 在初始化完成后自动添加,强制 closable=false
|
|
58
|
+
// 用 ref 持有 addTab,避免 addTab 引用变化引起无限循环
|
|
59
|
+
const addTabRef = useRef(addTab);
|
|
60
|
+
addTabRef.current = addTab;
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!isInitialized || !config.fixed || config.fixed.length === 0) return;
|
|
63
|
+
const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
|
|
64
|
+
|
|
65
|
+
// menus 为空说明异步数据尚未返回,等 dataSource 变化后重跑,避免用 code 创建占位 tab
|
|
66
|
+
if (sourceMenus.length === 0) return;
|
|
67
|
+
config.fixed.forEach(code => {
|
|
68
|
+
const matchedMenu = sourceMenus.find(item => item.code === code);
|
|
69
|
+
const menuItem = matchedMenu ? {
|
|
70
|
+
...matchedMenu,
|
|
71
|
+
closable: false
|
|
72
|
+
} : {
|
|
73
|
+
id: Date.now(),
|
|
74
|
+
code,
|
|
75
|
+
name: code,
|
|
76
|
+
url: `/${code}`,
|
|
77
|
+
closable: false
|
|
78
|
+
};
|
|
79
|
+
addTabRef.current(menuItem);
|
|
80
|
+
});
|
|
81
|
+
}, [isInitialized, config.fixed, dataSource]);
|
|
82
|
+
|
|
47
83
|
// 处理菜单点击 - 拦截原有的菜单点击逻辑
|
|
48
84
|
const handleMenuClick = useCallback(params => {
|
|
49
85
|
if (params.item) {
|
|
50
|
-
// 添加到标签页
|
|
51
86
|
addTab(params.item);
|
|
52
87
|
}
|
|
53
|
-
|
|
54
|
-
// 如果有原始的菜单点击处理函数,也调用它
|
|
55
88
|
originalOnMenuClick?.(params);
|
|
56
89
|
}, [addTab, originalOnMenuClick]);
|
|
57
90
|
|
|
@@ -63,13 +96,18 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
63
96
|
name,
|
|
64
97
|
extra
|
|
65
98
|
} = params;
|
|
66
|
-
const
|
|
99
|
+
const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
|
|
100
|
+
const matchedMenu = sourceMenus.find(item => item.code === code);
|
|
101
|
+
const menuItem = matchedMenu ? {
|
|
102
|
+
...matchedMenu,
|
|
103
|
+
// 允许外部覆盖展示名称和附加数据,但保留菜单原始 closable/url 等配置
|
|
104
|
+
name: name || matchedMenu.name,
|
|
105
|
+
extra: extra ?? matchedMenu.extra
|
|
106
|
+
} : {
|
|
67
107
|
id: Date.now(),
|
|
68
|
-
// 生成临时 ID
|
|
69
108
|
code,
|
|
70
109
|
name,
|
|
71
110
|
url: `/${code}`,
|
|
72
|
-
// 生成 URL
|
|
73
111
|
extra
|
|
74
112
|
};
|
|
75
113
|
addTab(menuItem, options);
|
|
@@ -80,14 +118,10 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
80
118
|
activeTabInfo: state.tabsList.find(tab => tab.id === state.activeKey),
|
|
81
119
|
activeComponent: state.activeComponent
|
|
82
120
|
})
|
|
83
|
-
}), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent]);
|
|
84
|
-
|
|
85
|
-
// 处理标签切换
|
|
121
|
+
}), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent, dataSource]);
|
|
86
122
|
const handleTabChange = useCallback(activeKey => {
|
|
87
123
|
switchTab(activeKey);
|
|
88
124
|
}, [switchTab]);
|
|
89
|
-
|
|
90
|
-
// 处理标签关闭
|
|
91
125
|
const handleTabEdit = useCallback((targetKey, action) => {
|
|
92
126
|
if (action === 'remove') {
|
|
93
127
|
removeTab(targetKey);
|
|
@@ -111,12 +145,11 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
111
145
|
menuItems: config?.menuItems,
|
|
112
146
|
tabMenuClick: config?.tabMenuClick
|
|
113
147
|
}),
|
|
114
|
-
closable:
|
|
115
|
-
|
|
148
|
+
closable: false,
|
|
149
|
+
// 关闭 antd 内置 ×,由 TabItemComponent 自定义按钮处理
|
|
150
|
+
children: null
|
|
116
151
|
}));
|
|
117
152
|
}, [state.tabsList, state.activeKey, config?.menuItems, config?.tabMenuClick, switchTab, removeTab, closeOtherTabs, closeRightTabs, closeAllTabs]);
|
|
118
|
-
|
|
119
|
-
// 从 config 中获取组件解析函数和空状态组件
|
|
120
153
|
const activeComponent = config?.activeComponent;
|
|
121
154
|
const emptyComponent = config?.empty;
|
|
122
155
|
|
|
@@ -127,8 +160,6 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
127
160
|
children: [state.tabsList.map(tab => {
|
|
128
161
|
const isActive = tab.id === state.activeKey;
|
|
129
162
|
const hasVisited = visitedTabIds.has(tab.id);
|
|
130
|
-
|
|
131
|
-
// 如果既不是当前激活,也没有访问过,则只渲染占位符(懒加载)
|
|
132
163
|
if (!isActive && !hasVisited) {
|
|
133
164
|
return /*#__PURE__*/_jsx("div", {
|
|
134
165
|
className: "tab-pane hidden",
|
|
@@ -136,12 +167,9 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
136
167
|
}, tab.id);
|
|
137
168
|
}
|
|
138
169
|
let content = children;
|
|
139
|
-
|
|
140
|
-
// 如果提供了组件解析函数,尝试解析组件
|
|
141
170
|
if (activeComponent && tab.menuItem?.code) {
|
|
142
171
|
const ResolvedComponent = activeComponent(tab.menuItem.code);
|
|
143
172
|
if (ResolvedComponent) {
|
|
144
|
-
// 将 extra 中的所有属性作为 props 传递给组件
|
|
145
173
|
content = /*#__PURE__*/_jsx(ResolvedComponent, {
|
|
146
174
|
...(tab.menuItem.extra || {})
|
|
147
175
|
});
|
|
@@ -160,23 +188,27 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
160
188
|
});
|
|
161
189
|
};
|
|
162
190
|
|
|
163
|
-
//
|
|
191
|
+
// TabsHeader 节点:有 tabsBarContainer 时用 Portal 渲染到外部,否则原位 fallback
|
|
192
|
+
const tabsHeaderNode = state.tabsList.length > 0 ? /*#__PURE__*/_jsx(TabsHeader, {
|
|
193
|
+
activeKey: state.activeKey || undefined,
|
|
194
|
+
tabsItems: tabsItems,
|
|
195
|
+
onTabChange: handleTabChange,
|
|
196
|
+
onTabEdit: handleTabEdit,
|
|
197
|
+
draggable: config?.draggable !== false,
|
|
198
|
+
onReorder: reorderTabs
|
|
199
|
+
}) : null;
|
|
164
200
|
useImperativeHandle(ref, () => ({
|
|
165
201
|
handleMenuClick,
|
|
166
|
-
canAddTab
|
|
167
|
-
|
|
168
|
-
|
|
202
|
+
canAddTab,
|
|
203
|
+
getTabInfo: tabsInstance.getTabInfo
|
|
204
|
+
}), [handleMenuClick, canAddTab, tabsInstance]);
|
|
205
|
+
return /*#__PURE__*/_jsxs(TabsContext.Provider, {
|
|
169
206
|
value: tabsInstance,
|
|
170
|
-
children: /*#__PURE__*/
|
|
207
|
+
children: [tabsBarContainer ? /*#__PURE__*/createPortal(tabsHeaderNode, tabsBarContainer) : tabsHeaderNode, /*#__PURE__*/_jsx("div", {
|
|
171
208
|
className: "pro-layout-tabs",
|
|
172
209
|
"data-testid": "tabs-manager",
|
|
173
|
-
children:
|
|
174
|
-
|
|
175
|
-
tabsItems: tabsItems,
|
|
176
|
-
onTabChange: handleTabChange,
|
|
177
|
-
onTabEdit: handleTabEdit
|
|
178
|
-
}), renderContent()]
|
|
179
|
-
})
|
|
210
|
+
children: renderContent()
|
|
211
|
+
})]
|
|
180
212
|
});
|
|
181
213
|
});
|
|
182
214
|
TabsManager.displayName = 'TabsManager';
|
|
@@ -14,6 +14,10 @@ export interface TabsManagerProps {
|
|
|
14
14
|
key: string;
|
|
15
15
|
keyPath: string[];
|
|
16
16
|
}) => void;
|
|
17
|
+
/** Tab 栏的 Portal 挂载目标(由 ProLayout 提供),不传则原位渲染 */
|
|
18
|
+
tabsBarContainer?: HTMLElement | null;
|
|
19
|
+
/** Tab 有无变化回调,供 ProLayout 控制 wrapper 动画 */
|
|
20
|
+
onTabsChange?: (hasTabs: boolean) => void;
|
|
17
21
|
}
|
|
18
22
|
export interface TabContextMenuProps {
|
|
19
23
|
tabId: string;
|
|
@@ -47,6 +51,8 @@ export interface UseTabsStateOptions {
|
|
|
47
51
|
export interface UseTabsStateReturn {
|
|
48
52
|
/** 当前状态 */
|
|
49
53
|
state: TabsState;
|
|
54
|
+
/** 缓存是否已恢复完成 */
|
|
55
|
+
isInitialized: boolean;
|
|
50
56
|
/** 添加标签页 */
|
|
51
57
|
addTab: (menuItem: MenusType, options?: AddTabOptions) => void;
|
|
52
58
|
/** 检查是否可以添加标签页 */
|
|
@@ -63,6 +69,8 @@ export interface UseTabsStateReturn {
|
|
|
63
69
|
closeAllTabs: () => void;
|
|
64
70
|
/** 重置状态 */
|
|
65
71
|
resetTabs: () => void;
|
|
72
|
+
/** 重排标签页顺序 */
|
|
73
|
+
reorderTabs: (activeId: string, overId: string) => void;
|
|
66
74
|
}
|
|
67
75
|
export interface TabsCacheManager {
|
|
68
76
|
save: (state: TabsState) => void;
|