@zat-design/sisyphus-react 4.6.0-beta.3 → 4.6.0

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.
@@ -32,7 +32,7 @@ export declare const difference: <T extends Record<string, any>, U extends Recor
32
32
  * @param rowName 行名称路径
33
33
  * @returns 校验结果Promise
34
34
  */
35
- export declare const customValidate: (validateKeys: string[], form: FormInstance, rowName: NamePath) => Promise<any>;
35
+ export declare const customValidate: (validateKeys: NamePath[], form: FormInstance, rowName: NamePath) => Promise<any>;
36
36
  /**
37
37
  * 拆解数组names
38
38
  * @param names 名称数组
@@ -47,10 +47,11 @@ const difference = (object, base) => {
47
47
  return changes(object, base);
48
48
  };
49
49
  const customValidate = (validateKeys, form, rowName) => {
50
- const array = [];
51
- validateKeys.forEach((key) => {
52
- array.push([...Array.isArray(rowName) ? rowName : [rowName], key]);
53
- });
50
+ const rowNamePath = Array.isArray(rowName) ? rowName : [rowName];
51
+ const array = validateKeys.map((key) => [
52
+ ...rowNamePath,
53
+ ...Array.isArray(key) ? key : [key]
54
+ ]);
54
55
  return form.validateFields(array);
55
56
  };
56
57
  const splitNames = (names) => {
@@ -13,8 +13,8 @@ import {
13
13
  flattenMenuData,
14
14
  canOpenAsTab,
15
15
  getMenuRoute,
16
- hasRelationKey,
17
16
  findExistingTab,
17
+ getUrlSyncTabIdentity,
18
18
  getComparableTabUrl,
19
19
  normalizeTabUrl,
20
20
  isSafeExternalUrl,
@@ -69,18 +69,20 @@ const useTabsState = (options) => {
69
69
  const retainedTabByIdentity = /* @__PURE__ */ new Map();
70
70
  const retainedIdByOriginalId = /* @__PURE__ */ new Map();
71
71
  restoredState.tabsList.forEach((tab) => {
72
- var _a2;
73
72
  const normalizedUrl = normalizeTabUrl(tab.url);
74
73
  if (!normalizedUrl)
75
74
  return;
76
75
  const comparableUrl = getComparableTabUrl(normalizedUrl, urlSyncBasename);
77
76
  if (!comparableUrl)
78
77
  return;
79
- const relationKey = tab.relationKey ?? ((_a2 = tab.menuItem) == null ? void 0 : _a2.relationKey);
80
- const identity = urlSyncIdentity === "relationKey" && hasRelationKey(relationKey) ? `relationKey:${typeof relationKey}:${String(relationKey)}` : `url:${comparableUrl}`;
78
+ const identity = getUrlSyncTabIdentity(
79
+ { ...tab, url: normalizedUrl },
80
+ { identity: urlSyncIdentity, basename: urlSyncBasename }
81
+ );
82
+ if (!identity)
83
+ return;
81
84
  const retainedTab = retainedTabByIdentity.get(identity);
82
85
  if (retainedTab) {
83
- retainedTabByIdentity.set(identity, { ...retainedTab, url: normalizedUrl });
84
86
  retainedIdByOriginalId.set(tab.id, retainedTab.id);
85
87
  return;
86
88
  }
@@ -181,12 +183,7 @@ const useTabsState = (options) => {
181
183
  }) : void 0;
182
184
  if (existingTab) {
183
185
  const shouldFixClosable = resolvedMenuItem.closable === false && existingTab.closable !== false;
184
- const shouldUpdateUrl = urlSyncIdentity === "relationKey" && normalizedUrl !== void 0 && getComparableTabUrl(existingTab.url, urlSyncBasename) !== getComparableTabUrl(normalizedUrl, urlSyncBasename);
185
- const correctedTab = shouldFixClosable || shouldUpdateUrl ? {
186
- ...existingTab,
187
- ...shouldFixClosable ? { closable: false } : {},
188
- ...shouldUpdateUrl ? { url: normalizedUrl } : {}
189
- } : existingTab;
186
+ const correctedTab = shouldFixClosable ? { ...existingTab, closable: false } : existingTab;
190
187
  const updatedTabsList = correctedTab !== existingTab ? prevState.tabsList.map((t) => t.id === existingTab.id ? correctedTab : t) : prevState.tabsList;
191
188
  return {
192
189
  ...prevState,
@@ -257,7 +254,10 @@ const useTabsState = (options) => {
257
254
  ...updates.icon !== void 0 && { icon: updates.icon },
258
255
  ...updates.extra !== void 0 && { extra: updates.extra },
259
256
  ...updates.closable !== void 0 && { closable: updates.closable },
260
- ...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
+ }
261
261
  } : target.menuItem;
262
262
  const nextTab = {
263
263
  ...target,
@@ -265,6 +265,9 @@ const useTabsState = (options) => {
265
265
  ...updates.icon !== void 0 && { icon: updates.icon },
266
266
  ...updates.closable !== void 0 && { closable: updates.closable },
267
267
  ...updates.pinned !== void 0 && { pinned: updates.pinned },
268
+ ...updates.menuCodeSource !== void 0 && {
269
+ menuCodeSource: updates.menuCodeSource
270
+ },
268
271
  menuItem: nextMenuItem
269
272
  };
270
273
  const newTabsList = [...prevState.tabsList];
@@ -3,6 +3,7 @@ import * as ReactRouterDom from "react-router-dom";
3
3
  import {
4
4
  canOpenAsTab,
5
5
  flattenMenuData,
6
+ findExistingTab,
6
7
  getBrowserTabUrl,
7
8
  getComparableTabUrl,
8
9
  getCurrentTabUrl,
@@ -95,6 +96,7 @@ const useTabsUrlSync = ({
95
96
  const enabled = config.urlSync === true || typeof config.urlSync === "object" && config.urlSync !== null;
96
97
  const historyMode = typeof config.urlSync === "object" ? config.urlSync.history || "replace" : "replace";
97
98
  const basename = typeof config.urlSync === "object" ? config.urlSync.basename : void 0;
99
+ const identity = typeof config.urlSync === "object" ? config.urlSync.identity : void 0;
98
100
  const bootstrappedRef = useRef(false);
99
101
  const writingUrlRef = useRef(false);
100
102
  const preserveUrlInteractionRef = useRef(null);
@@ -201,7 +203,21 @@ const useTabsUrlSync = ({
201
203
  handledLocationSequenceRef.current = locationChange.sequence;
202
204
  bootstrappedRef.current = true;
203
205
  preserveUrlInteractionRef.current = interactionSequence;
204
- 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);
205
221
  return;
206
222
  }
207
223
  if (!bootstrappedRef.current) {
@@ -230,7 +246,8 @@ const useTabsUrlSync = ({
230
246
  switchTab,
231
247
  updateTabUrl,
232
248
  interactionSequence,
233
- basename
249
+ basename,
250
+ identity
234
251
  ]);
235
252
  useEffect(() => {
236
253
  if (!enabled || !isInitialized || !bootstrappedRef.current || typeof window === "undefined") {
@@ -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;
@@ -24,6 +24,8 @@ export interface FindExistingTabOptions {
24
24
  identity?: TabsUrlSyncConfig['identity'];
25
25
  basename?: TabsUrlSyncConfig['basename'];
26
26
  }
27
+ /** URL 同步模式的唯一身份;relationKey 模式兼容无关联键的旧菜单 code。 */
28
+ export declare const getUrlSyncTabIdentity: (item: MenusType | TabItem, options?: Pick<FindExistingTabOptions, 'identity' | 'basename'>) => string | undefined;
27
29
  export declare const findExistingTab: (tabsList: TabItem[], menuItem: MenusType, options?: FindExistingTabOptions) => TabItem | undefined;
28
30
  /**
29
31
  * 根据菜单项生成TabItem
@@ -78,20 +78,59 @@ const getTabUrlPathname = (value) => {
78
78
  return void 0;
79
79
  return new URL(normalized, window.location.origin).pathname;
80
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
+ };
81
105
  const findExistingTab = (tabsList, menuItem, options = {}) => {
82
106
  if (options.urlSync) {
83
- if (options.identity === "relationKey" && hasRelationKey(menuItem.relationKey)) {
84
- return tabsList.find(
85
- (tab) => {
86
- var _a;
87
- 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
+ );
88
120
  }
89
- );
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
+ }
90
127
  }
91
128
  const targetUrl = getComparableTabUrl(getMenuRoute(menuItem), options.basename);
92
129
  if (!targetUrl)
93
130
  return void 0;
94
- return tabsList.find((tab) => getComparableTabUrl(tab.url, options.basename) === targetUrl);
131
+ return tabsList.find(
132
+ (tab) => getComparableTabUrl(tab.url, options.basename) === targetUrl
133
+ );
95
134
  }
96
135
  if (hasRelationKey(menuItem.relationKey)) {
97
136
  return tabsList.find(
@@ -282,6 +321,7 @@ export {
282
321
  getPinnedBoundary,
283
322
  getRightTabs,
284
323
  getTabUrlPathname,
324
+ getUrlSyncTabIdentity,
285
325
  handleExternalOpen,
286
326
  hasRelationKey,
287
327
  isLeafMenuItem,
@@ -369,7 +369,7 @@ 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';
@@ -425,7 +425,7 @@ export interface AddTabOptions {
425
425
  silent?: boolean;
426
426
  }
427
427
  /**
428
- * @description 更新标签页的字段(仅允许更新展示与扩展数据)
428
+ * @description 更新标签页的展示、扩展数据与来源菜单身份
429
429
  */
430
430
  export interface UpdateTabParams {
431
431
  /**
@@ -448,6 +448,10 @@ export interface UpdateTabParams {
448
448
  * @description 是否固定标签(本质状态,driven closable)
449
449
  */
450
450
  pinned?: boolean;
451
+ /**
452
+ * @description 最初打开当前业务标签链路的左侧菜单 code;用于兼容性迁移已缓存的来源身份
453
+ */
454
+ menuCodeSource?: string;
451
455
  }
452
456
  /**
453
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.3",
3
+ "version": "4.6.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",