@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.
@@ -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 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}`;
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 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;
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
- if (!currentUrl)
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((tab) => normalizeTabUrl(tab.url) === currentUrl);
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(currentUrl);
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
- addTab({ ...targetMenu, url: currentUrl, redirectUrl: void 0 });
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 (targetUrl === getCurrentTabUrl()) {
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 || targetUrl === getCurrentTabUrl()) {
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 (targetUrl === currentUrl || committedDataNavigationUrl === currentUrl) {
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 = targetUrl;
297
- void dataRouterContext.router.navigate(getDataRouterUrl(targetUrl, dataRouterContext.basename), {
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(targetUrl, routerState);
328
+ navigator.push(browserTargetUrl, routerState);
304
329
  } else {
305
- navigator.replace(targetUrl, routerState);
330
+ navigator.replace(browserTargetUrl, routerState);
306
331
  }
307
332
  commitActiveTab();
308
333
  } else if (historyMode === "push") {
309
- window.history.pushState(createPushHistoryState(window.history.state), "", targetUrl);
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, "", targetUrl);
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" && 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;
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 = normalizeTabUrl(getMenuRoute(menuItem));
128
+ const targetUrl = getComparableTabUrl(getMenuRoute(menuItem), options.basename);
49
129
  if (!targetUrl)
50
130
  return void 0;
51
- return tabsList.find((tab) => normalizeTabUrl(tab.url) === targetUrl);
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 保持既有完整 URL 唯一语义;relationKey 在显式传入时优先按业务关联键复用
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
- // ::before 在子内容之前绘制,ProStep / ProHeader 仍可完整覆盖圆弧。
357
+ // 与内容区外边框对齐,同时遮住底层直角,避免缩放时出现断线或双重圆弧。
377
358
  &::before {
378
359
  content: '';
379
360
  position: absolute;
380
- top: 0;
381
- right: 0;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zat-design/sisyphus-react",
3
- "version": "4.6.0-beta.2",
3
+ "version": "4.6.0-beta.4",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",