@zat-design/sisyphus-react 4.2.0 → 4.3.0-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.
@@ -9,11 +9,20 @@ const AddonWrapper = props => {
9
9
  } = props;
10
10
  return /*#__PURE__*/_jsxs("div", {
11
11
  className: "addon-wrapper",
12
+ style: {
13
+ display: 'flex',
14
+ alignItems: 'center',
15
+ gap: '8px'
16
+ },
12
17
  children: [before && /*#__PURE__*/_jsx("div", {
13
- className: "before",
18
+ style: {
19
+ whiteSpace: 'nowrap'
20
+ },
14
21
  children: before
15
22
  }), children, after && /*#__PURE__*/_jsx("div", {
16
- className: "after",
23
+ style: {
24
+ whiteSpace: 'nowrap'
25
+ },
17
26
  children: after
18
27
  })]
19
28
  });
@@ -1,4 +1,5 @@
1
1
  @import '../../../../../style/variables.less';
2
+ @import '../component/AddonWrapper/index.less';
2
3
 
3
4
  .pro-group {
4
5
  display: flex;
@@ -115,7 +116,7 @@
115
116
 
116
117
  // 确保输入框完全贴合
117
118
  .@{ant-prefix}-input,
118
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
119
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
119
120
  .@{ant-prefix}-input-number,
120
121
  .@{ant-prefix}-input-number-input {
121
122
  // 重置z-index确保正确的层级关系
@@ -127,7 +128,7 @@
127
128
  // 第一个表单项:左边保持圆角
128
129
  &:first-child {
129
130
  .@{ant-prefix}-input,
130
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
131
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
131
132
  .@{ant-prefix}-input-number,
132
133
  .@{ant-prefix}-input-number-input {
133
134
  border-top-left-radius: 6px;
@@ -138,7 +139,7 @@
138
139
  // 最后一个表单项:右边保持圆角
139
140
  &:last-child {
140
141
  .@{ant-prefix}-input,
141
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
142
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
142
143
  .@{ant-prefix}-input-number,
143
144
  .@{ant-prefix}-input-number-input {
144
145
  border-top-right-radius: 6px;
@@ -154,7 +155,7 @@
154
155
 
155
156
  // 聚焦时恢复左边框
156
157
  .@{ant-prefix}-input:focus,
157
- .@{ant-prefix}-select:focus .@{ant-prefix}-select-selector,
158
+ .@{ant-prefix}-select:focus .@{ant-prefix}-select-content,
158
159
  .@{ant-prefix}-input-number:focus,
159
160
  .@{ant-prefix}-input-number:focus .@{ant-prefix}-input-number-input {
160
161
  margin-left: -1px;
@@ -165,7 +166,7 @@
165
166
  // 错误状态:确保边框变红(优先级要高于上面的 border-left-width: 0)
166
167
  &.@{ant-prefix}-form-item-has-error {
167
168
  .@{ant-prefix}-input,
168
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
169
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
169
170
  .@{ant-prefix}-input-number,
170
171
  .@{ant-prefix}-input-number-input {
171
172
  border-color: #ff4d4f !important;
@@ -174,7 +175,7 @@
174
175
  // 错误状态下,中间元素也需要恢复左边框并设置为红色
175
176
  &:not(:first-child) {
176
177
  .@{ant-prefix}-input,
177
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
178
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
178
179
  .@{ant-prefix}-input-number {
179
180
  border-left-color: #ff4d4f !important;
180
181
  border-left-width: 1px !important;
@@ -187,14 +188,14 @@
187
188
 
188
189
  // 聚焦时保持红色边框
189
190
  .@{ant-prefix}-input:focus,
190
- .@{ant-prefix}-select:focus .@{ant-prefix}-select-selector,
191
+ .@{ant-prefix}-select:focus .@{ant-prefix}-select-content,
191
192
  .@{ant-prefix}-input-number:focus,
192
193
  .@{ant-prefix}-input-number:focus .@{ant-prefix}-input-number-input {
193
194
  border-color: #ff4d4f !important;
194
195
  }
195
196
 
196
197
  // Select 组件的错误状态处理
197
- .@{ant-prefix}-select.@{ant-prefix}-select-status-error .@{ant-prefix}-select-selector {
198
+ .@{ant-prefix}-select.@{ant-prefix}-select-status-error .@{ant-prefix}-select-content {
198
199
  border-color: #ff4d4f !important;
199
200
  }
200
201
  }
@@ -317,37 +318,40 @@
317
318
  }
318
319
 
319
320
  // 确保输入框完全贴合
321
+ .@{ant-prefix}-input-affix-wrapper,
320
322
  .@{ant-prefix}-input,
321
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
323
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
322
324
  .@{ant-prefix}-input-number,
323
325
  .@{ant-prefix}-input-number-input,
324
326
  .@{ant-prefix}-picker {
325
327
  // 重置z-index确保正确的层级关系
326
328
  position: relative;
327
- // 重置圆角
328
- border-radius: 0;
329
+ // 重置圆角(!important 确保覆盖 antd v6 CSS-in-JS 懒注入的 disabled 等状态样式)
330
+ border-radius: 0 !important;
329
331
  }
330
332
 
331
333
  // 第一个表单项:左边保持圆角
332
334
  &:first-child {
335
+ .@{ant-prefix}-input-affix-wrapper,
333
336
  .@{ant-prefix}-input,
334
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
337
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
335
338
  .@{ant-prefix}-input-number,
336
339
  .@{ant-prefix}-input-number-input {
337
- border-top-left-radius: 6px;
338
- border-bottom-left-radius: 6px;
340
+ border-top-left-radius: 6px !important;
341
+ border-bottom-left-radius: 6px !important;
339
342
  }
340
343
  }
341
344
 
342
345
  // 最后一个表单项:右边保持圆角
343
346
  &:last-child {
347
+ .@{ant-prefix}-input-affix-wrapper,
344
348
  .@{ant-prefix}-input,
345
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
349
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
346
350
  .@{ant-prefix}-input-number,
347
351
  .@{ant-prefix}-input-number-input,
348
352
  .@{ant-prefix}-picker {
349
- border-top-right-radius: 6px;
350
- border-bottom-right-radius: 6px;
353
+ border-top-right-radius: 6px !important;
354
+ border-bottom-right-radius: 6px !important;
351
355
  }
352
356
  }
353
357
 
@@ -355,7 +359,7 @@
355
359
  &:not(:first-child) {
356
360
  // 聚焦时恢复左边框
357
361
  .@{ant-prefix}-input:focus,
358
- .@{ant-prefix}-select:focus .@{ant-prefix}-select-selector,
362
+ .@{ant-prefix}-select:focus .@{ant-prefix}-select-content,
359
363
  .@{ant-prefix}-input-number:focus,
360
364
  .@{ant-prefix}-input-number:focus .@{ant-prefix}-input-number-input {
361
365
  margin-left: -1px;
@@ -426,7 +430,7 @@
426
430
  &:not(.pro-group-flexible) {
427
431
  // 错误状态:确保边框变红(优先级要高于上面的 border-left-width: 0)
428
432
  .@{ant-prefix}-input,
429
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
433
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
430
434
  .@{ant-prefix}-input-number,
431
435
  .@{ant-prefix}-input-number-input {
432
436
  border-color: #ff4d4f !important;
@@ -435,7 +439,7 @@
435
439
  // 错误状态下,中间元素也需要恢复左边框并设置为红色
436
440
  &:not(:first-child) {
437
441
  .@{ant-prefix}-input,
438
- .@{ant-prefix}-select .@{ant-prefix}-select-selector,
442
+ .@{ant-prefix}-select .@{ant-prefix}-select-content,
439
443
  .@{ant-prefix}-input-number {
440
444
  border-left-color: #ff4d4f !important;
441
445
  border-left-width: 1px !important;
@@ -448,14 +452,14 @@
448
452
 
449
453
  // 聚焦时保持红色边框
450
454
  .@{ant-prefix}-input:focus,
451
- .@{ant-prefix}-select:focus .@{ant-prefix}-select-selector,
455
+ .@{ant-prefix}-select:focus .@{ant-prefix}-select-content,
452
456
  .@{ant-prefix}-input-number:focus,
453
457
  .@{ant-prefix}-input-number:focus .@{ant-prefix}-input-number-input {
454
458
  border-color: #ff4d4f !important;
455
459
  }
456
460
 
457
461
  // Select 组件的错误状态处理
458
- .@{ant-prefix}-select.@{ant-prefix}-select-status-error .@{ant-prefix}-select-selector {
462
+ .@{ant-prefix}-select.@{ant-prefix}-select-status-error .@{ant-prefix}-select-content {
459
463
  border-color: #ff4d4f !important;
460
464
  }
461
465
  }
@@ -464,8 +468,10 @@
464
468
  // compact模式容器, 修复antd6定制模式下,Group组件内嵌套Form.Item时,表单样式问题
465
469
  .pro-group-compact-container {
466
470
  .@{ant-prefix}-select {
467
- border-radius: 0;
468
- border-radius: 0;
471
+ border-radius: 0 !important;
472
+ }
473
+ .@{ant-prefix}-input-affix-wrapper {
474
+ border-radius: 0 !important;
469
475
  }
470
476
 
471
477
  > .pro-group-form-item:not(:first-child) {
@@ -492,9 +498,10 @@
492
498
  > .pro-group-form-item:first-child {
493
499
  .pro-select .@{ant-prefix}-select,
494
500
  .@{ant-prefix}-select,
501
+ .@{ant-prefix}-input-affix-wrapper,
495
502
  .@{ant-prefix}-input-number {
496
- border-top-left-radius: 6px;
497
- border-bottom-left-radius: 6px;
503
+ border-top-left-radius: 6px !important;
504
+ border-bottom-left-radius: 6px !important;
498
505
  }
499
506
  }
500
507
 
@@ -502,9 +509,10 @@
502
509
  > .pro-group-form-item:last-child {
503
510
  .pro-select .@{ant-prefix}-select,
504
511
  .@{ant-prefix}-select,
512
+ .@{ant-prefix}-input-affix-wrapper,
505
513
  .@{ant-prefix}-input-number {
506
- border-top-right-radius: 6px;
507
- border-bottom-right-radius: 6px;
514
+ border-top-right-radius: 6px !important;
515
+ border-bottom-right-radius: 6px !important;
508
516
  }
509
517
  }
510
518
  }
@@ -75,31 +75,36 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
75
75
  confirm?: boolean | import("antd").ModalFuncProps | import("../../../render/propsType").FunctionArgs<any, boolean | import("antd").ModalFuncProps>;
76
76
  show?: boolean | ReactiveFunction<any, boolean>;
77
77
  component?: React.ReactNode | ReactiveFunction<any, React.ReactNode>;
78
+ children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
78
79
  trim?: boolean;
79
80
  normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
80
- children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
81
81
  className?: string;
82
82
  style?: React.CSSProperties;
83
- prefixCls?: string;
84
- trigger?: string;
83
+ preserve?: boolean;
85
84
  id?: string;
86
- isView?: boolean;
87
- rootClassName?: string;
88
85
  hidden?: boolean;
89
86
  onReset?: () => void;
90
- status?: "" | "warning" | "error" | "success" | "validating";
87
+ prefixCls?: string;
88
+ rootClassName?: string;
89
+ layout?: import("antd/es/form/Form").FormItemLayout;
90
+ help?: React.ReactNode;
91
91
  vertical?: boolean;
92
- validateTrigger?: string | false | string[];
93
- preserve?: boolean;
94
92
  htmlFor?: string;
95
- colon?: boolean;
93
+ trigger?: string;
94
+ status?: "" | "warning" | "error" | "success" | "validating";
95
+ isView?: boolean;
96
+ getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
97
+ desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
98
+ validateTrigger?: string | false | string[];
99
+ clearNotShow?: boolean;
96
100
  labelAlign?: import("antd/es/form/interface").FormLabelAlign;
101
+ colon?: boolean;
97
102
  labelCol?: import("antd").ColProps;
103
+ wrapperCol?: import("antd").ColProps;
98
104
  getValueFromEvent?: (...args: import("@rc-component/form/lib/interface").EventArgs) => any;
99
105
  shouldUpdate?: import("@rc-component/form/lib/Field").ShouldUpdate<any>;
100
106
  validateDebounce?: number;
101
107
  valuePropName?: string;
102
- getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
103
108
  messageVariables?: Record<string, string>;
104
109
  initialValue?: any;
105
110
  onMetaChange?: (meta: import("@rc-component/form/lib/Field").MetaEvent) => void;
@@ -110,9 +115,6 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
110
115
  icons: import("antd/es/form/FormItem").FeedbackIcons;
111
116
  };
112
117
  validateStatus?: "" | "warning" | "error" | "success" | "validating";
113
- layout?: import("antd/es/form/Form").FormItemLayout;
114
- wrapperCol?: import("antd").ColProps;
115
- help?: React.ReactNode;
116
118
  fieldId?: string;
117
119
  valueType?: import("../../../render/propsType").ProFormValueType;
118
120
  switchValue?: [any, any];
@@ -125,8 +127,6 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
125
127
  upperCase?: boolean;
126
128
  toISOString?: boolean;
127
129
  toCSTString?: boolean;
128
- clearNotShow?: boolean;
129
- desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
130
130
  name: any;
131
131
  dependencies: any[];
132
132
  tooltip: string | {
@@ -141,7 +141,7 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
141
141
  * 创建组件属性
142
142
  */
143
143
  export declare const createComponentProps: (column: FlexibleGroupColumnType, formItemProps: any) => {
144
- componentProps: import("lodash").Omit<any, "format" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "clearNotShow" | "precision">;
144
+ componentProps: import("lodash").Omit<any, "format" | "clearNotShow" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "precision">;
145
145
  formItemTransform: {
146
146
  getValueProps: any;
147
147
  normalize: any;
@@ -4,9 +4,7 @@ export interface TabsHeaderProps {
4
4
  tabsItems: TabsProps['items'];
5
5
  onTabChange: (activeKey: string) => void;
6
6
  onTabEdit: (targetKey: string, action: 'add' | 'remove') => void;
7
+ draggable?: boolean;
8
+ onReorder?: (activeId: string, overId: string) => void;
7
9
  }
8
- /**
9
- * 标签页头部:监听 window 滚动(实际滚动在 window/documentElement,非 body),
10
- * 滚动时添加阴影类名;仅此组件随滚动重渲染,不带动父组件内容区。
11
- */
12
- export declare function TabsHeader({ activeKey, tabsItems, onTabChange, onTabEdit }: TabsHeaderProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function TabsHeader({ activeKey, tabsItems, onTabChange, onTabEdit, draggable, onReorder, }: TabsHeaderProps): import("react/jsx-runtime").JSX.Element;
@@ -1,29 +1,57 @@
1
- import { useEffect, useState } from 'react';
1
+ import React, { createElement as _createElement } from 'react';
2
2
  import { Tabs } from 'antd';
3
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
- /**
5
- * 标签页头部:监听 window 滚动(实际滚动在 window/documentElement,非 body),
6
- * 滚动时添加阴影类名;仅此组件随滚动重渲染,不带动父组件内容区。
7
- */
3
+ import { closestCenter, DndContext, PointerSensor, useSensor } from '@dnd-kit/core';
4
+ import { horizontalListSortingStrategy, SortableContext, useSortable } from '@dnd-kit/sortable';
5
+ import { CSS } from '@dnd-kit/utilities';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ const DraggableTabNode = props => {
8
+ const {
9
+ attributes,
10
+ listeners,
11
+ setNodeRef,
12
+ transform,
13
+ transition
14
+ } = useSortable({
15
+ id: props['data-node-key']
16
+ });
17
+ const style = {
18
+ ...props.style,
19
+ transform: CSS.Translate.toString(transform),
20
+ transition,
21
+ cursor: 'move'
22
+ };
23
+ return /*#__PURE__*/React.cloneElement(props.children, {
24
+ ref: setNodeRef,
25
+ style,
26
+ ...attributes,
27
+ ...listeners
28
+ });
29
+ };
8
30
  export function TabsHeader({
9
31
  activeKey,
10
32
  tabsItems,
11
33
  onTabChange,
12
- onTabEdit
34
+ onTabEdit,
35
+ draggable = true,
36
+ onReorder
13
37
  }) {
14
- const [windowScrollY, setWindowScrollY] = useState(() => typeof window !== 'undefined' ? window.scrollY : 0);
15
- useEffect(() => {
16
- if (typeof window === 'undefined') return;
17
- const onScroll = () => setWindowScrollY(window.scrollY);
18
- window.addEventListener('scroll', onScroll, {
19
- passive: true
20
- });
21
- return () => window.removeEventListener('scroll', onScroll);
22
- }, []);
23
- const isScrolled = windowScrollY > 0;
24
- return /*#__PURE__*/_jsxs("div", {
38
+ const sensor = useSensor(PointerSensor, {
39
+ activationConstraint: {
40
+ distance: 10
41
+ }
42
+ });
43
+ const onDragEnd = ({
44
+ active,
45
+ over
46
+ }) => {
47
+ if (over && active.id !== over.id) {
48
+ onReorder?.(String(active.id), String(over.id));
49
+ }
50
+ };
51
+ const tabKeys = (tabsItems ?? []).map(item => item.key);
52
+ return /*#__PURE__*/_jsx("div", {
25
53
  className: "pro-layout-tabs-header",
26
- children: [/*#__PURE__*/_jsx(Tabs, {
54
+ children: /*#__PURE__*/_jsx(Tabs, {
27
55
  activeKey: activeKey,
28
56
  onChange: onTabChange,
29
57
  onEdit: onTabEdit,
@@ -35,10 +63,23 @@ export function TabsHeader({
35
63
  popup: {
36
64
  root: 'pro-layout-tabs-dropdown-menu'
37
65
  }
38
- }
39
- }), isScrolled && /*#__PURE__*/_jsx("div", {
40
- className: "pro-layout-tabs-header-shadow",
41
- "aria-hidden": true
42
- })]
66
+ },
67
+ renderTabBar: draggable ? (tabBarProps, DefaultTabBar) => /*#__PURE__*/_jsx(DndContext, {
68
+ sensors: [sensor],
69
+ onDragEnd: onDragEnd,
70
+ collisionDetection: closestCenter,
71
+ children: /*#__PURE__*/_jsx(SortableContext, {
72
+ items: tabKeys,
73
+ strategy: horizontalListSortingStrategy,
74
+ children: /*#__PURE__*/_jsx(DefaultTabBar, {
75
+ ...tabBarProps,
76
+ children: node => /*#__PURE__*/_createElement(DraggableTabNode, {
77
+ ...node.props,
78
+ key: node.key
79
+ }, node)
80
+ })
81
+ })
82
+ }) : undefined
83
+ })
43
84
  });
44
85
  }
@@ -1,5 +1,6 @@
1
1
  import { useState, useCallback, useEffect, useRef } from 'react';
2
2
  import { message } from 'antd';
3
+ import { arrayMove } from '@dnd-kit/sortable';
3
4
  import { DEFAULT_TABS_CONFIG } from "../propTypes";
4
5
  import { useTabsCache } from "./useTabsCache";
5
6
  import locale, { formatMessage } from "../../../../locale";
@@ -332,6 +333,21 @@ export const useTabsState = options => {
332
333
  });
333
334
  }, [clearCache, finalConfig]);
334
335
 
336
+ /**
337
+ * 重排标签页顺序(拖拽结束后调用)
338
+ */
339
+ const reorderTabs = useCallback((activeId, overId) => {
340
+ setState(prevState => {
341
+ const activeIndex = prevState.tabsList.findIndex(tab => tab.id === activeId);
342
+ const overIndex = prevState.tabsList.findIndex(tab => tab.id === overId);
343
+ if (activeIndex === -1 || overIndex === -1 || activeIndex === overIndex) return prevState;
344
+ return {
345
+ ...prevState,
346
+ tabsList: arrayMove(prevState.tabsList, activeIndex, overIndex)
347
+ };
348
+ });
349
+ }, []);
350
+
335
351
  /**
336
352
  * 重置状态
337
353
  */
@@ -388,6 +404,7 @@ export const useTabsState = options => {
388
404
  closeOtherTabs,
389
405
  closeRightTabs,
390
406
  closeAllTabs,
391
- resetTabs
407
+ resetTabs,
408
+ reorderTabs
392
409
  };
393
410
  };
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
2
+ import { createPortal } from 'react-dom';
2
3
  import { useTabsState } from "./hooks/useTabsState";
3
4
  import { TabItemComponent } from "./components/TabItem";
4
5
  import { TabsHeader } from "./components/TabsHeader";
@@ -12,9 +13,10 @@ const TabsManager = /*#__PURE__*/forwardRef(({
12
13
  config,
13
14
  children,
14
15
  dataSource,
15
- originalOnMenuClick
16
+ originalOnMenuClick,
17
+ tabsBarContainer,
18
+ onTabsChange
16
19
  }, ref) => {
17
- // 使用标签页状态管理Hook
18
20
  const {
19
21
  state,
20
22
  addTab,
@@ -23,7 +25,8 @@ const TabsManager = /*#__PURE__*/forwardRef(({
23
25
  switchTab,
24
26
  closeOtherTabs,
25
27
  closeRightTabs,
26
- closeAllTabs
28
+ closeAllTabs,
29
+ reorderTabs
27
30
  } = useTabsState({
28
31
  config,
29
32
  dataSource
@@ -44,14 +47,16 @@ const TabsManager = /*#__PURE__*/forwardRef(({
44
47
  }
45
48
  }, [state.activeKey]);
46
49
 
50
+ // 通知父级(ProLayout)当前是否有 Tab
51
+ useEffect(() => {
52
+ onTabsChange?.(state.tabsList.length > 0);
53
+ }, [state.tabsList.length, onTabsChange]);
54
+
47
55
  // 处理菜单点击 - 拦截原有的菜单点击逻辑
48
56
  const handleMenuClick = useCallback(params => {
49
57
  if (params.item) {
50
- // 添加到标签页
51
58
  addTab(params.item);
52
59
  }
53
-
54
- // 如果有原始的菜单点击处理函数,也调用它
55
60
  originalOnMenuClick?.(params);
56
61
  }, [addTab, originalOnMenuClick]);
57
62
 
@@ -65,11 +70,9 @@ const TabsManager = /*#__PURE__*/forwardRef(({
65
70
  } = params;
66
71
  const menuItem = {
67
72
  id: Date.now(),
68
- // 生成临时 ID
69
73
  code,
70
74
  name,
71
75
  url: `/${code}`,
72
- // 生成 URL
73
76
  extra
74
77
  };
75
78
  addTab(menuItem, options);
@@ -81,13 +84,9 @@ const TabsManager = /*#__PURE__*/forwardRef(({
81
84
  activeComponent: state.activeComponent
82
85
  })
83
86
  }), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent]);
84
-
85
- // 处理标签切换
86
87
  const handleTabChange = useCallback(activeKey => {
87
88
  switchTab(activeKey);
88
89
  }, [switchTab]);
89
-
90
- // 处理标签关闭
91
90
  const handleTabEdit = useCallback((targetKey, action) => {
92
91
  if (action === 'remove') {
93
92
  removeTab(targetKey);
@@ -111,12 +110,11 @@ const TabsManager = /*#__PURE__*/forwardRef(({
111
110
  menuItems: config?.menuItems,
112
111
  tabMenuClick: config?.tabMenuClick
113
112
  }),
114
- closable: tab.closable,
115
- children: null // 内容在 renderContent() 中渲染
113
+ closable: false,
114
+ // 关闭 antd 内置 ×,由 TabItemComponent 自定义按钮处理
115
+ children: null
116
116
  }));
117
117
  }, [state.tabsList, state.activeKey, config?.menuItems, config?.tabMenuClick, switchTab, removeTab, closeOtherTabs, closeRightTabs, closeAllTabs]);
118
-
119
- // 从 config 中获取组件解析函数和空状态组件
120
118
  const activeComponent = config?.activeComponent;
121
119
  const emptyComponent = config?.empty;
122
120
 
@@ -127,8 +125,6 @@ const TabsManager = /*#__PURE__*/forwardRef(({
127
125
  children: [state.tabsList.map(tab => {
128
126
  const isActive = tab.id === state.activeKey;
129
127
  const hasVisited = visitedTabIds.has(tab.id);
130
-
131
- // 如果既不是当前激活,也没有访问过,则只渲染占位符(懒加载)
132
128
  if (!isActive && !hasVisited) {
133
129
  return /*#__PURE__*/_jsx("div", {
134
130
  className: "tab-pane hidden",
@@ -136,12 +132,9 @@ const TabsManager = /*#__PURE__*/forwardRef(({
136
132
  }, tab.id);
137
133
  }
138
134
  let content = children;
139
-
140
- // 如果提供了组件解析函数,尝试解析组件
141
135
  if (activeComponent && tab.menuItem?.code) {
142
136
  const ResolvedComponent = activeComponent(tab.menuItem.code);
143
137
  if (ResolvedComponent) {
144
- // 将 extra 中的所有属性作为 props 传递给组件
145
138
  content = /*#__PURE__*/_jsx(ResolvedComponent, {
146
139
  ...(tab.menuItem.extra || {})
147
140
  });
@@ -160,23 +153,26 @@ const TabsManager = /*#__PURE__*/forwardRef(({
160
153
  });
161
154
  };
162
155
 
163
- // 暴露方法给父组件
156
+ // TabsHeader 节点:有 tabsBarContainer 时用 Portal 渲染到外部,否则原位 fallback
157
+ const tabsHeaderNode = state.tabsList.length > 0 ? /*#__PURE__*/_jsx(TabsHeader, {
158
+ activeKey: state.activeKey || undefined,
159
+ tabsItems: tabsItems,
160
+ onTabChange: handleTabChange,
161
+ onTabEdit: handleTabEdit,
162
+ draggable: config?.draggable !== false,
163
+ onReorder: reorderTabs
164
+ }) : null;
164
165
  useImperativeHandle(ref, () => ({
165
166
  handleMenuClick,
166
167
  canAddTab
167
168
  }), [handleMenuClick, canAddTab]);
168
- return /*#__PURE__*/_jsx(TabsContext.Provider, {
169
+ return /*#__PURE__*/_jsxs(TabsContext.Provider, {
169
170
  value: tabsInstance,
170
- children: /*#__PURE__*/_jsxs("div", {
171
+ children: [tabsBarContainer ? /*#__PURE__*/createPortal(tabsHeaderNode, tabsBarContainer) : tabsHeaderNode, /*#__PURE__*/_jsx("div", {
171
172
  className: "pro-layout-tabs",
172
173
  "data-testid": "tabs-manager",
173
- children: [state.tabsList.length > 0 && /*#__PURE__*/_jsx(TabsHeader, {
174
- activeKey: state.activeKey || undefined,
175
- tabsItems: tabsItems,
176
- onTabChange: handleTabChange,
177
- onTabEdit: handleTabEdit
178
- }), renderContent()]
179
- })
174
+ children: renderContent()
175
+ })]
180
176
  });
181
177
  });
182
178
  TabsManager.displayName = 'TabsManager';
@@ -14,6 +14,10 @@ export interface TabsManagerProps {
14
14
  key: string;
15
15
  keyPath: string[];
16
16
  }) => void;
17
+ /** Tab 栏的 Portal 挂载目标(由 ProLayout 提供),不传则原位渲染 */
18
+ tabsBarContainer?: HTMLElement | null;
19
+ /** Tab 有无变化回调,供 ProLayout 控制 wrapper 动画 */
20
+ onTabsChange?: (hasTabs: boolean) => void;
17
21
  }
18
22
  export interface TabContextMenuProps {
19
23
  tabId: string;
@@ -63,6 +67,8 @@ export interface UseTabsStateReturn {
63
67
  closeAllTabs: () => void;
64
68
  /** 重置状态 */
65
69
  resetTabs: () => void;
70
+ /** 重排标签页顺序 */
71
+ reorderTabs: (activeId: string, overId: string) => void;
66
72
  }
67
73
  export interface TabsCacheManager {
68
74
  save: (state: TabsState) => void;