@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.
@@ -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 retainedTabByUrl = /* @__PURE__ */ new Map();
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 retainedTab = retainedTabByUrl.get(normalizedUrl);
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
- retainedTabByUrl.set(normalizedUrl, normalizedTab);
83
+ retainedTabByIdentity.set(identity, normalizedTab);
78
84
  retainedIdByOriginalId.set(tab.id, normalizedTab.id);
79
85
  });
80
- restoredState.tabsList = Array.from(retainedTabByUrl.values());
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, { urlSync: urlSyncEnabled }) : void 0;
171
+ const existingTab = urlSyncEnabled || !forceNew ? findExistingTab(prevState.tabsList, resolvedMenuItem, {
172
+ urlSync: urlSyncEnabled,
173
+ identity: urlSyncIdentity
174
+ }) : void 0;
165
175
  if (existingTab) {
166
- const correctedTab = resolvedMenuItem.closable === false && existingTab.closable !== false ? { ...existingTab, closable: false } : existingTab;
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zat-design/sisyphus-react",
3
- "version": "4.5.10-beta.2",
3
+ "version": "4.6.0-beta.1",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",