@zat-design/sisyphus-react 4.3.0-beta.6 → 4.3.0-beta.7
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.
- package/dist/index.esm.css +1 -1
- package/dist/less.esm.css +1 -1
- package/es/ProEditTable/components/RenderField/index.js +1 -23
- package/es/ProEditTable/index.js +2 -6
- package/es/ProEditTable/propsType.d.ts +1 -3
- package/es/ProEditTable/style/index.less +0 -14
- package/es/ProEditTable/utils/index.js +0 -18
- package/es/ProForm/components/combination/Group/utils/index.d.ts +21 -21
- package/es/ProForm/utils/useForm.js +12 -10
- package/es/ProLayout/components/Layout/Menu/FoldMenu/index.js +11 -3
- package/es/ProLayout/components/Layout/Menu/OpenMenu/index.js +3 -3
- package/es/ProLayout/components/TabsManager/hooks/useTabsState.js +13 -11
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +14 -0
- package/es/ProLayout/components/TabsManager/utils/index.js +39 -5
- package/es/ProLayout/index.js +11 -7
- package/es/ProLayout/propTypes.d.ts +5 -0
- package/es/assets/edit.svg +1 -0
- package/package.json +1 -1
|
@@ -66,7 +66,6 @@ const RenderField = ({
|
|
|
66
66
|
const {
|
|
67
67
|
mode,
|
|
68
68
|
cellName,
|
|
69
|
-
cellNamePath,
|
|
70
69
|
_isEditing,
|
|
71
70
|
form,
|
|
72
71
|
setState,
|
|
@@ -89,11 +88,7 @@ const RenderField = ({
|
|
|
89
88
|
let _disabled = false;
|
|
90
89
|
let _desensitization = desensitization || [];
|
|
91
90
|
let _component = component || editRender;
|
|
92
|
-
const isCell = mode === 'cell';
|
|
93
91
|
const isSingleMode = mode === 'single';
|
|
94
|
-
if (isCell) {
|
|
95
|
-
record['is-view'] = !_isEqual(cellNamePath, cellName);
|
|
96
|
-
}
|
|
97
92
|
|
|
98
93
|
// type类型 首字母转大写
|
|
99
94
|
type = type?.replace?.(type[0], type[0].toUpperCase()) || 'Input';
|
|
@@ -512,12 +507,6 @@ const RenderField = ({
|
|
|
512
507
|
}
|
|
513
508
|
}
|
|
514
509
|
}
|
|
515
|
-
if (isCell) {
|
|
516
|
-
await form.validateFields([cellName]);
|
|
517
|
-
setState({
|
|
518
|
-
cellNamePath: []
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
510
|
// 单行编辑时需要 强制更新视图,仅对当前行打标记
|
|
522
511
|
compatStartTransition(() => {
|
|
523
512
|
setState({
|
|
@@ -526,7 +515,7 @@ const RenderField = ({
|
|
|
526
515
|
}
|
|
527
516
|
});
|
|
528
517
|
});
|
|
529
|
-
}, [onBlur, formatArgs, namePath, index, form, TargetComponent, validateTrigger, debounceValidate,
|
|
518
|
+
}, [onBlur, formatArgs, namePath, index, form, TargetComponent, validateTrigger, debounceValidate, cellName, setState]);
|
|
530
519
|
|
|
531
520
|
// 使用useMemo优化componentProps对象
|
|
532
521
|
let componentProps = useMemo(() => ({
|
|
@@ -556,20 +545,9 @@ const RenderField = ({
|
|
|
556
545
|
}
|
|
557
546
|
}), [cellName, _fieldProps, TargetComponent?.props, namePath, index, _disabled, _onChange, _onblur, confirm, _desensitization, names, originalName, viewEmpty, _valueType, isView, otherProps?.desensitizationKey]);
|
|
558
547
|
componentProps = _omit(componentProps, ['onFieldChange', 'namePath', 'index', ...OMIT_FORM_ITEM_AND_DOM_KEYS]);
|
|
559
|
-
|
|
560
|
-
// 单元格编辑时,设置各个单元格disabled属性
|
|
561
|
-
if (isCell) {
|
|
562
|
-
record[`${dataIndex}-Disabled`] = _disabled;
|
|
563
|
-
}
|
|
564
548
|
if (['Switch', 'SwitchCheckbox'].includes(type)) {
|
|
565
549
|
_formItemProps.valuePropName = 'checked';
|
|
566
550
|
}
|
|
567
|
-
useEffect(() => {
|
|
568
|
-
if (isCell) {
|
|
569
|
-
const editingDom = document.getElementById(cellNamePath.join('_'));
|
|
570
|
-
editingDom?.focus?.();
|
|
571
|
-
}
|
|
572
|
-
}, [cellNamePath, isCell]);
|
|
573
551
|
|
|
574
552
|
// 可编辑表格默认关闭scrollFollowParent
|
|
575
553
|
if (['Select', 'ProSelect', 'ProEnum'].includes(type)) {
|
package/es/ProEditTable/index.js
CHANGED
|
@@ -81,8 +81,6 @@ const ProEditTable = ({
|
|
|
81
81
|
return themeConfig?.data?.zauiFormAlign ?? configRequiredAlign ?? 'left';
|
|
82
82
|
}, [themeConfig?.data?.zauiFormAlign, configRequiredAlign]);
|
|
83
83
|
const [state, setState] = useSetState({
|
|
84
|
-
cellNamePath: [],
|
|
85
|
-
// hover模式下正在编辑的单元格路径
|
|
86
84
|
forceUpdate: {},
|
|
87
85
|
// 表格内部强制刷新开关
|
|
88
86
|
editingKeys: [],
|
|
@@ -99,7 +97,6 @@ const ProEditTable = ({
|
|
|
99
97
|
}
|
|
100
98
|
});
|
|
101
99
|
const {
|
|
102
|
-
cellNamePath,
|
|
103
100
|
forceUpdate,
|
|
104
101
|
editingKeys,
|
|
105
102
|
virtualKey,
|
|
@@ -186,7 +183,6 @@ const ProEditTable = ({
|
|
|
186
183
|
isView,
|
|
187
184
|
viewEmpty,
|
|
188
185
|
disabled,
|
|
189
|
-
cellNamePath,
|
|
190
186
|
forceUpdate,
|
|
191
187
|
insertType,
|
|
192
188
|
emptyBtnText,
|
|
@@ -221,7 +217,7 @@ const ProEditTable = ({
|
|
|
221
217
|
getIsNew,
|
|
222
218
|
handlePageChange,
|
|
223
219
|
...resetProps
|
|
224
|
-
}), [actionDirection, actionProps, actionWidth,
|
|
220
|
+
}), [actionDirection, actionProps, actionWidth, deletePoConfirmMsg, diffConfig, editingKeys, emptyBtnText, form,
|
|
225
221
|
// forceUpdate 不应该作为依赖项,因为它是触发刷新的信号,而非用于比较的数据
|
|
226
222
|
getIsNew, handlePageChange, insertType, isView, max, mode, mulDeletePoConfirmMsg, name, namePath, onlyOneLineMsg, originalValues, prefixCls, requiredAlign, resetProps, rowDisabled, selectedRowKeys, selectedRows, shouldUpdateDebounce, tableRef, toolbarProps, value?.length, viewEmpty, virtualKey, page]);
|
|
227
223
|
|
|
@@ -317,7 +313,7 @@ const ProEditTable = ({
|
|
|
317
313
|
};
|
|
318
314
|
const _columns = useMemo(() => {
|
|
319
315
|
return transformColumns(columns, config);
|
|
320
|
-
}, [disabled, forceUpdate, columns, page, actionProps, editingKeys,
|
|
316
|
+
}, [disabled, forceUpdate, columns, page, actionProps, editingKeys, config]);
|
|
321
317
|
const initDataSource = () => {
|
|
322
318
|
// 检查每一项是否有 rowKey 或通过 rowKey 字段获取的 key
|
|
323
319
|
const isAllHasKey = value?.every?.(item => item.rowKey || typeof rowKey === 'string' && item[rowKey]);
|
|
@@ -194,8 +194,6 @@ export type ProEditTableColumnsProps<K = any> = ProColumnsProps<K, 'ProEditTable
|
|
|
194
194
|
* 状态接口
|
|
195
195
|
*/
|
|
196
196
|
export interface StateProps {
|
|
197
|
-
/** 单元格名称路径 */
|
|
198
|
-
cellNamePath: NamePath[];
|
|
199
197
|
/** 表格内部强制刷新开关 */
|
|
200
198
|
forceUpdate: Record<string, any>;
|
|
201
199
|
/** 正在编辑行的row-key集合 */
|
|
@@ -232,7 +230,7 @@ export type ProEditTableRefType = ProEditTableRefProps;
|
|
|
232
230
|
/**
|
|
233
231
|
* 可编辑表格模式类型
|
|
234
232
|
*/
|
|
235
|
-
export type ProEditTableMode = 'single' | 'multiple'
|
|
233
|
+
export type ProEditTableMode = 'single' | 'multiple';
|
|
236
234
|
export type ProEditTableModeType = ProEditTableMode;
|
|
237
235
|
/**
|
|
238
236
|
* 表格插入类型
|
|
@@ -76,20 +76,6 @@
|
|
|
76
76
|
left: -9999px;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
.is-cell
|
|
80
|
-
.@{ant-prefix}-form-item
|
|
81
|
-
.@{ant-prefix}-form-item-row
|
|
82
|
-
.@{ant-prefix}-form-item-control
|
|
83
|
-
.@{ant-prefix}-form-item-control-input {
|
|
84
|
-
border: 1px solid transparent;
|
|
85
|
-
|
|
86
|
-
&:hover {
|
|
87
|
-
border: 1px solid #dee0e3;
|
|
88
|
-
border-radius: var(--zaui-border-radius, 8px);
|
|
89
|
-
text-indent: 5px;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
79
|
.pro-edit-table-drag-wrapper {
|
|
94
80
|
display: flex;
|
|
95
81
|
align-items: center;
|
|
@@ -185,7 +185,6 @@ export const transformColumns = (columns = [], config) => {
|
|
|
185
185
|
form,
|
|
186
186
|
name,
|
|
187
187
|
disabled,
|
|
188
|
-
cellNamePath,
|
|
189
188
|
editingKeys,
|
|
190
189
|
virtualKey,
|
|
191
190
|
requiredAlign,
|
|
@@ -193,7 +192,6 @@ export const transformColumns = (columns = [], config) => {
|
|
|
193
192
|
setState,
|
|
194
193
|
page
|
|
195
194
|
} = config;
|
|
196
|
-
const isCell = mode === 'cell';
|
|
197
195
|
let nextColumns = columns.map(item => cloneDeepFilterNode(item));
|
|
198
196
|
const pageNum = tools.calc(page.pageNum, '-', 1);
|
|
199
197
|
const firstIndex = tools.calc(pageNum, '*', page.pageSize);
|
|
@@ -304,25 +302,9 @@ export const transformColumns = (columns = [], config) => {
|
|
|
304
302
|
isResponsiveWidth: true
|
|
305
303
|
});
|
|
306
304
|
}
|
|
307
|
-
let cellClassName;
|
|
308
|
-
const isCanEdit = isCell && !record[`${columnName}-Disabled`] && item.isEditable !== false;
|
|
309
|
-
// 单元格编辑场景下,需要将所有单元格设置为不可编辑状态,当鼠标hover时,展示输出框。点击可修改
|
|
310
|
-
if (isCanEdit) {
|
|
311
|
-
record['is-view'] = true;
|
|
312
|
-
// 样式处理
|
|
313
|
-
cellClassName = classnames({
|
|
314
|
-
'is-cell': !_isEqual(cellNamePath, cellName)
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
305
|
|
|
318
306
|
// 单行多行交互有较大差异
|
|
319
307
|
return /*#__PURE__*/_jsxs("div", {
|
|
320
|
-
className: cellClassName,
|
|
321
|
-
onClick: () => {
|
|
322
|
-
isCanEdit && setState({
|
|
323
|
-
cellNamePath: cellName
|
|
324
|
-
});
|
|
325
|
-
},
|
|
326
308
|
children: [index === 0 && !item.width ? /*#__PURE__*/_jsx("div", {
|
|
327
309
|
style: {
|
|
328
310
|
visibility: 'hidden',
|
|
@@ -75,58 +75,58 @@ 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
|
-
|
|
79
|
-
trim?: boolean;
|
|
80
|
-
normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
|
|
78
|
+
status?: "" | "success" | "warning" | "error" | "validating";
|
|
81
79
|
className?: string;
|
|
82
|
-
style?: React.CSSProperties;
|
|
83
|
-
preserve?: boolean;
|
|
84
|
-
id?: string;
|
|
85
80
|
hidden?: boolean;
|
|
81
|
+
id?: string;
|
|
82
|
+
style?: React.CSSProperties;
|
|
83
|
+
children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
|
|
86
84
|
onReset?: () => void;
|
|
87
85
|
prefixCls?: string;
|
|
88
86
|
rootClassName?: string;
|
|
89
|
-
|
|
90
|
-
help?: React.ReactNode;
|
|
91
|
-
vertical?: boolean;
|
|
87
|
+
colon?: boolean;
|
|
92
88
|
htmlFor?: string;
|
|
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;
|
|
100
89
|
labelAlign?: import("antd/es/form/interface").FormLabelAlign;
|
|
101
|
-
colon?: boolean;
|
|
102
90
|
labelCol?: import("antd").ColProps;
|
|
103
|
-
|
|
91
|
+
vertical?: boolean;
|
|
104
92
|
getValueFromEvent?: (...args: import("@rc-component/form/lib/interface").EventArgs) => any;
|
|
93
|
+
normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
|
|
105
94
|
shouldUpdate?: import("@rc-component/form/lib/Field").ShouldUpdate<any>;
|
|
95
|
+
trigger?: string;
|
|
96
|
+
validateTrigger?: string | false | string[];
|
|
106
97
|
validateDebounce?: number;
|
|
107
98
|
valuePropName?: string;
|
|
99
|
+
getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
|
|
108
100
|
messageVariables?: Record<string, string>;
|
|
109
101
|
initialValue?: any;
|
|
110
102
|
onMetaChange?: (meta: import("@rc-component/form/lib/Field").MetaEvent) => void;
|
|
103
|
+
preserve?: boolean;
|
|
111
104
|
isListField?: boolean;
|
|
112
105
|
isList?: boolean;
|
|
113
106
|
noStyle?: boolean;
|
|
114
107
|
hasFeedback?: boolean | {
|
|
115
108
|
icons: import("antd/es/form/FormItem").FeedbackIcons;
|
|
116
109
|
};
|
|
117
|
-
validateStatus?: "" | "
|
|
110
|
+
validateStatus?: "" | "success" | "warning" | "error" | "validating";
|
|
111
|
+
layout?: import("antd/es/form/Form").FormItemLayout;
|
|
112
|
+
wrapperCol?: import("antd").ColProps;
|
|
113
|
+
help?: React.ReactNode;
|
|
118
114
|
fieldId?: string;
|
|
119
115
|
valueType?: import("../../../render/propsType").ProFormValueType;
|
|
120
|
-
switchValue?: [any, any];
|
|
121
116
|
viewRender?: (value: any, record: any, { form, index, namePath, }: {
|
|
122
117
|
[key: string]: any;
|
|
123
118
|
form: FormInstance<any>;
|
|
124
119
|
index?: number;
|
|
125
120
|
}) => string | React.ReactElement<any, any>;
|
|
121
|
+
desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
|
|
122
|
+
isView?: boolean;
|
|
123
|
+
switchValue?: [any, any];
|
|
126
124
|
viewType?: import("../../../render/propsType").ViewType;
|
|
125
|
+
trim?: boolean;
|
|
127
126
|
upperCase?: boolean;
|
|
128
127
|
toISOString?: boolean;
|
|
129
128
|
toCSTString?: boolean;
|
|
129
|
+
clearNotShow?: boolean;
|
|
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, "
|
|
144
|
+
componentProps: import("lodash").Omit<any, "valueType" | "precision" | "format" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "clearNotShow">;
|
|
145
145
|
formItemTransform: {
|
|
146
146
|
getValueProps: any;
|
|
147
147
|
normalize: any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Form } from 'antd';
|
|
2
|
-
import { useContext
|
|
2
|
+
import { useContext } from 'react';
|
|
3
3
|
import { filterInternalFields, getFormFieldPaths, mergeNames, isListForm } from "./index";
|
|
4
4
|
import { handleScrollToError as handleScrollToErrorProEditTable } from "../../ProEditTable/utils/tools";
|
|
5
5
|
import { FormsContext } from "../../FormsProvider";
|
|
@@ -19,15 +19,17 @@ export const useForm = function (originForm, options) {
|
|
|
19
19
|
|
|
20
20
|
// formKey全局共享逻辑
|
|
21
21
|
const forms = useContext(FormsContext);
|
|
22
|
-
const [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const [localForm] = Form.useForm(_originForm);
|
|
23
|
+
// 同步注册共享实例,确保同一渲染流程中相同 formKey 复用同一个实例
|
|
24
|
+
if (formKey && !forms[formKey]) {
|
|
25
|
+
forms[formKey] = localForm;
|
|
26
|
+
localForm.formKey = formKey;
|
|
27
|
+
}
|
|
28
|
+
const sharedForm = formKey ? forms[formKey] : undefined;
|
|
29
|
+
const form = sharedForm || localForm;
|
|
30
|
+
if (formKey && form.formKey !== formKey) {
|
|
31
|
+
form.formKey = formKey;
|
|
32
|
+
}
|
|
31
33
|
const {
|
|
32
34
|
getFieldsValue,
|
|
33
35
|
validateFields,
|
|
@@ -109,7 +109,7 @@ const FoldMenu = props => {
|
|
|
109
109
|
},
|
|
110
110
|
placement: "rightTop",
|
|
111
111
|
children: /*#__PURE__*/_jsx("span", {
|
|
112
|
-
onClick:
|
|
112
|
+
onClick: e => {
|
|
113
113
|
// 查找完整的菜单项数据
|
|
114
114
|
const menuItem = findMenuItemByKey(menus, String(id));
|
|
115
115
|
const menuKeyPath = menuItem?.keyIdPath ? menuItem.keyIdPath.map(id => String(id)) : [String(id)];
|
|
@@ -141,7 +141,7 @@ const FoldMenu = props => {
|
|
|
141
141
|
placement: "right",
|
|
142
142
|
title: name,
|
|
143
143
|
children: /*#__PURE__*/_jsx("span", {
|
|
144
|
-
|
|
144
|
+
onClickCapture: e => {
|
|
145
145
|
// 查找完整的菜单项数据
|
|
146
146
|
const menuItem = findMenuItemByKey(menus, String(id));
|
|
147
147
|
const menuKeyPath = menuItem?.keyIdPath ? menuItem.keyIdPath.map(id => String(id)) : [String(id)];
|
|
@@ -165,6 +165,10 @@ const FoldMenu = props => {
|
|
|
165
165
|
onSelected({
|
|
166
166
|
selectedPath: toPath
|
|
167
167
|
});
|
|
168
|
+
} else {
|
|
169
|
+
// Capture 阶段拦截,早于 Link 内部导航处理器
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
e.stopPropagation();
|
|
168
172
|
}
|
|
169
173
|
},
|
|
170
174
|
children: /*#__PURE__*/_jsx(Link, {
|
|
@@ -173,7 +177,7 @@ const FoldMenu = props => {
|
|
|
173
177
|
})
|
|
174
178
|
})
|
|
175
179
|
}, `${id}-${name}`) : /*#__PURE__*/_jsx("span", {
|
|
176
|
-
|
|
180
|
+
onClickCapture: e => {
|
|
177
181
|
// 查找完整的菜单项数据
|
|
178
182
|
const menuItem = findMenuItemByKey(menus, String(id));
|
|
179
183
|
const menuKeyPath = menuItem?.keyIdPath ? menuItem.keyIdPath.map(id => String(id)) : [String(id)];
|
|
@@ -197,6 +201,10 @@ const FoldMenu = props => {
|
|
|
197
201
|
onSelected({
|
|
198
202
|
selectedPath: toPath
|
|
199
203
|
});
|
|
204
|
+
} else {
|
|
205
|
+
// Capture 阶段拦截,早于 Link 内部导航处理器
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
e.stopPropagation();
|
|
200
208
|
}
|
|
201
209
|
},
|
|
202
210
|
children: /*#__PURE__*/_jsx(Link, {
|
|
@@ -7,7 +7,7 @@ import classnames from 'classnames';
|
|
|
7
7
|
import { Link as RouterLink } from 'react-router-dom';
|
|
8
8
|
import { LayoutContext } from "../../../../index";
|
|
9
9
|
import { getIdsByPathName, findMenuItemByKey } from "../../../../utils";
|
|
10
|
-
import {
|
|
10
|
+
import { canOpenAsTab } from "../../../TabsManager/utils";
|
|
11
11
|
import { Icon } from "../../index";
|
|
12
12
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
const Link = RouterLink;
|
|
@@ -163,9 +163,9 @@ const OpenMenu = props => {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
//
|
|
166
|
+
// 可入签节点(叶子 + 根节点特例)才设置选中状态和路径
|
|
167
167
|
// 并且只有在 shouldActivate 为 true 时才设置
|
|
168
|
-
if (menuItem &&
|
|
168
|
+
if (menuItem && canOpenAsTab(menuItem) && shouldActivate) {
|
|
169
169
|
setState({
|
|
170
170
|
selectedKeys: keyPath,
|
|
171
171
|
router: item?.props?.router
|
|
@@ -4,7 +4,7 @@ import { arrayMove } from '@dnd-kit/sortable';
|
|
|
4
4
|
import { DEFAULT_TABS_CONFIG } from "../propTypes";
|
|
5
5
|
import { useTabsCache } from "./useTabsCache";
|
|
6
6
|
import locale, { formatMessage } from "../../../../locale";
|
|
7
|
-
import { createTabFromMenu, generateTabId, shouldOpenExternal, handleExternalOpen, checkTabLimit, getRightTabs, flattenMenuData,
|
|
7
|
+
import { createTabFromMenu, generateTabId, shouldOpenExternal, handleExternalOpen, checkTabLimit, getRightTabs, flattenMenuData, canOpenAsTab, getMenuRoute } from "../utils";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* 标签页状态管理Hook
|
|
@@ -107,8 +107,8 @@ export const useTabsState = options => {
|
|
|
107
107
|
forceNew = false
|
|
108
108
|
} = options || {};
|
|
109
109
|
|
|
110
|
-
//
|
|
111
|
-
if (!
|
|
110
|
+
// 非叶子节点默认不入签,仅根节点特例可入签
|
|
111
|
+
if (!canOpenAsTab(menuItem)) {
|
|
112
112
|
return false;
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -119,7 +119,8 @@ export const useTabsState = options => {
|
|
|
119
119
|
|
|
120
120
|
// 如果 forceNew = false(默认),检查是否已存在相同的标签页
|
|
121
121
|
if (!forceNew) {
|
|
122
|
-
const
|
|
122
|
+
const menuRoute = getMenuRoute(menuItem);
|
|
123
|
+
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === menuItem.code || tab.url === menuRoute);
|
|
123
124
|
if (existingTab) {
|
|
124
125
|
// 如果已存在,可以添加(会切换到已存在的标签页)
|
|
125
126
|
return true;
|
|
@@ -142,8 +143,8 @@ export const useTabsState = options => {
|
|
|
142
143
|
forceNew = false
|
|
143
144
|
} = options || {};
|
|
144
145
|
setState(prevState => {
|
|
145
|
-
//
|
|
146
|
-
if (!
|
|
146
|
+
// 非叶子节点默认不入签,仅根节点特例可入签
|
|
147
|
+
if (!canOpenAsTab(menuItem)) {
|
|
147
148
|
return prevState;
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -157,7 +158,8 @@ export const useTabsState = options => {
|
|
|
157
158
|
|
|
158
159
|
// 如果 forceNew = false(默认),检查是否已存在相同的标签页
|
|
159
160
|
if (!forceNew) {
|
|
160
|
-
const
|
|
161
|
+
const menuRoute = getMenuRoute(menuItem);
|
|
162
|
+
const existingTab = prevState.tabsList.find(tab => tab.menuItem?.code === menuItem.code || tab.url === menuRoute);
|
|
161
163
|
if (existingTab) {
|
|
162
164
|
// 如果已存在,直接切换到该标签页
|
|
163
165
|
const newState = {
|
|
@@ -375,15 +377,15 @@ export const useTabsState = options => {
|
|
|
375
377
|
|
|
376
378
|
// 在菜单中查找当前 URL 对应的菜单项
|
|
377
379
|
const flatMenus = flattenMenuData(dataSource.menus || []);
|
|
378
|
-
const targetMenu = flatMenus.find(item => item
|
|
380
|
+
const targetMenu = flatMenus.find(item => getMenuRoute(item) === currentPath);
|
|
379
381
|
if (targetMenu) {
|
|
380
|
-
//
|
|
381
|
-
if (!
|
|
382
|
+
// 非叶子节点默认不入签,仅根节点特例可入签
|
|
383
|
+
if (!canOpenAsTab(targetMenu)) {
|
|
382
384
|
return;
|
|
383
385
|
}
|
|
384
386
|
|
|
385
387
|
// 检查是否已在 Tabs 中
|
|
386
|
-
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === targetMenu
|
|
388
|
+
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === getMenuRoute(targetMenu));
|
|
387
389
|
if (existingTab) {
|
|
388
390
|
// 如果已存在,切换到该 Tab
|
|
389
391
|
if (state.activeKey !== existingTab.id) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { TabItem, MenusType } from '../../../propTypes';
|
|
2
|
+
/**
|
|
3
|
+
* 获取菜单实际用于路由/签页的路径
|
|
4
|
+
* 约定优先使用 redirectUrl,其次回退 url
|
|
5
|
+
*/
|
|
6
|
+
export declare const getMenuRoute: (menuItem: MenusType) => string;
|
|
2
7
|
/**
|
|
3
8
|
* 根据菜单项生成TabItem
|
|
4
9
|
*/
|
|
@@ -39,3 +44,12 @@ export declare const flattenMenuData: (menus?: MenusType[]) => MenusType[];
|
|
|
39
44
|
* 只有当菜单项没有子菜单或子菜单为空时,才认为是叶子节点
|
|
40
45
|
*/
|
|
41
46
|
export declare const isLeafMenuItem: (menuItem: MenusType) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* 判断菜单项是否为根节点
|
|
49
|
+
*/
|
|
50
|
+
export declare const isRootMenuItem: (menuItem: MenusType) => boolean;
|
|
51
|
+
/**
|
|
52
|
+
* 判断菜单项是否可以在 Tabs 模式下打开
|
|
53
|
+
* 规则:叶子节点可打开;有 children 时仅根节点可打开
|
|
54
|
+
*/
|
|
55
|
+
export declare const canOpenAsTab: (menuItem: MenusType) => boolean;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取菜单实际用于路由/签页的路径
|
|
3
|
+
* 约定优先使用 redirectUrl,其次回退 url
|
|
4
|
+
*/
|
|
5
|
+
export const getMenuRoute = menuItem => {
|
|
6
|
+
return menuItem.redirectUrl || menuItem.url || '';
|
|
7
|
+
};
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* 根据菜单项生成TabItem
|
|
3
11
|
*/
|
|
4
12
|
export const createTabFromMenu = (menuItem, index = 0) => {
|
|
13
|
+
const route = getMenuRoute(menuItem);
|
|
5
14
|
return {
|
|
6
15
|
id: String(menuItem.id || menuItem.code || menuItem.url || index),
|
|
7
16
|
code: menuItem.code,
|
|
8
17
|
name: menuItem.name,
|
|
9
18
|
title: menuItem.name,
|
|
10
|
-
url:
|
|
11
|
-
closable:
|
|
19
|
+
url: route,
|
|
20
|
+
closable: menuItem.closable !== false,
|
|
12
21
|
menuItem,
|
|
13
22
|
icon: menuItem.icon || menuItem.imgUrl
|
|
14
23
|
};
|
|
@@ -34,16 +43,17 @@ export const generateTabId = (menuItem, existingIds) => {
|
|
|
34
43
|
* 检查菜单项是否应该外部跳转
|
|
35
44
|
*/
|
|
36
45
|
export const shouldOpenExternal = (menuItem, target) => {
|
|
37
|
-
|
|
46
|
+
const menuTarget = menuItem?.target || target;
|
|
47
|
+
return menuTarget === '_blank' || menuItem.type === 'EXTERNAL';
|
|
38
48
|
};
|
|
39
49
|
|
|
40
50
|
/**
|
|
41
51
|
* 处理外部跳转
|
|
42
52
|
*/
|
|
43
53
|
export const handleExternalOpen = menuItem => {
|
|
44
|
-
const url = menuItem
|
|
54
|
+
const url = getMenuRoute(menuItem);
|
|
45
55
|
if (url) {
|
|
46
|
-
window.open(url, '_blank');
|
|
56
|
+
window.open(url, menuItem.target || '_blank');
|
|
47
57
|
}
|
|
48
58
|
};
|
|
49
59
|
|
|
@@ -109,4 +119,28 @@ export const isLeafMenuItem = menuItem => {
|
|
|
109
119
|
} = menuItem || {};
|
|
110
120
|
// 如果 children 不存在、为 null、为 undefined,或者为空数组,则认为是叶子节点
|
|
111
121
|
return !children || Array.isArray(children) && children.length === 0;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 判断菜单项是否为根节点
|
|
126
|
+
*/
|
|
127
|
+
export const isRootMenuItem = menuItem => {
|
|
128
|
+
if (!menuItem) return false;
|
|
129
|
+
if (menuItem.parentId === null || menuItem.parentId === undefined) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return Array.isArray(menuItem.keyIdPath) && menuItem.keyIdPath.length === 1;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 判断菜单项是否可以在 Tabs 模式下打开
|
|
137
|
+
* 规则:叶子节点可打开;有 children 时仅根节点可打开
|
|
138
|
+
*/
|
|
139
|
+
export const canOpenAsTab = menuItem => {
|
|
140
|
+
if (!menuItem) return false;
|
|
141
|
+
if (!(menuItem.url || menuItem.redirectUrl)) return false;
|
|
142
|
+
if (isLeafMenuItem(menuItem)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return isRootMenuItem(menuItem);
|
|
112
146
|
};
|
package/es/ProLayout/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { isTabsMode, validateTabsProps } from "./propTypes";
|
|
|
11
11
|
import { useProLayoutTabs } from "./components/TabsManager/hooks/useProLayoutTabs";
|
|
12
12
|
import { useActiveTab } from "./components/TabsManager/hooks/useActiveTab";
|
|
13
13
|
import { transformMenus } from "./utils";
|
|
14
|
-
import {
|
|
14
|
+
import { canOpenAsTab } from "./components/TabsManager/utils";
|
|
15
15
|
import headerBg from "../assets/header_bg.png";
|
|
16
16
|
|
|
17
17
|
// 全局上下文
|
|
@@ -99,7 +99,7 @@ const ProLayout = props => {
|
|
|
99
99
|
});
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
const targetPath = activeTab?.url || activeTab?.menuItem?.router || activeTab?.menuItem?.
|
|
102
|
+
const targetPath = activeTab?.url || activeTab?.menuItem?.router || activeTab?.menuItem?.redirectUrl || activeTab?.menuItem?.url;
|
|
103
103
|
if (targetPath && targetPath !== selectedPath) {
|
|
104
104
|
setState({
|
|
105
105
|
selectedPath: targetPath
|
|
@@ -134,13 +134,16 @@ const ProLayout = props => {
|
|
|
134
134
|
|
|
135
135
|
// tabs 模式:调用 TabsManager 的 handleMenuClick 来添加标签页
|
|
136
136
|
tabsManagerRef.current?.handleMenuClick?.(params);
|
|
137
|
+
if (!params.item) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
137
140
|
|
|
138
|
-
//
|
|
139
|
-
if (!
|
|
141
|
+
// 根节点可入签;中间层级(有 children)不激活;叶子入签
|
|
142
|
+
if (!canOpenAsTab(params.item)) {
|
|
140
143
|
return true;
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
//
|
|
146
|
+
// 可入签节点:检查是否可以添加标签页(检查 max 限制、_blank 等)
|
|
144
147
|
const canAdd = tabsManagerRef.current?.canAddTab?.(params.item);
|
|
145
148
|
if (!canAdd) {
|
|
146
149
|
// 超过限制,返回 false 阻止菜单激活
|
|
@@ -148,13 +151,14 @@ const ProLayout = props => {
|
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
// 可以添加标签页,设置选中路径(激活菜单)
|
|
151
|
-
const targetPath = params.item?.
|
|
154
|
+
const targetPath = params.item?.redirectUrl || params.item?.url || params.item?.router;
|
|
152
155
|
if (targetPath && targetPath !== selectedPath) {
|
|
153
156
|
setState({
|
|
154
157
|
selectedPath: targetPath
|
|
155
158
|
});
|
|
156
159
|
}
|
|
157
|
-
|
|
160
|
+
// tabs 模式下由 Tab 管理内容,阻止菜单默认 Link 跳转
|
|
161
|
+
return false;
|
|
158
162
|
}, [isTabsLayout, onMenuClick, selectedPath]);
|
|
159
163
|
|
|
160
164
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1775025459029" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7119" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><path d="M181.10185262 930.4882664c-18.69801175 0-33.42977859-15.29837325-33.42977858-33.42977858 0-18.69801175 15.29837325-33.42977859 33.42977858-33.4297786h691.25982844c18.69801175 0 33.42977859 15.29837325 33.42977859 33.4297786 0 18.69801175-15.29837325 33.42977859-33.42977859 33.42977858h-691.25982844z m671.42860384-747.92047012L752.80772713 82.84506694c-12.46534117-12.46534117-32.86317217-12.46534117-45.32851335 0l-100.85594218 100.85594218 145.05124269 145.05124269 100.85594217-100.85594218c12.46534117-12.46534117 12.46534117-32.29656575 0-45.32851335z m-275.93732495 31.16335293L240.59552637 549.72875434c-9.06570266 9.06570266-16.9981925 28.89692726-16.9981925 28.89692726l-52.12779033 165.44907369c-3.96624492 12.46534117 7.93248983 23.7974695 20.397831 20.397831l164.31586085-52.12779034s19.83122459-7.36588342 29.46353368-17.56479891L721.6443742 358.7823919 576.59313151 213.73114921z" fill="#2C2C2C" p-id="7120"></path></svg>
|