@zat-design/sisyphus-react 4.5.10-beta.2 → 4.6.0-beta.1
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/ProLayout/components/TabsManager/hooks/useTabsState.js +26 -10
- package/es/ProLayout/components/TabsManager/index.js +9 -1
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +2 -1
- package/es/ProLayout/components/TabsManager/utils/index.js +8 -0
- package/es/ProLayout/propTypes.d.ts +9 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
flattenMenuData,
|
|
14
14
|
canOpenAsTab,
|
|
15
15
|
getMenuRoute,
|
|
16
|
+
hasRelationKey,
|
|
16
17
|
findExistingTab,
|
|
17
18
|
normalizeTabUrl,
|
|
18
19
|
isSafeExternalUrl,
|
|
@@ -28,6 +29,7 @@ const useTabsState = (options) => {
|
|
|
28
29
|
const { initialState = {}, config, dataSource, cacheEnabled = true } = options;
|
|
29
30
|
const finalConfig = { ...DEFAULT_TABS_CONFIG, ...config };
|
|
30
31
|
const urlSyncEnabled = finalConfig.urlSync === true || typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null;
|
|
32
|
+
const urlSyncIdentity = typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null ? finalConfig.urlSync.identity || "url" : "url";
|
|
31
33
|
const [state, setState] = useState(() => ({
|
|
32
34
|
tabsList: [],
|
|
33
35
|
activeKey: "",
|
|
@@ -62,22 +64,26 @@ const useTabsState = (options) => {
|
|
|
62
64
|
}));
|
|
63
65
|
restoredState.activeKey = restoredState.activeKey === void 0 || restoredState.activeKey === null ? "" : String(restoredState.activeKey);
|
|
64
66
|
if (urlSyncEnabled) {
|
|
65
|
-
const
|
|
67
|
+
const retainedTabByIdentity = /* @__PURE__ */ new Map();
|
|
66
68
|
const retainedIdByOriginalId = /* @__PURE__ */ new Map();
|
|
67
69
|
restoredState.tabsList.forEach((tab) => {
|
|
70
|
+
var _a2;
|
|
68
71
|
const normalizedUrl = normalizeTabUrl(tab.url);
|
|
69
72
|
if (!normalizedUrl)
|
|
70
73
|
return;
|
|
71
|
-
const
|
|
74
|
+
const relationKey = tab.relationKey ?? ((_a2 = tab.menuItem) == null ? void 0 : _a2.relationKey);
|
|
75
|
+
const identity = urlSyncIdentity === "relationKey" && hasRelationKey(relationKey) ? `relationKey:${typeof relationKey}:${String(relationKey)}` : `url:${normalizedUrl}`;
|
|
76
|
+
const retainedTab = retainedTabByIdentity.get(identity);
|
|
72
77
|
if (retainedTab) {
|
|
78
|
+
retainedTabByIdentity.set(identity, { ...retainedTab, url: normalizedUrl });
|
|
73
79
|
retainedIdByOriginalId.set(tab.id, retainedTab.id);
|
|
74
80
|
return;
|
|
75
81
|
}
|
|
76
82
|
const normalizedTab = { ...tab, url: normalizedUrl };
|
|
77
|
-
|
|
83
|
+
retainedTabByIdentity.set(identity, normalizedTab);
|
|
78
84
|
retainedIdByOriginalId.set(tab.id, normalizedTab.id);
|
|
79
85
|
});
|
|
80
|
-
restoredState.tabsList = Array.from(
|
|
86
|
+
restoredState.tabsList = Array.from(retainedTabByIdentity.values());
|
|
81
87
|
restoredState.activeKey = retainedIdByOriginalId.get(restoredState.activeKey) || ((_a = restoredState.tabsList[0]) == null ? void 0 : _a.id) || "";
|
|
82
88
|
}
|
|
83
89
|
restoredState.activeTabInfo = restoredState.tabsList.find(
|
|
@@ -87,7 +93,7 @@ const useTabsState = (options) => {
|
|
|
87
93
|
setState(restoredState);
|
|
88
94
|
}
|
|
89
95
|
setIsInitialized(true);
|
|
90
|
-
}, [restoreFromCache, urlSyncEnabled]);
|
|
96
|
+
}, [restoreFromCache, urlSyncEnabled, urlSyncIdentity]);
|
|
91
97
|
const lastActiveKeyRef = useRef("");
|
|
92
98
|
const shouldSkipSaveRef = useRef(false);
|
|
93
99
|
const initialTabsAppliedRef = useRef(false);
|
|
@@ -125,7 +131,8 @@ const useTabsState = (options) => {
|
|
|
125
131
|
}
|
|
126
132
|
if (urlSyncEnabled || !forceNew) {
|
|
127
133
|
const existingTab = findExistingTab(state.tabsList, menuItem, {
|
|
128
|
-
urlSync: urlSyncEnabled
|
|
134
|
+
urlSync: urlSyncEnabled,
|
|
135
|
+
identity: urlSyncIdentity
|
|
129
136
|
});
|
|
130
137
|
if (existingTab) {
|
|
131
138
|
return true;
|
|
@@ -136,7 +143,7 @@ const useTabsState = (options) => {
|
|
|
136
143
|
}
|
|
137
144
|
return true;
|
|
138
145
|
},
|
|
139
|
-
[state.tabsList, finalConfig, urlSyncEnabled]
|
|
146
|
+
[state.tabsList, finalConfig, urlSyncEnabled, urlSyncIdentity]
|
|
140
147
|
);
|
|
141
148
|
const addTab = useCallback(
|
|
142
149
|
(menuItem, options2) => {
|
|
@@ -161,9 +168,18 @@ const useTabsState = (options) => {
|
|
|
161
168
|
return prevState;
|
|
162
169
|
}
|
|
163
170
|
const resolvedMenuItem = normalizedUrl ? { ...menuItem, url: normalizedUrl, redirectUrl: void 0 } : menuItem;
|
|
164
|
-
const existingTab = urlSyncEnabled || !forceNew ? findExistingTab(prevState.tabsList, resolvedMenuItem, {
|
|
171
|
+
const existingTab = urlSyncEnabled || !forceNew ? findExistingTab(prevState.tabsList, resolvedMenuItem, {
|
|
172
|
+
urlSync: urlSyncEnabled,
|
|
173
|
+
identity: urlSyncIdentity
|
|
174
|
+
}) : void 0;
|
|
165
175
|
if (existingTab) {
|
|
166
|
-
const
|
|
176
|
+
const shouldFixClosable = resolvedMenuItem.closable === false && existingTab.closable !== false;
|
|
177
|
+
const shouldUpdateUrl = urlSyncIdentity === "relationKey" && normalizedUrl !== void 0 && existingTab.url !== normalizedUrl;
|
|
178
|
+
const correctedTab = shouldFixClosable || shouldUpdateUrl ? {
|
|
179
|
+
...existingTab,
|
|
180
|
+
...shouldFixClosable ? { closable: false } : {},
|
|
181
|
+
...shouldUpdateUrl ? { url: normalizedUrl } : {}
|
|
182
|
+
} : existingTab;
|
|
167
183
|
const updatedTabsList = correctedTab !== existingTab ? prevState.tabsList.map((t) => t.id === existingTab.id ? correctedTab : t) : prevState.tabsList;
|
|
168
184
|
return {
|
|
169
185
|
...prevState,
|
|
@@ -200,7 +216,7 @@ const useTabsState = (options) => {
|
|
|
200
216
|
};
|
|
201
217
|
});
|
|
202
218
|
},
|
|
203
|
-
[finalConfig, urlSyncEnabled]
|
|
219
|
+
[finalConfig, urlSyncEnabled, urlSyncIdentity]
|
|
204
220
|
);
|
|
205
221
|
useEffect(() => {
|
|
206
222
|
const initialTabs = finalConfig.initialTabs;
|
|
@@ -13,7 +13,7 @@ import { createPortal } from "react-dom";
|
|
|
13
13
|
import { useTabsState } from "./hooks/useTabsState";
|
|
14
14
|
import { useTabsUrlSync } from "./hooks/useTabsUrlSync";
|
|
15
15
|
import { useIframeRoute } from "./hooks/useIframeRoute";
|
|
16
|
-
import { flattenMenuData, resolveTabMenuItem, derivePinned } from "./utils";
|
|
16
|
+
import { flattenMenuData, resolveTabMenuItem, derivePinned, normalizeTabUrl } from "./utils";
|
|
17
17
|
import { TabItemComponent } from "./components/TabItem";
|
|
18
18
|
import { TabsHeader } from "./components/TabsHeader";
|
|
19
19
|
import { TabsContext } from "./components/TabsContext";
|
|
@@ -234,6 +234,13 @@ const TabsManager = forwardRef(
|
|
|
234
234
|
removeTab(tabId);
|
|
235
235
|
},
|
|
236
236
|
updateTab,
|
|
237
|
+
updateTabUrl: (tabId, url) => {
|
|
238
|
+
const normalizedUrl = normalizeTabUrl(url);
|
|
239
|
+
if (!normalizedUrl)
|
|
240
|
+
return;
|
|
241
|
+
markTabInteraction();
|
|
242
|
+
updateTabUrl(tabId, normalizedUrl);
|
|
243
|
+
},
|
|
237
244
|
getTabInfo: () => ({
|
|
238
245
|
tabsList: state.tabsList,
|
|
239
246
|
activeTabInfo: state.tabsList.find((tab) => tab.id === state.activeKey),
|
|
@@ -248,6 +255,7 @@ const TabsManager = forwardRef(
|
|
|
248
255
|
markTabInteraction,
|
|
249
256
|
removeTab,
|
|
250
257
|
updateTab,
|
|
258
|
+
updateTabUrl,
|
|
251
259
|
state.tabsList,
|
|
252
260
|
state.activeKey,
|
|
253
261
|
state.activeComponent,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddTabParams, TabItem, MenusType } from '../../../propTypes';
|
|
1
|
+
import { AddTabParams, TabItem, MenusType, TabsUrlSyncConfig } from '../../../propTypes';
|
|
2
2
|
/**
|
|
3
3
|
* 获取菜单实际用于路由/签页的路径
|
|
4
4
|
* 约定优先使用 redirectUrl,其次回退 url
|
|
@@ -17,6 +17,7 @@ export declare const getCurrentTabUrl: () => string;
|
|
|
17
17
|
export declare const getTabUrlPathname: (value?: string) => string | undefined;
|
|
18
18
|
export interface FindExistingTabOptions {
|
|
19
19
|
urlSync?: boolean;
|
|
20
|
+
identity?: TabsUrlSyncConfig['identity'];
|
|
20
21
|
}
|
|
21
22
|
export declare const findExistingTab: (tabsList: TabItem[], menuItem: MenusType, options?: FindExistingTabOptions) => TabItem | undefined;
|
|
22
23
|
/**
|
|
@@ -37,6 +37,14 @@ const getTabUrlPathname = (value) => {
|
|
|
37
37
|
};
|
|
38
38
|
const findExistingTab = (tabsList, menuItem, options = {}) => {
|
|
39
39
|
if (options.urlSync) {
|
|
40
|
+
if (options.identity === "relationKey" && hasRelationKey(menuItem.relationKey)) {
|
|
41
|
+
return tabsList.find(
|
|
42
|
+
(tab) => {
|
|
43
|
+
var _a;
|
|
44
|
+
return tab.relationKey === menuItem.relationKey || ((_a = tab.menuItem) == null ? void 0 : _a.relationKey) === menuItem.relationKey;
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
40
48
|
const targetUrl = normalizeTabUrl(getMenuRoute(menuItem));
|
|
41
49
|
if (!targetUrl)
|
|
42
50
|
return void 0;
|
|
@@ -368,6 +368,11 @@ export interface TabsUrlSyncConfig {
|
|
|
368
368
|
* @default 'replace'
|
|
369
369
|
*/
|
|
370
370
|
history?: 'replace' | 'push';
|
|
371
|
+
/**
|
|
372
|
+
* @description 页签复用身份。url 保持既有完整 URL 唯一语义;relationKey 在显式传入时优先按业务关联键复用
|
|
373
|
+
* @default 'url'
|
|
374
|
+
*/
|
|
375
|
+
identity?: 'url' | 'relationKey';
|
|
371
376
|
}
|
|
372
377
|
/**
|
|
373
378
|
* @description 添加标签页的业务参数
|
|
@@ -468,6 +473,10 @@ export interface ProLayoutTabsInstance {
|
|
|
468
473
|
* @param updates 仅更新传入的字段,extra 为替换式覆盖
|
|
469
474
|
*/
|
|
470
475
|
updateTab: (tabId: string, updates: UpdateTabParams) => void;
|
|
476
|
+
/**
|
|
477
|
+
* @description 就地更新页签完整 URL,保持 tab id 与已挂载组件不变
|
|
478
|
+
*/
|
|
479
|
+
updateTabUrl: (tabId: string, url: string) => void;
|
|
471
480
|
/**
|
|
472
481
|
* @description 获取标签页信息
|
|
473
482
|
*/
|