@zat-design/sisyphus-react 4.3.0-beta.8 → 4.3.0-beta.9
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/es/ProForm/components/combination/Group/utils/index.d.ts +20 -20
- package/es/ProLayout/components/TabsManager/hooks/useTabsState.js +35 -60
- package/es/ProLayout/components/TabsManager/index.js +38 -5
- package/es/ProLayout/components/TabsManager/propTypes.d.ts +2 -0
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +1 -1
- package/es/ProLayout/components/TabsManager/utils/index.js +4 -6
- package/es/ProLayout/index.js +18 -5
- package/es/ProLayout/propTypes.d.ts +5 -0
- package/package.json +1 -1
|
@@ -75,58 +75,58 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
|
|
|
75
75
|
confirm?: boolean | import("antd").ModalFuncProps | import("../../../render/propsType").FunctionArgs<any, boolean | import("antd").ModalFuncProps>;
|
|
76
76
|
show?: boolean | ReactiveFunction<any, boolean>;
|
|
77
77
|
component?: React.ReactNode | ReactiveFunction<any, React.ReactNode>;
|
|
78
|
-
|
|
78
|
+
id?: string;
|
|
79
79
|
className?: string;
|
|
80
|
+
children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
|
|
80
81
|
hidden?: boolean;
|
|
81
|
-
id?: string;
|
|
82
82
|
style?: React.CSSProperties;
|
|
83
|
-
children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
|
|
84
83
|
onReset?: () => void;
|
|
85
84
|
prefixCls?: string;
|
|
86
85
|
rootClassName?: string;
|
|
87
|
-
|
|
86
|
+
layout?: import("antd/es/form/Form").FormItemLayout;
|
|
87
|
+
help?: React.ReactNode;
|
|
88
|
+
vertical?: boolean;
|
|
89
|
+
preserve?: boolean;
|
|
90
|
+
trim?: boolean;
|
|
91
|
+
normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
|
|
88
92
|
htmlFor?: string;
|
|
93
|
+
trigger?: string;
|
|
94
|
+
isView?: boolean;
|
|
95
|
+
status?: "" | "warning" | "error" | "success" | "validating";
|
|
96
|
+
getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
|
|
97
|
+
desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
|
|
98
|
+
validateTrigger?: string | false | string[];
|
|
99
|
+
clearNotShow?: boolean;
|
|
89
100
|
labelAlign?: import("antd/es/form/interface").FormLabelAlign;
|
|
101
|
+
colon?: boolean;
|
|
90
102
|
labelCol?: import("antd").ColProps;
|
|
91
|
-
|
|
103
|
+
wrapperCol?: import("antd").ColProps;
|
|
92
104
|
getValueFromEvent?: (...args: import("@rc-component/form/lib/interface").EventArgs) => any;
|
|
93
|
-
normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
|
|
94
105
|
shouldUpdate?: import("@rc-component/form/lib/Field").ShouldUpdate<any>;
|
|
95
|
-
trigger?: string;
|
|
96
|
-
validateTrigger?: string | false | string[];
|
|
97
106
|
validateDebounce?: number;
|
|
98
107
|
valuePropName?: string;
|
|
99
|
-
getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
|
|
100
108
|
messageVariables?: Record<string, string>;
|
|
101
109
|
initialValue?: any;
|
|
102
110
|
onMetaChange?: (meta: import("@rc-component/form/lib/Field").MetaEvent) => void;
|
|
103
|
-
preserve?: boolean;
|
|
104
111
|
isListField?: boolean;
|
|
105
112
|
isList?: boolean;
|
|
106
113
|
noStyle?: boolean;
|
|
107
114
|
hasFeedback?: boolean | {
|
|
108
115
|
icons: import("antd/es/form/FormItem").FeedbackIcons;
|
|
109
116
|
};
|
|
110
|
-
validateStatus?: "" | "
|
|
111
|
-
layout?: import("antd/es/form/Form").FormItemLayout;
|
|
112
|
-
wrapperCol?: import("antd").ColProps;
|
|
113
|
-
help?: React.ReactNode;
|
|
117
|
+
validateStatus?: "" | "warning" | "error" | "success" | "validating";
|
|
114
118
|
fieldId?: string;
|
|
115
119
|
valueType?: import("../../../render/propsType").ProFormValueType;
|
|
120
|
+
switchValue?: [any, any];
|
|
116
121
|
viewRender?: (value: any, record: any, { form, index, namePath, }: {
|
|
117
122
|
[key: string]: any;
|
|
118
123
|
form: FormInstance<any>;
|
|
119
124
|
index?: number;
|
|
120
125
|
}) => string | React.ReactElement<any, any>;
|
|
121
|
-
desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
|
|
122
|
-
isView?: boolean;
|
|
123
|
-
switchValue?: [any, any];
|
|
124
126
|
viewType?: import("../../../render/propsType").ViewType;
|
|
125
|
-
trim?: boolean;
|
|
126
127
|
upperCase?: boolean;
|
|
127
128
|
toISOString?: boolean;
|
|
128
129
|
toCSTString?: boolean;
|
|
129
|
-
clearNotShow?: boolean;
|
|
130
130
|
name: any;
|
|
131
131
|
dependencies: any[];
|
|
132
132
|
tooltip: string | {
|
|
@@ -141,7 +141,7 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
|
|
|
141
141
|
* 创建组件属性
|
|
142
142
|
*/
|
|
143
143
|
export declare const createComponentProps: (column: FlexibleGroupColumnType, formItemProps: any) => {
|
|
144
|
-
componentProps: import("lodash").Omit<any, "
|
|
144
|
+
componentProps: import("lodash").Omit<any, "format" | "clearNotShow" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "precision">;
|
|
145
145
|
formItemTransform: {
|
|
146
146
|
getValueProps: any;
|
|
147
147
|
normalize: any;
|
|
@@ -143,49 +143,41 @@ export const useTabsState = options => {
|
|
|
143
143
|
forceNew = false
|
|
144
144
|
} = options || {};
|
|
145
145
|
setState(prevState => {
|
|
146
|
-
|
|
147
|
-
if (!canOpenAsTab(menuItem)) {
|
|
148
|
-
return prevState;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 检查是否需要外部跳转
|
|
146
|
+
if (!canOpenAsTab(menuItem)) return prevState;
|
|
152
147
|
if (shouldOpenExternal(menuItem)) {
|
|
153
148
|
handleExternalOpen(menuItem);
|
|
154
149
|
return prevState;
|
|
155
150
|
}
|
|
156
|
-
const existingIds = prevState.tabsList.map(tab => tab.id);
|
|
157
|
-
const tabId = generateTabId(menuItem, existingIds);
|
|
158
151
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
};
|
|
173
169
|
}
|
|
174
|
-
|
|
175
|
-
// 检查是否超出限制
|
|
176
170
|
if (checkTabLimit(prevState.tabsList, finalConfig.max)) {
|
|
177
|
-
|
|
178
|
-
const messageText = formatMessage(locale.ProLayout.tabMaxLimitMessage, {
|
|
171
|
+
message.info(formatMessage(locale.ProLayout.tabMaxLimitMessage, {
|
|
179
172
|
max: finalConfig.max
|
|
180
|
-
});
|
|
181
|
-
message.info(messageText);
|
|
173
|
+
}));
|
|
182
174
|
return prevState;
|
|
183
175
|
}
|
|
184
|
-
|
|
185
|
-
|
|
176
|
+
const existingIds = prevState.tabsList.map(tab => tab.id);
|
|
177
|
+
const tabId = generateTabId(menuItem, existingIds);
|
|
186
178
|
const newTab = createTabFromMenu(menuItem, prevState.newTabIndex);
|
|
187
179
|
newTab.id = tabId;
|
|
188
|
-
|
|
180
|
+
return {
|
|
189
181
|
...prevState,
|
|
190
182
|
tabsList: [...prevState.tabsList, newTab],
|
|
191
183
|
activeKey: tabId,
|
|
@@ -193,7 +185,6 @@ export const useTabsState = options => {
|
|
|
193
185
|
newTabIndex: prevState.newTabIndex + 1,
|
|
194
186
|
activeComponent: menuItem.code || tabId
|
|
195
187
|
};
|
|
196
|
-
return newState;
|
|
197
188
|
});
|
|
198
189
|
}, [finalConfig]);
|
|
199
190
|
|
|
@@ -319,17 +310,18 @@ export const useTabsState = options => {
|
|
|
319
310
|
shouldSkipSaveRef.current = true;
|
|
320
311
|
clearCache();
|
|
321
312
|
|
|
322
|
-
//
|
|
323
|
-
if (
|
|
313
|
+
// 直接调用 onTabChange(useEffect 因 shouldSkipSaveRef 会跳过)
|
|
314
|
+
if (finalConfig.onTabChange) {
|
|
315
|
+
const firstTab = newTabs.length > 0 ? newTabs[0] : undefined;
|
|
324
316
|
setTimeout(() => {
|
|
325
|
-
finalConfig.onTabChange('',
|
|
317
|
+
finalConfig.onTabChange(firstTab?.id ?? '', firstTab, newTabs);
|
|
326
318
|
}, 0);
|
|
327
319
|
}
|
|
328
320
|
return {
|
|
329
321
|
...prevState,
|
|
330
322
|
tabsList: newTabs,
|
|
331
323
|
activeKey: newActiveKey,
|
|
332
|
-
activeTabInfo: undefined,
|
|
324
|
+
activeTabInfo: newTabs.length > 0 ? newTabs[0] : undefined,
|
|
333
325
|
activeComponent: newActiveComponent
|
|
334
326
|
};
|
|
335
327
|
});
|
|
@@ -365,40 +357,23 @@ export const useTabsState = options => {
|
|
|
365
357
|
// 监听 URL 变化并同步 Tabs (仅在初始化和 location 变化时)
|
|
366
358
|
// 放在最后以确保可以使用 addTab 和 switchTab
|
|
367
359
|
useEffect(() => {
|
|
368
|
-
// 确保数据源存在
|
|
369
360
|
if (!dataSource || !('menus' in dataSource)) return;
|
|
370
361
|
const currentPath = window.location.pathname;
|
|
371
|
-
|
|
372
|
-
// 如果当前没有 activeKey 或者 activeKey 对应的 URL 与当前 URL 不一致
|
|
373
362
|
const activeTab = state.tabsList.find(tab => tab.id === state.activeKey);
|
|
374
|
-
|
|
375
|
-
// 如果当前已经在正确的 Tab 上,无需处理
|
|
376
363
|
if (activeTab && activeTab.url === currentPath) return;
|
|
377
|
-
|
|
378
|
-
// 在菜单中查找当前 URL 对应的菜单项
|
|
379
364
|
const flatMenus = flattenMenuData(dataSource.menus || []);
|
|
380
365
|
const targetMenu = flatMenus.find(item => getMenuRoute(item) === currentPath);
|
|
381
|
-
if (targetMenu)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// 检查是否已在 Tabs 中
|
|
388
|
-
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === getMenuRoute(targetMenu));
|
|
389
|
-
if (existingTab) {
|
|
390
|
-
// 如果已存在,切换到该 Tab
|
|
391
|
-
if (state.activeKey !== existingTab.id) {
|
|
392
|
-
switchTab(existingTab.id);
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
// 如果不存在,添加新 Tab
|
|
396
|
-
addTab(targetMenu);
|
|
397
|
-
}
|
|
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;
|
|
398
371
|
}
|
|
372
|
+
addTab(targetMenu);
|
|
399
373
|
}, [dataSource, state.tabsList, state.activeKey, addTab, switchTab]);
|
|
400
374
|
return {
|
|
401
375
|
state,
|
|
376
|
+
isInitialized,
|
|
402
377
|
addTab,
|
|
403
378
|
canAddTab,
|
|
404
379
|
removeTab,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
|
1
|
+
import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { useTabsState } from "./hooks/useTabsState";
|
|
4
|
+
import { flattenMenuData } from "./utils";
|
|
4
5
|
import { TabItemComponent } from "./components/TabItem";
|
|
5
6
|
import { TabsHeader } from "./components/TabsHeader";
|
|
6
7
|
import { TabsContext } from "./components/TabsContext";
|
|
@@ -19,6 +20,7 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
19
20
|
}, ref) => {
|
|
20
21
|
const {
|
|
21
22
|
state,
|
|
23
|
+
isInitialized,
|
|
22
24
|
addTab,
|
|
23
25
|
canAddTab,
|
|
24
26
|
removeTab,
|
|
@@ -52,6 +54,29 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
52
54
|
onTabsChange?.(state.tabsList.length > 0);
|
|
53
55
|
}, [state.tabsList.length, onTabsChange]);
|
|
54
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
|
+
config.fixed.forEach(code => {
|
|
65
|
+
const matchedMenu = sourceMenus.find(item => item.code === code);
|
|
66
|
+
const menuItem = matchedMenu ? {
|
|
67
|
+
...matchedMenu,
|
|
68
|
+
closable: false
|
|
69
|
+
} : {
|
|
70
|
+
id: Date.now(),
|
|
71
|
+
code,
|
|
72
|
+
name: code,
|
|
73
|
+
url: `/${code}`,
|
|
74
|
+
closable: false
|
|
75
|
+
};
|
|
76
|
+
addTabRef.current(menuItem);
|
|
77
|
+
});
|
|
78
|
+
}, [isInitialized, config.fixed, dataSource]);
|
|
79
|
+
|
|
55
80
|
// 处理菜单点击 - 拦截原有的菜单点击逻辑
|
|
56
81
|
const handleMenuClick = useCallback(params => {
|
|
57
82
|
if (params.item) {
|
|
@@ -68,7 +93,14 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
68
93
|
name,
|
|
69
94
|
extra
|
|
70
95
|
} = params;
|
|
71
|
-
const
|
|
96
|
+
const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
|
|
97
|
+
const matchedMenu = sourceMenus.find(item => item.code === code);
|
|
98
|
+
const menuItem = matchedMenu ? {
|
|
99
|
+
...matchedMenu,
|
|
100
|
+
// 允许外部覆盖展示名称和附加数据,但保留菜单原始 closable/url 等配置
|
|
101
|
+
name: name || matchedMenu.name,
|
|
102
|
+
extra: extra ?? matchedMenu.extra
|
|
103
|
+
} : {
|
|
72
104
|
id: Date.now(),
|
|
73
105
|
code,
|
|
74
106
|
name,
|
|
@@ -83,7 +115,7 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
83
115
|
activeTabInfo: state.tabsList.find(tab => tab.id === state.activeKey),
|
|
84
116
|
activeComponent: state.activeComponent
|
|
85
117
|
})
|
|
86
|
-
}), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent]);
|
|
118
|
+
}), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent, dataSource]);
|
|
87
119
|
const handleTabChange = useCallback(activeKey => {
|
|
88
120
|
switchTab(activeKey);
|
|
89
121
|
}, [switchTab]);
|
|
@@ -164,8 +196,9 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
164
196
|
}) : null;
|
|
165
197
|
useImperativeHandle(ref, () => ({
|
|
166
198
|
handleMenuClick,
|
|
167
|
-
canAddTab
|
|
168
|
-
|
|
199
|
+
canAddTab,
|
|
200
|
+
getTabInfo: tabsInstance.getTabInfo
|
|
201
|
+
}), [handleMenuClick, canAddTab, tabsInstance]);
|
|
169
202
|
return /*#__PURE__*/_jsxs(TabsContext.Provider, {
|
|
170
203
|
value: tabsInstance,
|
|
171
204
|
children: [tabsBarContainer ? /*#__PURE__*/createPortal(tabsHeaderNode, tabsBarContainer) : tabsHeaderNode, /*#__PURE__*/_jsx("div", {
|
|
@@ -51,6 +51,8 @@ export interface UseTabsStateOptions {
|
|
|
51
51
|
export interface UseTabsStateReturn {
|
|
52
52
|
/** 当前状态 */
|
|
53
53
|
state: TabsState;
|
|
54
|
+
/** 缓存是否已恢复完成 */
|
|
55
|
+
isInitialized: boolean;
|
|
54
56
|
/** 添加标签页 */
|
|
55
57
|
addTab: (menuItem: MenusType, options?: AddTabOptions) => void;
|
|
56
58
|
/** 检查是否可以添加标签页 */
|
|
@@ -50,6 +50,6 @@ export declare const isLeafMenuItem: (menuItem: MenusType) => boolean;
|
|
|
50
50
|
export declare const isRootMenuItem: (menuItem: MenusType) => boolean;
|
|
51
51
|
/**
|
|
52
52
|
* 判断菜单项是否可以在 Tabs 模式下打开
|
|
53
|
-
*
|
|
53
|
+
* 规则:只有叶子节点(无 children)才可打开
|
|
54
54
|
*/
|
|
55
55
|
export declare const canOpenAsTab: (menuItem: MenusType) => boolean;
|
|
@@ -11,7 +11,7 @@ export const getMenuRoute = menuItem => {
|
|
|
11
11
|
*/
|
|
12
12
|
export const createTabFromMenu = (menuItem, index = 0) => {
|
|
13
13
|
const route = getMenuRoute(menuItem);
|
|
14
|
-
|
|
14
|
+
const result = {
|
|
15
15
|
id: String(menuItem.id || menuItem.code || menuItem.url || index),
|
|
16
16
|
code: menuItem.code,
|
|
17
17
|
name: menuItem.name,
|
|
@@ -21,6 +21,7 @@ export const createTabFromMenu = (menuItem, index = 0) => {
|
|
|
21
21
|
menuItem,
|
|
22
22
|
icon: menuItem.icon || menuItem.imgUrl
|
|
23
23
|
};
|
|
24
|
+
return result;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -134,13 +135,10 @@ export const isRootMenuItem = menuItem => {
|
|
|
134
135
|
|
|
135
136
|
/**
|
|
136
137
|
* 判断菜单项是否可以在 Tabs 模式下打开
|
|
137
|
-
*
|
|
138
|
+
* 规则:只有叶子节点(无 children)才可打开
|
|
138
139
|
*/
|
|
139
140
|
export const canOpenAsTab = menuItem => {
|
|
140
141
|
if (!menuItem) return false;
|
|
141
142
|
if (!(menuItem.url || menuItem.redirectUrl)) return false;
|
|
142
|
-
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
return isRootMenuItem(menuItem);
|
|
143
|
+
return isLeafMenuItem(menuItem);
|
|
146
144
|
};
|
package/es/ProLayout/index.js
CHANGED
|
@@ -82,9 +82,22 @@ const ProLayout = props => {
|
|
|
82
82
|
menus: menuData
|
|
83
83
|
});
|
|
84
84
|
}, [dataSource]);
|
|
85
|
-
const menuDataSource = useMemo(() =>
|
|
86
|
-
menus
|
|
87
|
-
|
|
85
|
+
const menuDataSource = useMemo(() => {
|
|
86
|
+
// menus 已通过 useDeepCompareEffect 填充,直接使用
|
|
87
|
+
if (Array.isArray(menus) && menus.length > 0) {
|
|
88
|
+
return {
|
|
89
|
+
menus
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// menus state 尚未填充(初始为 []),直接用 dataSource 原始 menus,
|
|
93
|
+
// 确保 tabsInstance 第一次创建时就能查到完整菜单数据(含 closable 等属性)
|
|
94
|
+
if (!Array.isArray(dataSource) && Array.isArray(dataSource?.menus)) {
|
|
95
|
+
return dataSource;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
menus: menus || []
|
|
99
|
+
};
|
|
100
|
+
}, [menus, dataSource]);
|
|
88
101
|
const enhancedTabsConfig = useMemo(() => {
|
|
89
102
|
if (!isTabsLayout || !tabs) {
|
|
90
103
|
return tabs;
|
|
@@ -138,9 +151,9 @@ const ProLayout = props => {
|
|
|
138
151
|
return true;
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
//
|
|
154
|
+
// 只有叶子节点(无 children)可入签;有 children 的节点不激活
|
|
142
155
|
if (!canOpenAsTab(params.item)) {
|
|
143
|
-
return
|
|
156
|
+
return false;
|
|
144
157
|
}
|
|
145
158
|
|
|
146
159
|
// 可入签节点:检查是否可以添加标签页(检查 max 限制、_blank 等)
|
|
@@ -429,6 +429,11 @@ export interface TabsConfig {
|
|
|
429
429
|
tab: TabItem;
|
|
430
430
|
tabs: TabItem[];
|
|
431
431
|
}) => void;
|
|
432
|
+
/**
|
|
433
|
+
* @description 声明式固定标签页,传入菜单 code 数组,这些 tab 会在初始化时自动添加且不可关闭
|
|
434
|
+
* @example fixed: ['Workspace', 'PolicyInput']
|
|
435
|
+
*/
|
|
436
|
+
fixed?: string[];
|
|
432
437
|
}
|
|
433
438
|
export type ProLayoutMode = 'normal' | 'tabs';
|
|
434
439
|
export interface ProLayoutTabsProps extends ProLayoutBaseProps {
|