@zat-design/sisyphus-react 4.5.6-beta.1 → 4.5.6-beta.2

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.
@@ -172,7 +172,7 @@ export interface ProHeaderType {
172
172
  */
173
173
  fixedTop?: string | number;
174
174
  /**
175
- * @description 是否显示返回按钮
175
+ * @description 是否显示返回按钮。显式传值优先;不传时若有 onBack 则显示,否则由 autoBack 按浏览器历史长度判断
176
176
  * @default -
177
177
  */
178
178
  showBack?: boolean;
@@ -198,8 +198,8 @@ export interface ProHeaderType {
198
198
  */
199
199
  zIndex?: number;
200
200
  /**
201
- * @description 返回
202
- * @default 100
201
+ * @description 返回按钮点击回调;不传时默认执行 window.history.back()
202
+ * @default -
203
203
  */
204
204
  onBack?: () => void;
205
205
  [key: string]: any;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import type { DescribeColumnType } from '../../PropTypes';
3
+ /** 描述项里的单条值(link / copyable / tag 组合),对应 items 数组元素 */
4
+ type DescribeValueItem = Pick<DescribeColumnType, 'tag' | 'link' | 'value' | 'copyable'>;
5
+ /**
6
+ * 标签渲染:数组 tag 逐个渲染,单值渲染一个,空则不渲染
7
+ */
8
+ export declare const DescribeTag: ({ tag, parentKey, }: {
9
+ tag?: DescribeColumnType["tag"];
10
+ parentKey: string;
11
+ }) => import("react/jsx-runtime").JSX.Element;
12
+ /**
13
+ * 单行值渲染:link → <a>,copyable+字符串 → 复制按钮,末尾追加 tag
14
+ */
15
+ export declare const DescribeValue: ({ value, link, copyable, tag, parentKey, }: DescribeValueItem & {
16
+ parentKey: string;
17
+ }) => import("react/jsx-runtime").JSX.Element;
18
+ /**
19
+ * 多值渲染:以「、」连接;超 3 个显示「共N个,」前缀;内容溢出时显示「...」并用 Tooltip 展示全部
20
+ */
21
+ export declare const DescribeItems: ({ items, parentKey, containerRef, }: {
22
+ items: DescribeValueItem[];
23
+ parentKey: string;
24
+ containerRef: React.RefObject<HTMLDivElement>;
25
+ }) => import("react/jsx-runtime").JSX.Element;
26
+ export {};
@@ -0,0 +1,106 @@
1
+ import _isString from "lodash/isString";
2
+ import React from 'react';
3
+ import { Space, Tooltip } from 'antd';
4
+ import classnames from 'classnames';
5
+ import Copy from "../Copy";
6
+ import { checkDescribeItemsHidden, getTagKey, getDescribeValueKey } from "../../utils";
7
+
8
+ /** 描述项里的单条值(link / copyable / tag 组合),对应 items 数组元素 */
9
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
10
+ /**
11
+ * 标签渲染:数组 tag 逐个渲染,单值渲染一个,空则不渲染
12
+ */
13
+ export const DescribeTag = ({
14
+ tag,
15
+ parentKey
16
+ }) => {
17
+ if (Array.isArray(tag) && tag.length) {
18
+ return /*#__PURE__*/_jsx(Space, {
19
+ size: 4,
20
+ children: tag.map((tagItem, tagIndex) => /*#__PURE__*/_jsx("div", {
21
+ className: "pro-header-tag",
22
+ children: tagItem
23
+ }, getTagKey(tagItem, parentKey, tagIndex)))
24
+ });
25
+ }
26
+ return tag ? /*#__PURE__*/_jsx("div", {
27
+ className: "pro-header-tag",
28
+ children: tag
29
+ }) : null;
30
+ };
31
+
32
+ /**
33
+ * 单行值渲染:link → <a>,copyable+字符串 → 复制按钮,末尾追加 tag
34
+ */
35
+ export const DescribeValue = ({
36
+ value,
37
+ link,
38
+ copyable,
39
+ tag,
40
+ parentKey
41
+ }) => {
42
+ return /*#__PURE__*/_jsxs(Space, {
43
+ size: 4,
44
+ align: tag ? 'baseline' : 'center',
45
+ children: [link ? /*#__PURE__*/_jsx("a", {
46
+ onClick: () => {
47
+ const newWindow = window.open(link);
48
+ newWindow.opener = null;
49
+ },
50
+ children: value
51
+ }) : value, copyable && _isString(value) ? /*#__PURE__*/_jsx(Copy, {
52
+ text: value
53
+ }) : null, /*#__PURE__*/_jsx(DescribeTag, {
54
+ tag: tag,
55
+ parentKey: parentKey
56
+ })]
57
+ });
58
+ };
59
+
60
+ /**
61
+ * 多值渲染:以「、」连接;超 3 个显示「共N个,」前缀;内容溢出时显示「...」并用 Tooltip 展示全部
62
+ */
63
+ export const DescribeItems = ({
64
+ items,
65
+ parentKey,
66
+ containerRef
67
+ }) => {
68
+ // items 配置是否超长,超长则展示 ... + tip 提示
69
+ const itemsHidden = checkDescribeItemsHidden(containerRef?.current);
70
+ const result = /*#__PURE__*/_jsxs("div", {
71
+ ref: containerRef,
72
+ className: classnames({
73
+ 'pro-header-describe-items': true,
74
+ 'pro-header-describe-items-more': items.length >= 2
75
+ }),
76
+ children: [items?.length > 3 ? `共${items?.length}个,` : null, items.map((item, itemIndex) => /*#__PURE__*/_jsxs("span", {
77
+ children: [/*#__PURE__*/_jsx(DescribeValue, {
78
+ ...item,
79
+ parentKey: parentKey
80
+ }), items?.length !== itemIndex + 1 ? ' 、' : null]
81
+ }, getDescribeValueKey(item, parentKey, itemIndex))), itemsHidden ? /*#__PURE__*/_jsx("span", {
82
+ className: "pro-header-describe-items-omit",
83
+ children: "..."
84
+ }) : null]
85
+ });
86
+ if (itemsHidden) {
87
+ const tipResult = /*#__PURE__*/_jsx(_Fragment, {
88
+ children: items.map((item, itemIndex) => /*#__PURE__*/_jsx("p", {
89
+ className: "pro-header-describe-items-tip-value",
90
+ children: /*#__PURE__*/_jsx(DescribeValue, {
91
+ ...item,
92
+ parentKey: parentKey
93
+ })
94
+ }, getDescribeValueKey(item, parentKey, itemIndex)))
95
+ });
96
+ return /*#__PURE__*/_jsx(Tooltip, {
97
+ placement: "bottomLeft",
98
+ classNames: {
99
+ root: 'pro-header-describe-items-tip'
100
+ },
101
+ title: tipResult,
102
+ children: result
103
+ });
104
+ }
105
+ return result;
106
+ };
@@ -7,26 +7,18 @@ import React, { memo, useEffect, useState, useRef } from 'react';
7
7
  import { tools } from '@zat-design/utils';
8
8
  import classnames from 'classnames';
9
9
  import dayjs from 'dayjs';
10
- import { useToggle, useSize } from 'ahooks';
10
+ import { useToggle } from 'ahooks';
11
11
  import { ReactSVG } from 'react-svg';
12
12
  import foldSvg from "../../../assets/arrow.svg";
13
13
  import { Copy, ProBackBtn } from "./components";
14
+ import { DescribeItems, DescribeValue } from "./components/Describe";
14
15
  import getEnumLabel from "../../../ProEnum/utils/getEnumLabel";
15
16
  import locale from "../../../locale";
16
- import { checkDescribeItemsHidden } from "./utils";
17
+ import { toKeyPart, getDescribeColumnKey, getTagKey, getSubDescribeKey } from "./utils";
17
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
19
  const {
19
20
  formatAmount
20
21
  } = tools;
21
- const toKeyPart = value => {
22
- if (value == null || value === '') return undefined;
23
- if (typeof value === 'string' || typeof value === 'number') return String(value);
24
- return undefined;
25
- };
26
- const getDescribeColumnKey = (item, index) => toKeyPart(item.label) ?? toKeyPart(item.value) ?? `describe-${index}`;
27
- const getDescribeValueKey = (item, parentKey, index) => toKeyPart(item.link) ?? toKeyPart(item.value) ?? `${parentKey}-value-${index}`;
28
- const getTagKey = (tagItem, parentKey, index) => toKeyPart(tagItem) ?? `${parentKey}-tag-${index}`;
29
- const getSubDescribeKey = (item, index) => toKeyPart(item.label) ?? toKeyPart(item.code) ?? `sub-describe-${index}`;
30
22
  const ProHeader = props => {
31
23
  const {
32
24
  breadcrumbColumns,
@@ -37,7 +29,6 @@ const ProHeader = props => {
37
29
  className,
38
30
  bodyOverFlowHidden = false,
39
31
  fixedTop,
40
- showBack = true,
41
32
  showShadow = false,
42
33
  zIndex = 98,
43
34
  title,
@@ -47,19 +38,32 @@ const ProHeader = props => {
47
38
  isConfirmBack = false,
48
39
  isConfirmBackModalProps = {}
49
40
  } = props;
50
- const [backState, setBackState] = useState(showBack);
41
+
42
+ // 返回按钮是否显示。
43
+ // 显式意图优先于 autoBack 启发式:
44
+ // 1) 用户显式传了 showBack → 完全尊重该值;
45
+ // 2) 传了 onBack(返回不依赖浏览器历史,如 tab 内返回 / 自定义跳转)→ 显示;
46
+ // 3) 都没传(纯默认,返回兜底走 window.history.back)→ 由 autoBack 按历史长度智能判断:
47
+ // 新开页面(history.length <= autoBack)无处可返回,则隐藏。
48
+ const computeBackState = () => {
49
+ if (props.showBack !== undefined) return props.showBack;
50
+ if (onBack) return true;
51
+ return window.history.length > autoBack;
52
+ };
53
+ const [backState, setBackState] = useState(computeBackState);
51
54
  const [fold, {
52
55
  toggle
53
56
  }] = useToggle(collapsed);
54
57
  const ref = useRef(null);
55
- const size = useSize(ref);
56
- let _title = title;
58
+
59
+ // 最终用于渲染的标题(title + showBack 时会被转成面包屑,此处置空)
60
+ let finalTitle = title;
57
61
 
58
62
  // 面包屑配置数据源
59
- const _breadcrumbColumns = breadcrumbColumns || [];
63
+ const finalBreadcrumbColumns = breadcrumbColumns || [];
60
64
 
61
65
  // 次级配置数据源
62
- const _subDescribeColumns = subDescribeColumns || [];
66
+ const finalSubDescribeColumns = subDescribeColumns || [];
63
67
  useEffect(() => {
64
68
  if (bodyOverFlowHidden) {
65
69
  const bodyElement = document.querySelector('body');
@@ -70,13 +74,11 @@ const ProHeader = props => {
70
74
  bodyElement.style.overflow = '';
71
75
  };
72
76
  }, [bodyOverFlowHidden]);
77
+
78
+ // showBack / onBack / autoBack 变化时同步 backState(避免 props 更新后按钮显隐不同步)
73
79
  useEffect(() => {
74
- const isNewPage = window.history.length;
75
- // history的记录,默认2,代办新开页面
76
- if (isNewPage <= autoBack) {
77
- setBackState(false);
78
- }
79
- }, []);
80
+ setBackState(computeBackState());
81
+ }, [props.showBack, onBack, autoBack]);
80
82
 
81
83
  /**
82
84
  * 主级描述数据渲染
@@ -108,82 +110,6 @@ const ProHeader = props => {
108
110
  return null;
109
111
  }
110
112
  const onlyTag = tag && !label && !value;
111
- const tagRender = params => {
112
- if (Array.isArray(params?.tag) && params?.tag.length) {
113
- return /*#__PURE__*/_jsx(Space, {
114
- size: 4,
115
- children: params.tag.map((tagItem, tagIndex) => {
116
- return /*#__PURE__*/_jsx("div", {
117
- className: "pro-header-tag",
118
- children: tagItem
119
- }, getTagKey(tagItem, describeItemKey, tagIndex));
120
- })
121
- });
122
- }
123
- return params?.tag ? /*#__PURE__*/_jsx("div", {
124
- className: "pro-header-tag",
125
- children: params?.tag
126
- }) : null;
127
- };
128
-
129
- /** 单行渲染 */
130
- const valueRender = params => {
131
- return /*#__PURE__*/_jsxs(Space, {
132
- size: 4,
133
- align: params?.tag ? 'baseline' : 'center',
134
- children: [params?.link ? /*#__PURE__*/_jsx("a", {
135
- onClick: () => {
136
- const newWindow = window.open(params.link);
137
- newWindow.opener = null;
138
- },
139
- children: params?.value
140
- }) : params?.value, params?.copyable && _isString(params?.value) ? /*#__PURE__*/_jsx(Copy, {
141
- text: params.value
142
- }) : null, tagRender({
143
- tag: params?.tag
144
- })]
145
- });
146
- };
147
-
148
- /** 多行渲染 */
149
- const itemsRender = () => {
150
- // items配置是否超长,超长配置... + tip 提示
151
- const itemsHidden = checkDescribeItemsHidden(ref?.current);
152
- const result = /*#__PURE__*/_jsxs("div", {
153
- ref: ref,
154
- className: classnames({
155
- 'pro-header-describe-items': true,
156
- 'pro-header-describe-items-more': items.length >= 2
157
- }),
158
- children: [items?.length > 3 ? `共${items?.length}个,` : null, items.map((item, itemIndex) => {
159
- return /*#__PURE__*/_jsxs("span", {
160
- children: [valueRender(item), items?.length !== itemIndex + 1 ? ' 、' : null]
161
- }, getDescribeValueKey(item, describeItemKey, itemIndex));
162
- }), itemsHidden ? /*#__PURE__*/_jsx("span", {
163
- className: "pro-header-describe-items-omit",
164
- children: "..."
165
- }) : null]
166
- });
167
- if (itemsHidden) {
168
- const tipResult = /*#__PURE__*/_jsx(_Fragment, {
169
- children: items.map((item, itemIndex) => {
170
- return /*#__PURE__*/_jsx("p", {
171
- className: "pro-header-describe-items-tip-value",
172
- children: valueRender(item)
173
- }, getDescribeValueKey(item, describeItemKey, itemIndex));
174
- })
175
- });
176
- return /*#__PURE__*/_jsx(Tooltip, {
177
- placement: "bottomLeft",
178
- classNames: {
179
- root: 'pro-header-describe-items-tip'
180
- },
181
- title: tipResult,
182
- children: result
183
- });
184
- }
185
- return result;
186
- };
187
113
  const tagCls = classnames({
188
114
  'pro-header-only-tag': onlyTag,
189
115
  'pro-header-describe-items-calc': _isString(width) ? width.includes('calc') : false
@@ -200,12 +126,17 @@ const ProHeader = props => {
200
126
  children: label
201
127
  }) : null, /*#__PURE__*/_jsx("span", {
202
128
  className: "pro-header-describe-value",
203
- children: items?.length ? itemsRender() : /*#__PURE__*/_jsx("span", {
204
- children: valueRender({
205
- value,
206
- copyable,
207
- tag,
208
- link
129
+ children: items?.length ? /*#__PURE__*/_jsx(DescribeItems, {
130
+ items: items,
131
+ parentKey: describeItemKey,
132
+ containerRef: ref
133
+ }) : /*#__PURE__*/_jsx("span", {
134
+ children: /*#__PURE__*/_jsx(DescribeValue, {
135
+ value: value,
136
+ copyable: copyable,
137
+ tag: tag,
138
+ link: link,
139
+ parentKey: describeItemKey
209
140
  })
210
141
  })
211
142
  })]
@@ -302,10 +233,10 @@ const ProHeader = props => {
302
233
  * 总结信息面板渲染
303
234
  */
304
235
  const breadcrumbRender = () => {
305
- if (Array.isArray(_breadcrumbColumns) && !_breadcrumbColumns.length) {
236
+ if (Array.isArray(finalBreadcrumbColumns) && !finalBreadcrumbColumns.length) {
306
237
  return [];
307
238
  }
308
- const list = _breadcrumbColumns?.filter(item => {
239
+ const list = finalBreadcrumbColumns?.filter(item => {
309
240
  return item.type === 'breadcrumb';
310
241
  });
311
242
  return list.map((item, index) => {
@@ -355,10 +286,10 @@ const ProHeader = props => {
355
286
  * 头部左侧导航栏后业务信息
356
287
  */
357
288
  const infoRender = () => {
358
- if (!Array.isArray(_breadcrumbColumns)) {
289
+ if (!Array.isArray(finalBreadcrumbColumns)) {
359
290
  return '';
360
291
  }
361
- const list = _breadcrumbColumns?.filter(item => {
292
+ const list = finalBreadcrumbColumns?.filter(item => {
362
293
  return item.type !== 'breadcrumb';
363
294
  });
364
295
  if (!list.length) {
@@ -436,24 +367,29 @@ const ProHeader = props => {
436
367
  })
437
368
  });
438
369
  };
370
+
371
+ // 说明:以下两个 class 的判断沿用原有等价逻辑(去掉了原表达式里对 describeColumns 的重复判断)。
372
+ // has = 有次级描述 或 有主级描述;no = 无主级描述。二者非严格互斥(原实现即如此,未改动其行为)。
373
+ const noDescribe = _isEmpty(describeColumns);
374
+ const hasDescribe = !_isEmpty(finalSubDescribeColumns) || !noDescribe;
439
375
  const cls = classnames({
440
376
  'pro-header': true,
441
377
  'pro-header-fixed': fixedTop,
442
378
  'pro-header-shadow': showShadow,
443
379
  // 内部窗口滚动自带阴影场景
444
380
  'pro-header-no-back': !backState,
445
- 'pro-header-has-describe': !_isEmpty(_subDescribeColumns) || !_isEmpty(describeColumns) || !_isEmpty(describeColumns),
446
- 'pro-header-no-describe': _isEmpty(_subDescribeColumns) && _isEmpty(describeColumns) || _isEmpty(describeColumns),
381
+ 'pro-header-has-describe': hasDescribe,
382
+ 'pro-header-no-describe': noDescribe,
447
383
  [`${className}`]: className
448
384
  });
449
- if (title && !_isEmpty(_breadcrumbColumns)) {
385
+ if (title && !_isEmpty(finalBreadcrumbColumns)) {
450
386
  console.error('warning: The title and the breadcrumbList cannot be used together');
451
387
  }
452
388
 
453
389
  // title默认不显示返回按钮,只有showBack设置为true时才显示
454
390
  if (title && props.showBack) {
455
- _title = null;
456
- _breadcrumbColumns.push({
391
+ finalTitle = null;
392
+ finalBreadcrumbColumns.push({
457
393
  type: 'breadcrumb',
458
394
  value: title
459
395
  });
@@ -492,9 +428,9 @@ const ProHeader = props => {
492
428
  top: fixedTop,
493
429
  zIndex
494
430
  },
495
- children: _title ? /*#__PURE__*/_jsx("div", {
431
+ children: finalTitle ? /*#__PURE__*/_jsx("div", {
496
432
  className: "pro-header-title",
497
- children: _title
433
+ children: finalTitle
498
434
  }) : /*#__PURE__*/_jsxs(_Fragment, {
499
435
  children: [/*#__PURE__*/_jsxs("div", {
500
436
  className: "pro-header-top",
@@ -516,14 +452,14 @@ const ProHeader = props => {
516
452
  className: "pro-header-right",
517
453
  children: actionsRender()
518
454
  })]
519
- }), describeRender(describeColumns), !_isEmpty(_subDescribeColumns) ? /*#__PURE__*/_jsxs("div", {
455
+ }), describeRender(describeColumns), !_isEmpty(finalSubDescribeColumns) ? /*#__PURE__*/_jsxs("div", {
520
456
  className: classnames({
521
457
  'pro-header-nav': true,
522
458
  'pro-header-nav-open': fold,
523
459
  'pro-header-nav-hidden': !fold
524
460
  }),
525
461
  children: [/*#__PURE__*/_jsx("ul", {
526
- children: _subDescribeColumns.map((item, index) => {
462
+ children: finalSubDescribeColumns.map((item, index) => {
527
463
  return /*#__PURE__*/_jsx("li", {
528
464
  children: subDescribeRender(item)
529
465
  }, getSubDescribeKey(item, index));
@@ -1,5 +1,20 @@
1
+ import type React from 'react';
2
+ import type { DescribeColumnType, SubDescribeColumnType } from '../PropTypes';
1
3
  /**
2
4
  * 检查描述dom是否部分隐藏
3
5
  * @param ele dom节点
4
6
  */
5
7
  export declare const checkDescribeItemsHidden: (ele?: any) => boolean;
8
+ /**
9
+ * 将值转为可用于 React key 的字符串片段
10
+ * 仅 string / number 有效,其余(含 ReactNode、对象、数组、boolean)返回 undefined
11
+ */
12
+ export declare const toKeyPart: (value: unknown) => string | undefined;
13
+ /** 主级描述项 key:label → value → describe-${index} */
14
+ export declare const getDescribeColumnKey: (item: DescribeColumnType, index: number) => string;
15
+ /** 描述值 key:link → value → ${parentKey}-value-${index} */
16
+ export declare const getDescribeValueKey: (item: Pick<DescribeColumnType, "tag" | "link" | "value" | "copyable">, parentKey: string, index: number) => string;
17
+ /** 标签 key:tagItem → ${parentKey}-tag-${index} */
18
+ export declare const getTagKey: (tagItem: string | React.ReactNode, parentKey: string, index: number) => string;
19
+ /** 次级描述项 key:label → code → sub-describe-${index} */
20
+ export declare const getSubDescribeKey: (item: SubDescribeColumnType, index: number) => string;
@@ -11,4 +11,26 @@ export const checkDescribeItemsHidden = ele => {
11
11
  const itemsScrollWidth = element.scrollWidth;
12
12
  const itemsClientWidth = element.clientWidth;
13
13
  return itemsScrollWidth > itemsClientWidth;
14
- };
14
+ };
15
+
16
+ /**
17
+ * 将值转为可用于 React key 的字符串片段
18
+ * 仅 string / number 有效,其余(含 ReactNode、对象、数组、boolean)返回 undefined
19
+ */
20
+ export const toKeyPart = value => {
21
+ if (value == null || value === '') return undefined;
22
+ if (typeof value === 'string' || typeof value === 'number') return String(value);
23
+ return undefined;
24
+ };
25
+
26
+ /** 主级描述项 key:label → value → describe-${index} */
27
+ export const getDescribeColumnKey = (item, index) => toKeyPart(item.label) ?? toKeyPart(item.value) ?? `describe-${index}`;
28
+
29
+ /** 描述值 key:link → value → ${parentKey}-value-${index} */
30
+ export const getDescribeValueKey = (item, parentKey, index) => toKeyPart(item.link) ?? toKeyPart(item.value) ?? `${parentKey}-value-${index}`;
31
+
32
+ /** 标签 key:tagItem → ${parentKey}-tag-${index} */
33
+ export const getTagKey = (tagItem, parentKey, index) => toKeyPart(tagItem) ?? `${parentKey}-tag-${index}`;
34
+
35
+ /** 次级描述项 key:label → code → sub-describe-${index} */
36
+ export const getSubDescribeKey = (item, index) => toKeyPart(item.label) ?? toKeyPart(item.code) ?? `sub-describe-${index}`;
@@ -85,6 +85,68 @@ const TabsManager = /*#__PURE__*/forwardRef(({
85
85
  }
86
86
  }, [state.activeKey]);
87
87
 
88
+ // tab 内子视图栈:按 tabId 隔离。栈底→栈顶顺序,入口页不在其中。
89
+ // 只活在内存、不进缓存(详情/工作流是运行态 ReactNode,无法 JSON 序列化)。
90
+ const [viewStacks, setViewStacks] = useState({});
91
+
92
+ // 用 ref 持有 activeKey,供稳定的 pushView/back 读取当前激活 tab,避免因 activeKey 变化重建实例
93
+ const activeKeyRef = useRef(state.activeKey);
94
+ activeKeyRef.current = state.activeKey;
95
+
96
+ // 同步持有最新 viewStacks,使 getViewStack 成为稳定引用且读取不受渲染时序影响
97
+ const viewStacksRef = useRef(viewStacks);
98
+ viewStacksRef.current = viewStacks;
99
+
100
+ // 在当前激活 tab 内压入一层子视图(入口页/下层随之隐藏但不卸载)
101
+ const pushView = useCallback(node => {
102
+ const tabId = activeKeyRef.current;
103
+ if (!tabId) return;
104
+ setViewStacks(prev => ({
105
+ ...prev,
106
+ [tabId]: [...(prev[tabId] || []), node]
107
+ }));
108
+ }, []);
109
+
110
+ // 返回:销毁当前激活 tab 的栈顶子视图(栈空时 no-op)
111
+ const back = useCallback(() => {
112
+ const tabId = activeKeyRef.current;
113
+ if (!tabId) return;
114
+ setViewStacks(prev => {
115
+ const stack = prev[tabId];
116
+ if (!stack || stack.length === 0) return prev;
117
+ const nextStack = stack.slice(0, -1);
118
+ const next = {
119
+ ...prev
120
+ };
121
+ if (nextStack.length === 0) {
122
+ delete next[tabId];
123
+ } else {
124
+ next[tabId] = nextStack;
125
+ }
126
+ return next;
127
+ });
128
+ }, []);
129
+
130
+ // 获取当前激活 tab 的子视图栈(从 ref 读最新值,稳定引用)
131
+ const getViewStack = useCallback(() => viewStacksRef.current[activeKeyRef.current] || [], []);
132
+
133
+ // 子视图栈与 tabsList 对账:任何关闭路径(removeTab / closeOthers / closeRight /
134
+ // closeAll)导致某 tab 消失后,清除其残留子视图栈,避免关闭后 node 仍驻留内存。
135
+ useEffect(() => {
136
+ setViewStacks(prev => {
137
+ const keys = Object.keys(prev);
138
+ if (keys.length === 0) return prev;
139
+ const aliveIds = new Set(state.tabsList.map(tab => tab.id));
140
+ const staleKeys = keys.filter(id => !aliveIds.has(id));
141
+ if (staleKeys.length === 0) return prev;
142
+ const next = {
143
+ ...prev
144
+ };
145
+ staleKeys.forEach(id => delete next[id]);
146
+ return next;
147
+ });
148
+ }, [state.tabsList]);
149
+
88
150
  // 通知父级(ProLayout)当前是否有 Tab
89
151
  useEffect(() => {
90
152
  onTabsChange?.(state.tabsList.length > 0);
@@ -169,8 +231,11 @@ const TabsManager = /*#__PURE__*/forwardRef(({
169
231
  tabsList: state.tabsList,
170
232
  activeTabInfo: state.tabsList.find(tab => tab.id === state.activeKey),
171
233
  activeComponent: state.activeComponent
172
- })
173
- }), [addTab, removeTab, updateTab, state.tabsList, state.activeKey, state.activeComponent, dataSource]);
234
+ }),
235
+ pushView,
236
+ back,
237
+ getViewStack
238
+ }), [addTab, removeTab, updateTab, state.tabsList, state.activeKey, state.activeComponent, dataSource, pushView, back, getViewStack]);
174
239
  const handleTabChange = useCallback(activeKey => {
175
240
  switchTab(activeKey);
176
241
  }, [switchTab]);
@@ -227,11 +292,30 @@ const TabsManager = /*#__PURE__*/forwardRef(({
227
292
  });
228
293
  }
229
294
  }
295
+
296
+ // 该 tab 的子视图栈:栈底→栈顶。只有「当前激活 tab 的栈顶层」可见,
297
+ // 入口页与下层子视图 display:none 但保持挂载 → 返回秒回、状态不丢。
298
+ const stack = viewStacks[tab.id] || [];
299
+ // 入口页可见条件:该 tab 激活 且 无子视图
300
+ const entryVisible = isActive && stack.length === 0;
230
301
  return /*#__PURE__*/_jsx("div", {
231
- className: `tab-pane ${isActive ? '' : 'hidden'}`,
302
+ className: `tab-pane ${entryVisible ? '' : 'hidden'}`,
232
303
  "data-testid": `tab-pane-${tab.id}`,
233
304
  children: content
234
305
  }, tab.id);
306
+ }), state.tabsList.map(tab => {
307
+ const isActive = tab.id === state.activeKey;
308
+ const stack = viewStacks[tab.id] || [];
309
+ if (stack.length === 0) return null;
310
+ const topIndex = stack.length - 1;
311
+ return stack.map((node, index) => {
312
+ const layerVisible = isActive && index === topIndex;
313
+ return /*#__PURE__*/_jsx("div", {
314
+ className: `tab-pane ${layerVisible ? '' : 'hidden'}`,
315
+ "data-testid": `tab-subview-${tab.id}-${index}`,
316
+ children: node
317
+ }, `${tab.id}-subview-${index}`);
318
+ });
235
319
  }), state.tabsList.length === 0 && /*#__PURE__*/_jsx("div", {
236
320
  className: "tab-pane",
237
321
  "data-testid": "default-content",
@@ -84,10 +84,13 @@
84
84
  margin: 0;
85
85
  padding: 0;
86
86
  border-bottom: none;
87
+ }
87
88
 
88
- &::before {
89
- display: none; // 隐藏底部边框
90
- }
89
+ // 横线右端缩短,空出圆角半径,让横线在圆弧起点处结束
90
+ // 用属性选择器提升权重,确保覆盖 antd :where() 规则
91
+ .@{ant-prefix}-tabs-nav[class]::before {
92
+ right: var(--zaui-border-radius, 8px) !important;
93
+ left: 0 !important;
91
94
  }
92
95
 
93
96
  // 覆盖 nav-list 样式,使其匹配 pro-layout-tab-list