@zat-design/sisyphus-react 4.6.0-beta.2 → 4.6.0-beta.4
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/ProLayout/components/TabsManager/hooks/useTabsState.js +27 -17
- package/es/ProLayout/components/TabsManager/hooks/useTabsUrlSync.js +47 -17
- package/es/ProLayout/components/TabsManager/style/index.less +7 -7
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +7 -0
- package/es/ProLayout/components/TabsManager/utils/index.js +93 -8
- package/es/ProLayout/propTypes.d.ts +11 -2
- package/es/ProLayout/style/index.less +6 -22
- package/package.json +1 -1
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
flattenMenuData,
|
|
14
14
|
canOpenAsTab,
|
|
15
15
|
getMenuRoute,
|
|
16
|
-
hasRelationKey,
|
|
17
16
|
findExistingTab,
|
|
17
|
+
getUrlSyncTabIdentity,
|
|
18
|
+
getComparableTabUrl,
|
|
18
19
|
normalizeTabUrl,
|
|
19
20
|
isSafeExternalUrl,
|
|
20
21
|
isSafeHttpUrl,
|
|
@@ -30,6 +31,7 @@ const useTabsState = (options) => {
|
|
|
30
31
|
const finalConfig = { ...DEFAULT_TABS_CONFIG, ...config };
|
|
31
32
|
const urlSyncEnabled = finalConfig.urlSync === true || typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null;
|
|
32
33
|
const urlSyncIdentity = typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null ? finalConfig.urlSync.identity || "url" : "url";
|
|
34
|
+
const urlSyncBasename = typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null ? finalConfig.urlSync.basename : void 0;
|
|
33
35
|
const [state, setState] = useState(() => ({
|
|
34
36
|
tabsList: [],
|
|
35
37
|
activeKey: "",
|
|
@@ -67,15 +69,20 @@ const useTabsState = (options) => {
|
|
|
67
69
|
const retainedTabByIdentity = /* @__PURE__ */ new Map();
|
|
68
70
|
const retainedIdByOriginalId = /* @__PURE__ */ new Map();
|
|
69
71
|
restoredState.tabsList.forEach((tab) => {
|
|
70
|
-
var _a2;
|
|
71
72
|
const normalizedUrl = normalizeTabUrl(tab.url);
|
|
72
73
|
if (!normalizedUrl)
|
|
73
74
|
return;
|
|
74
|
-
const
|
|
75
|
-
|
|
75
|
+
const comparableUrl = getComparableTabUrl(normalizedUrl, urlSyncBasename);
|
|
76
|
+
if (!comparableUrl)
|
|
77
|
+
return;
|
|
78
|
+
const identity = getUrlSyncTabIdentity(
|
|
79
|
+
{ ...tab, url: normalizedUrl },
|
|
80
|
+
{ identity: urlSyncIdentity, basename: urlSyncBasename }
|
|
81
|
+
);
|
|
82
|
+
if (!identity)
|
|
83
|
+
return;
|
|
76
84
|
const retainedTab = retainedTabByIdentity.get(identity);
|
|
77
85
|
if (retainedTab) {
|
|
78
|
-
retainedTabByIdentity.set(identity, { ...retainedTab, url: normalizedUrl });
|
|
79
86
|
retainedIdByOriginalId.set(tab.id, retainedTab.id);
|
|
80
87
|
return;
|
|
81
88
|
}
|
|
@@ -93,7 +100,7 @@ const useTabsState = (options) => {
|
|
|
93
100
|
setState(restoredState);
|
|
94
101
|
}
|
|
95
102
|
setIsInitialized(true);
|
|
96
|
-
}, [restoreFromCache, urlSyncEnabled, urlSyncIdentity]);
|
|
103
|
+
}, [restoreFromCache, urlSyncEnabled, urlSyncIdentity, urlSyncBasename]);
|
|
97
104
|
const lastActiveKeyRef = useRef("");
|
|
98
105
|
const shouldSkipSaveRef = useRef(false);
|
|
99
106
|
const initialTabsAppliedRef = useRef(false);
|
|
@@ -132,7 +139,8 @@ const useTabsState = (options) => {
|
|
|
132
139
|
if (urlSyncEnabled || !forceNew) {
|
|
133
140
|
const existingTab = findExistingTab(state.tabsList, menuItem, {
|
|
134
141
|
urlSync: urlSyncEnabled,
|
|
135
|
-
identity: urlSyncIdentity
|
|
142
|
+
identity: urlSyncIdentity,
|
|
143
|
+
basename: urlSyncBasename
|
|
136
144
|
});
|
|
137
145
|
if (existingTab) {
|
|
138
146
|
return true;
|
|
@@ -143,7 +151,7 @@ const useTabsState = (options) => {
|
|
|
143
151
|
}
|
|
144
152
|
return true;
|
|
145
153
|
},
|
|
146
|
-
[state.tabsList, finalConfig, urlSyncEnabled, urlSyncIdentity]
|
|
154
|
+
[state.tabsList, finalConfig, urlSyncEnabled, urlSyncIdentity, urlSyncBasename]
|
|
147
155
|
);
|
|
148
156
|
const addTab = useCallback(
|
|
149
157
|
(menuItem, options2) => {
|
|
@@ -170,16 +178,12 @@ const useTabsState = (options) => {
|
|
|
170
178
|
const resolvedMenuItem = normalizedUrl ? { ...menuItem, url: normalizedUrl, redirectUrl: void 0 } : menuItem;
|
|
171
179
|
const existingTab = urlSyncEnabled || !forceNew ? findExistingTab(prevState.tabsList, resolvedMenuItem, {
|
|
172
180
|
urlSync: urlSyncEnabled,
|
|
173
|
-
identity: urlSyncIdentity
|
|
181
|
+
identity: urlSyncIdentity,
|
|
182
|
+
basename: urlSyncBasename
|
|
174
183
|
}) : void 0;
|
|
175
184
|
if (existingTab) {
|
|
176
185
|
const shouldFixClosable = resolvedMenuItem.closable === false && existingTab.closable !== false;
|
|
177
|
-
const
|
|
178
|
-
const correctedTab = shouldFixClosable || shouldUpdateUrl ? {
|
|
179
|
-
...existingTab,
|
|
180
|
-
...shouldFixClosable ? { closable: false } : {},
|
|
181
|
-
...shouldUpdateUrl ? { url: normalizedUrl } : {}
|
|
182
|
-
} : existingTab;
|
|
186
|
+
const correctedTab = shouldFixClosable ? { ...existingTab, closable: false } : existingTab;
|
|
183
187
|
const updatedTabsList = correctedTab !== existingTab ? prevState.tabsList.map((t) => t.id === existingTab.id ? correctedTab : t) : prevState.tabsList;
|
|
184
188
|
return {
|
|
185
189
|
...prevState,
|
|
@@ -216,7 +220,7 @@ const useTabsState = (options) => {
|
|
|
216
220
|
};
|
|
217
221
|
});
|
|
218
222
|
},
|
|
219
|
-
[finalConfig, urlSyncEnabled, urlSyncIdentity]
|
|
223
|
+
[finalConfig, urlSyncEnabled, urlSyncIdentity, urlSyncBasename]
|
|
220
224
|
);
|
|
221
225
|
useEffect(() => {
|
|
222
226
|
const initialTabs = finalConfig.initialTabs;
|
|
@@ -250,7 +254,10 @@ const useTabsState = (options) => {
|
|
|
250
254
|
...updates.icon !== void 0 && { icon: updates.icon },
|
|
251
255
|
...updates.extra !== void 0 && { extra: updates.extra },
|
|
252
256
|
...updates.closable !== void 0 && { closable: updates.closable },
|
|
253
|
-
...updates.pinned !== void 0 && { pinned: updates.pinned }
|
|
257
|
+
...updates.pinned !== void 0 && { pinned: updates.pinned },
|
|
258
|
+
...updates.menuCodeSource !== void 0 && {
|
|
259
|
+
menuCodeSource: updates.menuCodeSource
|
|
260
|
+
}
|
|
254
261
|
} : target.menuItem;
|
|
255
262
|
const nextTab = {
|
|
256
263
|
...target,
|
|
@@ -258,6 +265,9 @@ const useTabsState = (options) => {
|
|
|
258
265
|
...updates.icon !== void 0 && { icon: updates.icon },
|
|
259
266
|
...updates.closable !== void 0 && { closable: updates.closable },
|
|
260
267
|
...updates.pinned !== void 0 && { pinned: updates.pinned },
|
|
268
|
+
...updates.menuCodeSource !== void 0 && {
|
|
269
|
+
menuCodeSource: updates.menuCodeSource
|
|
270
|
+
},
|
|
261
271
|
menuItem: nextMenuItem
|
|
262
272
|
};
|
|
263
273
|
const newTabsList = [...prevState.tabsList];
|
|
@@ -3,6 +3,9 @@ import * as ReactRouterDom from "react-router-dom";
|
|
|
3
3
|
import {
|
|
4
4
|
canOpenAsTab,
|
|
5
5
|
flattenMenuData,
|
|
6
|
+
findExistingTab,
|
|
7
|
+
getBrowserTabUrl,
|
|
8
|
+
getComparableTabUrl,
|
|
6
9
|
getCurrentTabUrl,
|
|
7
10
|
getMenuRoute,
|
|
8
11
|
getTabUrlPathname,
|
|
@@ -92,6 +95,8 @@ const useTabsUrlSync = ({
|
|
|
92
95
|
const navigator = navigationContext == null ? void 0 : navigationContext.navigator;
|
|
93
96
|
const enabled = config.urlSync === true || typeof config.urlSync === "object" && config.urlSync !== null;
|
|
94
97
|
const historyMode = typeof config.urlSync === "object" ? config.urlSync.history || "replace" : "replace";
|
|
98
|
+
const basename = typeof config.urlSync === "object" ? config.urlSync.basename : void 0;
|
|
99
|
+
const identity = typeof config.urlSync === "object" ? config.urlSync.identity : void 0;
|
|
95
100
|
const bootstrappedRef = useRef(false);
|
|
96
101
|
const writingUrlRef = useRef(false);
|
|
97
102
|
const preserveUrlInteractionRef = useRef(null);
|
|
@@ -156,11 +161,14 @@ const useTabsUrlSync = ({
|
|
|
156
161
|
if (!enabled || !isInitialized || typeof window === "undefined")
|
|
157
162
|
return;
|
|
158
163
|
const currentUrl = normalizeTabUrl(locationChange.url);
|
|
159
|
-
|
|
164
|
+
const comparableCurrentUrl = getComparableTabUrl(currentUrl, basename);
|
|
165
|
+
if (!currentUrl || !comparableCurrentUrl)
|
|
160
166
|
return;
|
|
161
167
|
if (handledLocationSequenceRef.current === locationChange.sequence)
|
|
162
168
|
return;
|
|
163
|
-
const exactTab = state.tabsList.find(
|
|
169
|
+
const exactTab = state.tabsList.find(
|
|
170
|
+
(tab) => getComparableTabUrl(tab.url, basename) === comparableCurrentUrl
|
|
171
|
+
);
|
|
164
172
|
if (exactTab) {
|
|
165
173
|
handledLocationSequenceRef.current = locationChange.sequence;
|
|
166
174
|
bootstrappedRef.current = true;
|
|
@@ -170,16 +178,16 @@ const useTabsUrlSync = ({
|
|
|
170
178
|
}
|
|
171
179
|
return;
|
|
172
180
|
}
|
|
173
|
-
const currentPathname = getTabUrlPathname(
|
|
181
|
+
const currentPathname = getTabUrlPathname(comparableCurrentUrl);
|
|
174
182
|
const sourceMenus = getSourceMenus(dataSource);
|
|
175
183
|
const activeTab = state.tabsList.find((tab) => tab.id === state.activeKey);
|
|
176
184
|
const activeCode = ((_a = activeTab == null ? void 0 : activeTab.menuItem) == null ? void 0 : _a.code) || (activeTab == null ? void 0 : activeTab.code);
|
|
177
185
|
const activeSourceMenu = activeCode ? sourceMenus.find((item) => item.code === activeCode) : void 0;
|
|
178
186
|
const activeMenuItem = activeSourceMenu || (activeTab == null ? void 0 : activeTab.menuItem);
|
|
179
|
-
const activeBasePathname = activeMenuItem ? getTabUrlPathname(getMenuRoute(activeMenuItem)) : getTabUrlPathname(activeTab == null ? void 0 : activeTab.url);
|
|
187
|
+
const activeBasePathname = activeMenuItem ? getTabUrlPathname(getComparableTabUrl(getMenuRoute(activeMenuItem), basename)) : getTabUrlPathname(getComparableTabUrl(activeTab == null ? void 0 : activeTab.url, basename));
|
|
180
188
|
const targetMenu = (_b = sourceMenus.map((item) => ({
|
|
181
189
|
item,
|
|
182
|
-
pathname: getTabUrlPathname(getMenuRoute(item))
|
|
190
|
+
pathname: getTabUrlPathname(getComparableTabUrl(getMenuRoute(item), basename))
|
|
183
191
|
})).filter(
|
|
184
192
|
(entry) => !!entry.pathname && isSameOrNestedPath(entry.pathname, currentPathname)
|
|
185
193
|
).sort((left, right) => right.pathname.length - left.pathname.length)[0]) == null ? void 0 : _b.item;
|
|
@@ -195,7 +203,21 @@ const useTabsUrlSync = ({
|
|
|
195
203
|
handledLocationSequenceRef.current = locationChange.sequence;
|
|
196
204
|
bootstrappedRef.current = true;
|
|
197
205
|
preserveUrlInteractionRef.current = interactionSequence;
|
|
198
|
-
|
|
206
|
+
const resolvedTargetMenu = { ...targetMenu, url: currentUrl, redirectUrl: void 0 };
|
|
207
|
+
const existingTargetTab = findExistingTab(state.tabsList, resolvedTargetMenu, {
|
|
208
|
+
urlSync: true,
|
|
209
|
+
identity,
|
|
210
|
+
basename
|
|
211
|
+
});
|
|
212
|
+
if (existingTargetTab) {
|
|
213
|
+
if (getComparableTabUrl(existingTargetTab.url, basename) !== comparableCurrentUrl) {
|
|
214
|
+
updateTabUrl(existingTargetTab.id, currentUrl);
|
|
215
|
+
}
|
|
216
|
+
if (state.activeKey !== existingTargetTab.id)
|
|
217
|
+
switchTab(existingTargetTab.id);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
addTab(resolvedTargetMenu);
|
|
199
221
|
return;
|
|
200
222
|
}
|
|
201
223
|
if (!bootstrappedRef.current) {
|
|
@@ -223,7 +245,9 @@ const useTabsUrlSync = ({
|
|
|
223
245
|
addTab,
|
|
224
246
|
switchTab,
|
|
225
247
|
updateTabUrl,
|
|
226
|
-
interactionSequence
|
|
248
|
+
interactionSequence,
|
|
249
|
+
basename,
|
|
250
|
+
identity
|
|
227
251
|
]);
|
|
228
252
|
useEffect(() => {
|
|
229
253
|
if (!enabled || !isInitialized || !bootstrappedRef.current || typeof window === "undefined") {
|
|
@@ -231,13 +255,14 @@ const useTabsUrlSync = ({
|
|
|
231
255
|
}
|
|
232
256
|
const activeTab = state.tabsList.find((tab) => tab.id === state.activeKey);
|
|
233
257
|
const targetUrl = normalizeTabUrl(activeTab == null ? void 0 : activeTab.url);
|
|
258
|
+
const browserTargetUrl = getBrowserTabUrl(targetUrl, basename);
|
|
234
259
|
const commitActiveTab = () => {
|
|
235
260
|
committedActiveKeyRef.current = state.activeKey;
|
|
236
261
|
committedTabIdsRef.current = new Set(state.tabsList.map((tab) => tab.id));
|
|
237
262
|
};
|
|
238
263
|
if (preserveUrlInteractionRef.current !== null) {
|
|
239
264
|
if (preserveUrlInteractionRef.current === interactionSequence) {
|
|
240
|
-
if (
|
|
265
|
+
if (browserTargetUrl === getCurrentTabUrl()) {
|
|
241
266
|
preserveUrlInteractionRef.current = null;
|
|
242
267
|
}
|
|
243
268
|
commitActiveTab();
|
|
@@ -245,7 +270,7 @@ const useTabsUrlSync = ({
|
|
|
245
270
|
}
|
|
246
271
|
preserveUrlInteractionRef.current = null;
|
|
247
272
|
}
|
|
248
|
-
if (!targetUrl ||
|
|
273
|
+
if (!targetUrl || !browserTargetUrl || browserTargetUrl === getCurrentTabUrl()) {
|
|
249
274
|
const pendingNavigation = pendingDataNavigationRef.current;
|
|
250
275
|
navigationAttemptRef.current += 1;
|
|
251
276
|
pendingDataNavigationRef.current = false;
|
|
@@ -286,30 +311,34 @@ const useTabsUrlSync = ({
|
|
|
286
311
|
pendingDataNavigationRef.current = false;
|
|
287
312
|
pendingDataNavigationUrlRef.current = null;
|
|
288
313
|
const currentUrl = getCurrentTabUrl();
|
|
289
|
-
if (
|
|
314
|
+
if (browserTargetUrl === currentUrl || committedDataNavigationUrl === currentUrl) {
|
|
290
315
|
commitActiveTab();
|
|
291
316
|
return;
|
|
292
317
|
}
|
|
293
318
|
rollbackTabActivation(previousActiveKey, attemptedActiveKey, removeAttemptedTab);
|
|
294
319
|
};
|
|
295
320
|
pendingDataNavigationRef.current = true;
|
|
296
|
-
pendingDataNavigationUrlRef.current =
|
|
297
|
-
void dataRouterContext.router.navigate(getDataRouterUrl(
|
|
321
|
+
pendingDataNavigationUrlRef.current = browserTargetUrl;
|
|
322
|
+
void dataRouterContext.router.navigate(getDataRouterUrl(browserTargetUrl, dataRouterContext.basename), {
|
|
298
323
|
replace: historyMode === "replace",
|
|
299
324
|
state: routerState
|
|
300
325
|
}).then(settleNavigation);
|
|
301
326
|
} else if (navigator) {
|
|
302
327
|
if (historyMode === "push") {
|
|
303
|
-
navigator.push(
|
|
328
|
+
navigator.push(browserTargetUrl, routerState);
|
|
304
329
|
} else {
|
|
305
|
-
navigator.replace(
|
|
330
|
+
navigator.replace(browserTargetUrl, routerState);
|
|
306
331
|
}
|
|
307
332
|
commitActiveTab();
|
|
308
333
|
} else if (historyMode === "push") {
|
|
309
|
-
window.history.pushState(
|
|
334
|
+
window.history.pushState(
|
|
335
|
+
createPushHistoryState(window.history.state),
|
|
336
|
+
"",
|
|
337
|
+
browserTargetUrl
|
|
338
|
+
);
|
|
310
339
|
commitActiveTab();
|
|
311
340
|
} else {
|
|
312
|
-
window.history.replaceState(window.history.state, "",
|
|
341
|
+
window.history.replaceState(window.history.state, "", browserTargetUrl);
|
|
313
342
|
commitActiveTab();
|
|
314
343
|
}
|
|
315
344
|
} finally {
|
|
@@ -324,7 +353,8 @@ const useTabsUrlSync = ({
|
|
|
324
353
|
interactionSequence,
|
|
325
354
|
navigator,
|
|
326
355
|
dataRouterContext,
|
|
327
|
-
rollbackTabActivation
|
|
356
|
+
rollbackTabActivation,
|
|
357
|
+
basename
|
|
328
358
|
]);
|
|
329
359
|
};
|
|
330
360
|
export {
|
|
@@ -86,13 +86,6 @@
|
|
|
86
86
|
border-bottom: none;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// 横线右端缩短,空出圆角半径,让横线在圆弧起点处结束
|
|
90
|
-
// 用属性选择器提升权重,确保覆盖 antd :where() 规则
|
|
91
|
-
.@{ant-prefix}-tabs-nav[class]::before {
|
|
92
|
-
right: var(--zaui-border-radius, 8px) !important;
|
|
93
|
-
left: 0 !important;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
89
|
// 覆盖 nav-list 样式,使其匹配 pro-layout-tab-list
|
|
97
90
|
.@{ant-prefix}-tabs-nav-list {
|
|
98
91
|
display: flex;
|
|
@@ -167,6 +160,13 @@
|
|
|
167
160
|
|
|
168
161
|
.pro-layout-tabs-antd {
|
|
169
162
|
&.@{ant-prefix}-tabs {
|
|
163
|
+
// TabsHeader 通过 Portal 渲染到 ProLayout 挂载点,需要在全局作用域截断横线。
|
|
164
|
+
// 右侧空出一个圆角半径,与内容区圆弧的顶部边线无缝连接。
|
|
165
|
+
.@{ant-prefix}-tabs-nav[class]::before {
|
|
166
|
+
right: var(--zaui-border-radius, 8px) !important;
|
|
167
|
+
left: 0 !important;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
170
|
.@{ant-prefix}-tabs-tab {
|
|
171
171
|
padding: 4px 0 5px !important;
|
|
172
172
|
margin-right: 8px !important;
|
|
@@ -14,11 +14,18 @@ export declare const normalizeTabUrl: (value?: string) => string | undefined;
|
|
|
14
14
|
export declare const isSafeExternalUrl: (value?: string) => boolean;
|
|
15
15
|
export declare const isSafeHttpUrl: (value?: string) => boolean;
|
|
16
16
|
export declare const getCurrentTabUrl: () => string;
|
|
17
|
+
/** 将浏览器地址和 Tab 内部地址转为同一身份,仅按完整路径段移除 basename。 */
|
|
18
|
+
export declare const getComparableTabUrl: (value?: string, basename?: string) => string | undefined;
|
|
19
|
+
/** 将 Tab 内部地址转为浏览器地址,已带 basename 时不重复添加。 */
|
|
20
|
+
export declare const getBrowserTabUrl: (value?: string, basename?: string) => string | undefined;
|
|
17
21
|
export declare const getTabUrlPathname: (value?: string) => string | undefined;
|
|
18
22
|
export interface FindExistingTabOptions {
|
|
19
23
|
urlSync?: boolean;
|
|
20
24
|
identity?: TabsUrlSyncConfig['identity'];
|
|
25
|
+
basename?: TabsUrlSyncConfig['basename'];
|
|
21
26
|
}
|
|
27
|
+
/** URL 同步模式的唯一身份;relationKey 模式兼容无关联键的旧菜单 code。 */
|
|
28
|
+
export declare const getUrlSyncTabIdentity: (item: MenusType | TabItem, options?: Pick<FindExistingTabOptions, 'identity' | 'basename'>) => string | undefined;
|
|
22
29
|
export declare const findExistingTab: (tabsList: TabItem[], menuItem: MenusType, options?: FindExistingTabOptions) => TabItem | undefined;
|
|
23
30
|
/**
|
|
24
31
|
* 根据菜单项生成TabItem
|
|
@@ -29,26 +29,108 @@ const isSafeExternalUrl = (value) => {
|
|
|
29
29
|
};
|
|
30
30
|
const isSafeHttpUrl = (value) => !!resolveHttpUrl(value);
|
|
31
31
|
const getCurrentTabUrl = () => `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
32
|
+
const normalizeTabsBasename = (basename) => {
|
|
33
|
+
const normalized = String(basename ?? "").trim();
|
|
34
|
+
if (!normalized || normalized === "/")
|
|
35
|
+
return "";
|
|
36
|
+
return `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
37
|
+
};
|
|
38
|
+
const splitNormalizedTabUrl = (url) => {
|
|
39
|
+
const pathnameEnd = [url.indexOf("?"), url.indexOf("#")].filter((index) => index >= 0).reduce((current, index) => Math.min(current, index), url.length);
|
|
40
|
+
return {
|
|
41
|
+
pathname: url.slice(0, pathnameEnd),
|
|
42
|
+
suffix: url.slice(pathnameEnd)
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const hasPathBasename = (pathname, basename) => {
|
|
46
|
+
const lowerPathname = pathname.toLowerCase();
|
|
47
|
+
const lowerBasename = basename.toLowerCase();
|
|
48
|
+
return lowerPathname === lowerBasename || lowerPathname.startsWith(`${lowerBasename}/`);
|
|
49
|
+
};
|
|
50
|
+
const getComparableTabUrl = (value, basename) => {
|
|
51
|
+
const normalizedUrl = normalizeTabUrl(value);
|
|
52
|
+
if (!normalizedUrl)
|
|
53
|
+
return void 0;
|
|
54
|
+
const normalizedBasename = normalizeTabsBasename(basename);
|
|
55
|
+
if (!normalizedBasename)
|
|
56
|
+
return normalizedUrl;
|
|
57
|
+
const { pathname, suffix } = splitNormalizedTabUrl(normalizedUrl);
|
|
58
|
+
if (!hasPathBasename(pathname, normalizedBasename))
|
|
59
|
+
return normalizedUrl;
|
|
60
|
+
const barePathname = pathname.slice(normalizedBasename.length) || "/";
|
|
61
|
+
return `${barePathname}${suffix}`;
|
|
62
|
+
};
|
|
63
|
+
const getBrowserTabUrl = (value, basename) => {
|
|
64
|
+
const normalizedUrl = normalizeTabUrl(value);
|
|
65
|
+
if (!normalizedUrl)
|
|
66
|
+
return void 0;
|
|
67
|
+
const normalizedBasename = normalizeTabsBasename(basename);
|
|
68
|
+
if (!normalizedBasename)
|
|
69
|
+
return normalizedUrl;
|
|
70
|
+
const { pathname, suffix } = splitNormalizedTabUrl(normalizedUrl);
|
|
71
|
+
if (hasPathBasename(pathname, normalizedBasename))
|
|
72
|
+
return normalizedUrl;
|
|
73
|
+
return `${normalizedBasename}${pathname}${suffix}`;
|
|
74
|
+
};
|
|
32
75
|
const getTabUrlPathname = (value) => {
|
|
33
76
|
const normalized = normalizeTabUrl(value);
|
|
34
77
|
if (!normalized || typeof window === "undefined")
|
|
35
78
|
return void 0;
|
|
36
79
|
return new URL(normalized, window.location.origin).pathname;
|
|
37
80
|
};
|
|
81
|
+
const getIdentityRelationKey = (item) => {
|
|
82
|
+
var _a;
|
|
83
|
+
return item.relationKey ?? ("menuItem" in item ? (_a = item.menuItem) == null ? void 0 : _a.relationKey : void 0);
|
|
84
|
+
};
|
|
85
|
+
const getIdentityCode = (item) => {
|
|
86
|
+
var _a;
|
|
87
|
+
return item.code ?? ("menuItem" in item ? (_a = item.menuItem) == null ? void 0 : _a.code : void 0);
|
|
88
|
+
};
|
|
89
|
+
const getTypedIdentity = (prefix, value) => `${prefix}:${typeof value}:${String(value)}`;
|
|
90
|
+
const isTabItem = (item) => typeof item.id === "string";
|
|
91
|
+
const getUrlSyncTabIdentity = (item, options = {}) => {
|
|
92
|
+
if (options.identity === "relationKey") {
|
|
93
|
+
const relationKey = getIdentityRelationKey(item);
|
|
94
|
+
if (hasRelationKey(relationKey))
|
|
95
|
+
return getTypedIdentity("relationKey", relationKey);
|
|
96
|
+
const code = getIdentityCode(item);
|
|
97
|
+
if (code !== void 0 && code !== null && code !== "") {
|
|
98
|
+
return getTypedIdentity("code", code);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const route = isTabItem(item) ? item.url : getMenuRoute(item);
|
|
102
|
+
const comparableUrl = getComparableTabUrl(route, options.basename);
|
|
103
|
+
return comparableUrl ? `url:${comparableUrl}` : void 0;
|
|
104
|
+
};
|
|
38
105
|
const findExistingTab = (tabsList, menuItem, options = {}) => {
|
|
39
106
|
if (options.urlSync) {
|
|
40
|
-
if (options.identity === "relationKey"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
107
|
+
if (options.identity === "relationKey") {
|
|
108
|
+
const relationKey = getIdentityRelationKey(menuItem);
|
|
109
|
+
if (hasRelationKey(relationKey)) {
|
|
110
|
+
const relatedTab = tabsList.find(
|
|
111
|
+
(tab) => getIdentityRelationKey(tab) === relationKey
|
|
112
|
+
);
|
|
113
|
+
if (relatedTab)
|
|
114
|
+
return relatedTab;
|
|
115
|
+
const legacyCode = getIdentityCode(menuItem);
|
|
116
|
+
if (legacyCode !== void 0 && legacyCode !== null && legacyCode !== "") {
|
|
117
|
+
return tabsList.find(
|
|
118
|
+
(tab) => !hasRelationKey(getIdentityRelationKey(tab)) && getIdentityCode(tab) === legacyCode
|
|
119
|
+
);
|
|
45
120
|
}
|
|
46
|
-
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
const code = getIdentityCode(menuItem);
|
|
124
|
+
if (code !== void 0 && code !== null && code !== "") {
|
|
125
|
+
return tabsList.find((tab) => getIdentityCode(tab) === code);
|
|
126
|
+
}
|
|
47
127
|
}
|
|
48
|
-
const targetUrl =
|
|
128
|
+
const targetUrl = getComparableTabUrl(getMenuRoute(menuItem), options.basename);
|
|
49
129
|
if (!targetUrl)
|
|
50
130
|
return void 0;
|
|
51
|
-
return tabsList.find(
|
|
131
|
+
return tabsList.find(
|
|
132
|
+
(tab) => getComparableTabUrl(tab.url, options.basename) === targetUrl
|
|
133
|
+
);
|
|
52
134
|
}
|
|
53
135
|
if (hasRelationKey(menuItem.relationKey)) {
|
|
54
136
|
return tabsList.find(
|
|
@@ -232,11 +314,14 @@ export {
|
|
|
232
314
|
findExistingTab,
|
|
233
315
|
flattenMenuData,
|
|
234
316
|
generateTabId,
|
|
317
|
+
getBrowserTabUrl,
|
|
318
|
+
getComparableTabUrl,
|
|
235
319
|
getCurrentTabUrl,
|
|
236
320
|
getMenuRoute,
|
|
237
321
|
getPinnedBoundary,
|
|
238
322
|
getRightTabs,
|
|
239
323
|
getTabUrlPathname,
|
|
324
|
+
getUrlSyncTabIdentity,
|
|
240
325
|
handleExternalOpen,
|
|
241
326
|
hasRelationKey,
|
|
242
327
|
isLeafMenuItem,
|
|
@@ -369,10 +369,15 @@ export interface TabsUrlSyncConfig {
|
|
|
369
369
|
*/
|
|
370
370
|
history?: 'replace' | 'push';
|
|
371
371
|
/**
|
|
372
|
-
* @description 页签复用身份。url
|
|
372
|
+
* @description 页签复用身份。url 保持完整 URL 唯一语义;relationKey 按关联键、无关联键的菜单 code、完整 URL 依次匹配
|
|
373
373
|
* @default 'url'
|
|
374
374
|
*/
|
|
375
375
|
identity?: 'url' | 'relationKey';
|
|
376
|
+
/**
|
|
377
|
+
* @description 浏览器地址中的显式基础路径;Tab 内部 URL 可以不带该前缀
|
|
378
|
+
* @example '/wjs'
|
|
379
|
+
*/
|
|
380
|
+
basename?: string;
|
|
376
381
|
}
|
|
377
382
|
/**
|
|
378
383
|
* @description 添加标签页的业务参数
|
|
@@ -420,7 +425,7 @@ export interface AddTabOptions {
|
|
|
420
425
|
silent?: boolean;
|
|
421
426
|
}
|
|
422
427
|
/**
|
|
423
|
-
* @description
|
|
428
|
+
* @description 更新标签页的展示、扩展数据与来源菜单身份
|
|
424
429
|
*/
|
|
425
430
|
export interface UpdateTabParams {
|
|
426
431
|
/**
|
|
@@ -443,6 +448,10 @@ export interface UpdateTabParams {
|
|
|
443
448
|
* @description 是否固定标签(本质状态,driven closable)
|
|
444
449
|
*/
|
|
445
450
|
pinned?: boolean;
|
|
451
|
+
/**
|
|
452
|
+
* @description 最初打开当前业务标签链路的左侧菜单 code;用于兼容性迁移已缓存的来源身份
|
|
453
|
+
*/
|
|
454
|
+
menuCodeSource?: string;
|
|
446
455
|
}
|
|
447
456
|
/**
|
|
448
457
|
* @description 获取标签页信息返回值
|
|
@@ -327,24 +327,6 @@
|
|
|
327
327
|
overflow: hidden;
|
|
328
328
|
min-height: 0;
|
|
329
329
|
}
|
|
330
|
-
|
|
331
|
-
// 稳定遮住 ant-tabs-nav::before 横线的右端,使其在内容区圆弧起点处截止
|
|
332
|
-
&.tabs-visible::before {
|
|
333
|
-
content: '';
|
|
334
|
-
position: absolute;
|
|
335
|
-
bottom: 0;
|
|
336
|
-
right: 0;
|
|
337
|
-
width: var(--zaui-border-radius, 8px);
|
|
338
|
-
height: 2px;
|
|
339
|
-
background-color: #f7f9fd;
|
|
340
|
-
background-image: var(--header-bg-image);
|
|
341
|
-
background-repeat: no-repeat;
|
|
342
|
-
background-position: top center;
|
|
343
|
-
background-size: 100% auto;
|
|
344
|
-
background-attachment: fixed;
|
|
345
|
-
z-index: 1;
|
|
346
|
-
pointer-events: none;
|
|
347
|
-
}
|
|
348
330
|
}
|
|
349
331
|
|
|
350
332
|
/** 内容区 */
|
|
@@ -370,17 +352,19 @@
|
|
|
370
352
|
border-top-color: transparent;
|
|
371
353
|
border-radius: 0;
|
|
372
354
|
padding-top: var(--zaui-space-size-md, 16px);
|
|
373
|
-
border-top-right-radius: var(--zaui-border-radius, 8px);
|
|
374
355
|
|
|
375
356
|
// 圆弧由内容区独立绘制并随内容滚动;sticky Tab 栏始终保持在其上方。
|
|
376
|
-
//
|
|
357
|
+
// 与内容区外边框对齐,同时遮住底层直角,避免缩放时出现断线或双重圆弧。
|
|
377
358
|
&::before {
|
|
378
359
|
content: '';
|
|
379
360
|
position: absolute;
|
|
380
|
-
|
|
381
|
-
|
|
361
|
+
box-sizing: border-box;
|
|
362
|
+
top: -1px;
|
|
363
|
+
right: -1px;
|
|
382
364
|
width: var(--zaui-border-radius, 8px);
|
|
383
365
|
height: var(--zaui-border-radius, 8px);
|
|
366
|
+
background: #ffffff;
|
|
367
|
+
border-top: 1px solid rgb(235 236 238);
|
|
384
368
|
border-right: 1px solid rgb(235 236 238);
|
|
385
369
|
border-top-right-radius: var(--zaui-border-radius, 8px);
|
|
386
370
|
pointer-events: none;
|