@zat-design/sisyphus-react 4.3.0-beta.8 → 4.3.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.
- package/es/ProForm/components/combination/Group/utils/index.d.ts +8 -8
- package/es/ProLayout/components/TabsManager/hooks/useTabsState.js +35 -60
- package/es/ProLayout/components/TabsManager/index.js +41 -5
- package/es/ProLayout/components/TabsManager/propTypes.d.ts +2 -0
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +1 -1
- package/es/ProLayout/components/TabsManager/utils/index.js +4 -6
- package/es/ProLayout/index.js +18 -5
- package/es/ProLayout/propTypes.d.ts +5 -0
- package/es/ProTable/components/EditableCell/EditIcon.d.ts +6 -0
- package/es/ProTable/components/EditableCell/EditIcon.js +19 -0
- package/es/ProTable/components/EditableCell/index.d.ts +4 -0
- package/es/ProTable/components/EditableCell/index.js +87 -0
- package/es/ProTable/components/EditableCell/index.less +29 -0
- package/es/ProTable/components/EditableCell/propsType.d.ts +10 -0
- package/es/ProTable/components/EditableCell/propsType.js +1 -0
- package/es/ProTable/components/FormatColumn/index.js +26 -2
- package/es/ProTable/components/index.d.ts +1 -0
- package/es/ProTable/components/index.js +2 -1
- package/es/ProTable/propsType.d.ts +20 -0
- package/package.json +1 -3
|
@@ -75,20 +75,21 @@ 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
|
-
|
|
78
|
+
id?: string;
|
|
79
79
|
className?: string;
|
|
80
80
|
hidden?: boolean;
|
|
81
|
-
id?: string;
|
|
82
81
|
style?: React.CSSProperties;
|
|
83
82
|
children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
|
|
84
83
|
onReset?: () => void;
|
|
85
84
|
prefixCls?: string;
|
|
86
85
|
rootClassName?: string;
|
|
86
|
+
status?: "" | "warning" | "error" | "success" | "validating";
|
|
87
|
+
vertical?: boolean;
|
|
88
|
+
isView?: boolean;
|
|
87
89
|
colon?: boolean;
|
|
88
90
|
htmlFor?: string;
|
|
89
91
|
labelAlign?: import("antd/es/form/interface").FormLabelAlign;
|
|
90
92
|
labelCol?: import("antd").ColProps;
|
|
91
|
-
vertical?: boolean;
|
|
92
93
|
getValueFromEvent?: (...args: import("@rc-component/form/lib/interface").EventArgs) => any;
|
|
93
94
|
normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
|
|
94
95
|
shouldUpdate?: import("@rc-component/form/lib/Field").ShouldUpdate<any>;
|
|
@@ -107,26 +108,25 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
|
|
|
107
108
|
hasFeedback?: boolean | {
|
|
108
109
|
icons: import("antd/es/form/FormItem").FeedbackIcons;
|
|
109
110
|
};
|
|
110
|
-
validateStatus?: "" | "
|
|
111
|
+
validateStatus?: "" | "warning" | "error" | "success" | "validating";
|
|
111
112
|
layout?: import("antd/es/form/Form").FormItemLayout;
|
|
112
113
|
wrapperCol?: import("antd").ColProps;
|
|
113
114
|
help?: React.ReactNode;
|
|
114
115
|
fieldId?: string;
|
|
115
116
|
valueType?: import("../../../render/propsType").ProFormValueType;
|
|
117
|
+
switchValue?: [any, any];
|
|
116
118
|
viewRender?: (value: any, record: any, { form, index, namePath, }: {
|
|
117
119
|
[key: string]: any;
|
|
118
120
|
form: FormInstance<any>;
|
|
119
121
|
index?: number;
|
|
120
122
|
}) => string | React.ReactElement<any, any>;
|
|
121
|
-
desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
|
|
122
|
-
isView?: boolean;
|
|
123
|
-
switchValue?: [any, any];
|
|
124
123
|
viewType?: import("../../../render/propsType").ViewType;
|
|
125
124
|
trim?: boolean;
|
|
126
125
|
upperCase?: boolean;
|
|
127
126
|
toISOString?: boolean;
|
|
128
127
|
toCSTString?: boolean;
|
|
129
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, "
|
|
144
|
+
componentProps: import("lodash").Omit<any, "format" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "clearNotShow" | "precision">;
|
|
145
145
|
formItemTransform: {
|
|
146
146
|
getValueProps: any;
|
|
147
147
|
normalize: any;
|
|
@@ -143,49 +143,41 @@ export const useTabsState = options => {
|
|
|
143
143
|
forceNew = false
|
|
144
144
|
} = options || {};
|
|
145
145
|
setState(prevState => {
|
|
146
|
-
|
|
147
|
-
if (!canOpenAsTab(menuItem)) {
|
|
148
|
-
return prevState;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 检查是否需要外部跳转
|
|
146
|
+
if (!canOpenAsTab(menuItem)) return prevState;
|
|
152
147
|
if (shouldOpenExternal(menuItem)) {
|
|
153
148
|
handleExternalOpen(menuItem);
|
|
154
149
|
return prevState;
|
|
155
150
|
}
|
|
156
|
-
const existingIds = prevState.tabsList.map(tab => tab.id);
|
|
157
|
-
const tabId = generateTabId(menuItem, existingIds);
|
|
158
151
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
152
|
+
// 非强制新建时,查找已存在的标签页并切换
|
|
153
|
+
const menuRoute = getMenuRoute(menuItem);
|
|
154
|
+
const existingTab = !forceNew ? prevState.tabsList.find(tab => tab.menuItem?.code === menuItem.code || tab.url === menuRoute) : undefined;
|
|
155
|
+
if (existingTab) {
|
|
156
|
+
// 若来源菜单明确标记 closable: false 而已有 tab 未继承,则修正
|
|
157
|
+
const correctedTab = menuItem.closable === false && existingTab.closable !== false ? {
|
|
158
|
+
...existingTab,
|
|
159
|
+
closable: false
|
|
160
|
+
} : existingTab;
|
|
161
|
+
const updatedTabsList = correctedTab !== existingTab ? prevState.tabsList.map(t => t.id === existingTab.id ? correctedTab : t) : prevState.tabsList;
|
|
162
|
+
return {
|
|
163
|
+
...prevState,
|
|
164
|
+
tabsList: updatedTabsList,
|
|
165
|
+
activeKey: existingTab.id,
|
|
166
|
+
activeTabInfo: correctedTab,
|
|
167
|
+
activeComponent: existingTab.menuItem?.code || existingTab.id
|
|
168
|
+
};
|
|
173
169
|
}
|
|
174
|
-
|
|
175
|
-
// 检查是否超出限制
|
|
176
170
|
if (checkTabLimit(prevState.tabsList, finalConfig.max)) {
|
|
177
|
-
|
|
178
|
-
const messageText = formatMessage(locale.ProLayout.tabMaxLimitMessage, {
|
|
171
|
+
message.info(formatMessage(locale.ProLayout.tabMaxLimitMessage, {
|
|
179
172
|
max: finalConfig.max
|
|
180
|
-
});
|
|
181
|
-
message.info(messageText);
|
|
173
|
+
}));
|
|
182
174
|
return prevState;
|
|
183
175
|
}
|
|
184
|
-
|
|
185
|
-
|
|
176
|
+
const existingIds = prevState.tabsList.map(tab => tab.id);
|
|
177
|
+
const tabId = generateTabId(menuItem, existingIds);
|
|
186
178
|
const newTab = createTabFromMenu(menuItem, prevState.newTabIndex);
|
|
187
179
|
newTab.id = tabId;
|
|
188
|
-
|
|
180
|
+
return {
|
|
189
181
|
...prevState,
|
|
190
182
|
tabsList: [...prevState.tabsList, newTab],
|
|
191
183
|
activeKey: tabId,
|
|
@@ -193,7 +185,6 @@ export const useTabsState = options => {
|
|
|
193
185
|
newTabIndex: prevState.newTabIndex + 1,
|
|
194
186
|
activeComponent: menuItem.code || tabId
|
|
195
187
|
};
|
|
196
|
-
return newState;
|
|
197
188
|
});
|
|
198
189
|
}, [finalConfig]);
|
|
199
190
|
|
|
@@ -319,17 +310,18 @@ export const useTabsState = options => {
|
|
|
319
310
|
shouldSkipSaveRef.current = true;
|
|
320
311
|
clearCache();
|
|
321
312
|
|
|
322
|
-
//
|
|
323
|
-
if (
|
|
313
|
+
// 直接调用 onTabChange(useEffect 因 shouldSkipSaveRef 会跳过)
|
|
314
|
+
if (finalConfig.onTabChange) {
|
|
315
|
+
const firstTab = newTabs.length > 0 ? newTabs[0] : undefined;
|
|
324
316
|
setTimeout(() => {
|
|
325
|
-
finalConfig.onTabChange('',
|
|
317
|
+
finalConfig.onTabChange(firstTab?.id ?? '', firstTab, newTabs);
|
|
326
318
|
}, 0);
|
|
327
319
|
}
|
|
328
320
|
return {
|
|
329
321
|
...prevState,
|
|
330
322
|
tabsList: newTabs,
|
|
331
323
|
activeKey: newActiveKey,
|
|
332
|
-
activeTabInfo: undefined,
|
|
324
|
+
activeTabInfo: newTabs.length > 0 ? newTabs[0] : undefined,
|
|
333
325
|
activeComponent: newActiveComponent
|
|
334
326
|
};
|
|
335
327
|
});
|
|
@@ -365,40 +357,23 @@ export const useTabsState = options => {
|
|
|
365
357
|
// 监听 URL 变化并同步 Tabs (仅在初始化和 location 变化时)
|
|
366
358
|
// 放在最后以确保可以使用 addTab 和 switchTab
|
|
367
359
|
useEffect(() => {
|
|
368
|
-
// 确保数据源存在
|
|
369
360
|
if (!dataSource || !('menus' in dataSource)) return;
|
|
370
361
|
const currentPath = window.location.pathname;
|
|
371
|
-
|
|
372
|
-
// 如果当前没有 activeKey 或者 activeKey 对应的 URL 与当前 URL 不一致
|
|
373
362
|
const activeTab = state.tabsList.find(tab => tab.id === state.activeKey);
|
|
374
|
-
|
|
375
|
-
// 如果当前已经在正确的 Tab 上,无需处理
|
|
376
363
|
if (activeTab && activeTab.url === currentPath) return;
|
|
377
|
-
|
|
378
|
-
// 在菜单中查找当前 URL 对应的菜单项
|
|
379
364
|
const flatMenus = flattenMenuData(dataSource.menus || []);
|
|
380
365
|
const targetMenu = flatMenus.find(item => getMenuRoute(item) === currentPath);
|
|
381
|
-
if (targetMenu)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// 检查是否已在 Tabs 中
|
|
388
|
-
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === getMenuRoute(targetMenu));
|
|
389
|
-
if (existingTab) {
|
|
390
|
-
// 如果已存在,切换到该 Tab
|
|
391
|
-
if (state.activeKey !== existingTab.id) {
|
|
392
|
-
switchTab(existingTab.id);
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
// 如果不存在,添加新 Tab
|
|
396
|
-
addTab(targetMenu);
|
|
397
|
-
}
|
|
366
|
+
if (!targetMenu || !canOpenAsTab(targetMenu)) return;
|
|
367
|
+
const existingTab = state.tabsList.find(tab => tab.menuItem?.code === targetMenu.code || tab.url === getMenuRoute(targetMenu));
|
|
368
|
+
if (existingTab) {
|
|
369
|
+
if (state.activeKey !== existingTab.id) switchTab(existingTab.id);
|
|
370
|
+
return;
|
|
398
371
|
}
|
|
372
|
+
addTab(targetMenu);
|
|
399
373
|
}, [dataSource, state.tabsList, state.activeKey, addTab, switchTab]);
|
|
400
374
|
return {
|
|
401
375
|
state,
|
|
376
|
+
isInitialized,
|
|
402
377
|
addTab,
|
|
403
378
|
canAddTab,
|
|
404
379
|
removeTab,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
|
1
|
+
import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { useTabsState } from "./hooks/useTabsState";
|
|
4
|
+
import { flattenMenuData } from "./utils";
|
|
4
5
|
import { TabItemComponent } from "./components/TabItem";
|
|
5
6
|
import { TabsHeader } from "./components/TabsHeader";
|
|
6
7
|
import { TabsContext } from "./components/TabsContext";
|
|
@@ -19,6 +20,7 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
19
20
|
}, ref) => {
|
|
20
21
|
const {
|
|
21
22
|
state,
|
|
23
|
+
isInitialized,
|
|
22
24
|
addTab,
|
|
23
25
|
canAddTab,
|
|
24
26
|
removeTab,
|
|
@@ -52,6 +54,32 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
52
54
|
onTabsChange?.(state.tabsList.length > 0);
|
|
53
55
|
}, [state.tabsList.length, onTabsChange]);
|
|
54
56
|
|
|
57
|
+
// 处理声明式固定标签页:config.fixed 中的 code 在初始化完成后自动添加,强制 closable=false
|
|
58
|
+
// 用 ref 持有 addTab,避免 addTab 引用变化引起无限循环
|
|
59
|
+
const addTabRef = useRef(addTab);
|
|
60
|
+
addTabRef.current = addTab;
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!isInitialized || !config.fixed || config.fixed.length === 0) return;
|
|
63
|
+
const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
|
|
64
|
+
|
|
65
|
+
// menus 为空说明异步数据尚未返回,等 dataSource 变化后重跑,避免用 code 创建占位 tab
|
|
66
|
+
if (sourceMenus.length === 0) return;
|
|
67
|
+
config.fixed.forEach(code => {
|
|
68
|
+
const matchedMenu = sourceMenus.find(item => item.code === code);
|
|
69
|
+
const menuItem = matchedMenu ? {
|
|
70
|
+
...matchedMenu,
|
|
71
|
+
closable: false
|
|
72
|
+
} : {
|
|
73
|
+
id: Date.now(),
|
|
74
|
+
code,
|
|
75
|
+
name: code,
|
|
76
|
+
url: `/${code}`,
|
|
77
|
+
closable: false
|
|
78
|
+
};
|
|
79
|
+
addTabRef.current(menuItem);
|
|
80
|
+
});
|
|
81
|
+
}, [isInitialized, config.fixed, dataSource]);
|
|
82
|
+
|
|
55
83
|
// 处理菜单点击 - 拦截原有的菜单点击逻辑
|
|
56
84
|
const handleMenuClick = useCallback(params => {
|
|
57
85
|
if (params.item) {
|
|
@@ -68,7 +96,14 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
68
96
|
name,
|
|
69
97
|
extra
|
|
70
98
|
} = params;
|
|
71
|
-
const
|
|
99
|
+
const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
|
|
100
|
+
const matchedMenu = sourceMenus.find(item => item.code === code);
|
|
101
|
+
const menuItem = matchedMenu ? {
|
|
102
|
+
...matchedMenu,
|
|
103
|
+
// 允许外部覆盖展示名称和附加数据,但保留菜单原始 closable/url 等配置
|
|
104
|
+
name: name || matchedMenu.name,
|
|
105
|
+
extra: extra ?? matchedMenu.extra
|
|
106
|
+
} : {
|
|
72
107
|
id: Date.now(),
|
|
73
108
|
code,
|
|
74
109
|
name,
|
|
@@ -83,7 +118,7 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
83
118
|
activeTabInfo: state.tabsList.find(tab => tab.id === state.activeKey),
|
|
84
119
|
activeComponent: state.activeComponent
|
|
85
120
|
})
|
|
86
|
-
}), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent]);
|
|
121
|
+
}), [addTab, removeTab, state.tabsList, state.activeKey, state.activeComponent, dataSource]);
|
|
87
122
|
const handleTabChange = useCallback(activeKey => {
|
|
88
123
|
switchTab(activeKey);
|
|
89
124
|
}, [switchTab]);
|
|
@@ -164,8 +199,9 @@ const TabsManager = /*#__PURE__*/forwardRef(({
|
|
|
164
199
|
}) : null;
|
|
165
200
|
useImperativeHandle(ref, () => ({
|
|
166
201
|
handleMenuClick,
|
|
167
|
-
canAddTab
|
|
168
|
-
|
|
202
|
+
canAddTab,
|
|
203
|
+
getTabInfo: tabsInstance.getTabInfo
|
|
204
|
+
}), [handleMenuClick, canAddTab, tabsInstance]);
|
|
169
205
|
return /*#__PURE__*/_jsxs(TabsContext.Provider, {
|
|
170
206
|
value: tabsInstance,
|
|
171
207
|
children: [tabsBarContainer ? /*#__PURE__*/createPortal(tabsHeaderNode, tabsBarContainer) : tabsHeaderNode, /*#__PURE__*/_jsx("div", {
|
|
@@ -51,6 +51,8 @@ export interface UseTabsStateOptions {
|
|
|
51
51
|
export interface UseTabsStateReturn {
|
|
52
52
|
/** 当前状态 */
|
|
53
53
|
state: TabsState;
|
|
54
|
+
/** 缓存是否已恢复完成 */
|
|
55
|
+
isInitialized: boolean;
|
|
54
56
|
/** 添加标签页 */
|
|
55
57
|
addTab: (menuItem: MenusType, options?: AddTabOptions) => void;
|
|
56
58
|
/** 检查是否可以添加标签页 */
|
|
@@ -50,6 +50,6 @@ export declare const isLeafMenuItem: (menuItem: MenusType) => boolean;
|
|
|
50
50
|
export declare const isRootMenuItem: (menuItem: MenusType) => boolean;
|
|
51
51
|
/**
|
|
52
52
|
* 判断菜单项是否可以在 Tabs 模式下打开
|
|
53
|
-
*
|
|
53
|
+
* 规则:只有叶子节点(无 children)才可打开
|
|
54
54
|
*/
|
|
55
55
|
export declare const canOpenAsTab: (menuItem: MenusType) => boolean;
|
|
@@ -11,7 +11,7 @@ export const getMenuRoute = menuItem => {
|
|
|
11
11
|
*/
|
|
12
12
|
export const createTabFromMenu = (menuItem, index = 0) => {
|
|
13
13
|
const route = getMenuRoute(menuItem);
|
|
14
|
-
|
|
14
|
+
const result = {
|
|
15
15
|
id: String(menuItem.id || menuItem.code || menuItem.url || index),
|
|
16
16
|
code: menuItem.code,
|
|
17
17
|
name: menuItem.name,
|
|
@@ -21,6 +21,7 @@ export const createTabFromMenu = (menuItem, index = 0) => {
|
|
|
21
21
|
menuItem,
|
|
22
22
|
icon: menuItem.icon || menuItem.imgUrl
|
|
23
23
|
};
|
|
24
|
+
return result;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -134,13 +135,10 @@ export const isRootMenuItem = menuItem => {
|
|
|
134
135
|
|
|
135
136
|
/**
|
|
136
137
|
* 判断菜单项是否可以在 Tabs 模式下打开
|
|
137
|
-
*
|
|
138
|
+
* 规则:只有叶子节点(无 children)才可打开
|
|
138
139
|
*/
|
|
139
140
|
export const canOpenAsTab = menuItem => {
|
|
140
141
|
if (!menuItem) return false;
|
|
141
142
|
if (!(menuItem.url || menuItem.redirectUrl)) return false;
|
|
142
|
-
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
return isRootMenuItem(menuItem);
|
|
143
|
+
return isLeafMenuItem(menuItem);
|
|
146
144
|
};
|
package/es/ProLayout/index.js
CHANGED
|
@@ -82,9 +82,22 @@ const ProLayout = props => {
|
|
|
82
82
|
menus: menuData
|
|
83
83
|
});
|
|
84
84
|
}, [dataSource]);
|
|
85
|
-
const menuDataSource = useMemo(() =>
|
|
86
|
-
menus
|
|
87
|
-
|
|
85
|
+
const menuDataSource = useMemo(() => {
|
|
86
|
+
// menus 已通过 useDeepCompareEffect 填充,直接使用
|
|
87
|
+
if (Array.isArray(menus) && menus.length > 0) {
|
|
88
|
+
return {
|
|
89
|
+
menus
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// menus state 尚未填充(初始为 []),直接用 dataSource 原始 menus,
|
|
93
|
+
// 确保 tabsInstance 第一次创建时就能查到完整菜单数据(含 closable 等属性)
|
|
94
|
+
if (!Array.isArray(dataSource) && Array.isArray(dataSource?.menus)) {
|
|
95
|
+
return dataSource;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
menus: menus || []
|
|
99
|
+
};
|
|
100
|
+
}, [menus, dataSource]);
|
|
88
101
|
const enhancedTabsConfig = useMemo(() => {
|
|
89
102
|
if (!isTabsLayout || !tabs) {
|
|
90
103
|
return tabs;
|
|
@@ -138,9 +151,9 @@ const ProLayout = props => {
|
|
|
138
151
|
return true;
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
//
|
|
154
|
+
// 只有叶子节点(无 children)可入签;有 children 的节点不激活
|
|
142
155
|
if (!canOpenAsTab(params.item)) {
|
|
143
|
-
return
|
|
156
|
+
return false;
|
|
144
157
|
}
|
|
145
158
|
|
|
146
159
|
// 可入签节点:检查是否可以添加标签页(检查 max 限制、_blank 等)
|
|
@@ -429,6 +429,11 @@ export interface TabsConfig {
|
|
|
429
429
|
tab: TabItem;
|
|
430
430
|
tabs: TabItem[];
|
|
431
431
|
}) => void;
|
|
432
|
+
/**
|
|
433
|
+
* @description 声明式固定标签页,传入菜单 code 数组,这些 tab 会在初始化时自动添加且不可关闭
|
|
434
|
+
* @example fixed: ['Workspace', 'PolicyInput']
|
|
435
|
+
*/
|
|
436
|
+
fixed?: string[];
|
|
432
437
|
}
|
|
433
438
|
export type ProLayoutMode = 'normal' | 'tabs';
|
|
434
439
|
export interface ProLayoutTabsProps extends ProLayoutBaseProps {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ProIcon from "../../../ProIcon";
|
|
3
|
+
import editSvg from "../../../assets/edit.svg";
|
|
4
|
+
import styles from "./index.less";
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
const EditIcon = ({
|
|
7
|
+
onClick
|
|
8
|
+
}) => {
|
|
9
|
+
return /*#__PURE__*/_jsx("span", {
|
|
10
|
+
className: styles['editable-cell-edit-icon'],
|
|
11
|
+
onClick: onClick,
|
|
12
|
+
children: /*#__PURE__*/_jsx(ProIcon, {
|
|
13
|
+
src: editSvg,
|
|
14
|
+
size: "20px",
|
|
15
|
+
buttonIcon: false
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export default EditIcon;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import _isEqual from "lodash/isEqual";
|
|
2
|
+
import React, { useState, useRef } from 'react';
|
|
3
|
+
import { Form } from 'antd';
|
|
4
|
+
import * as componentMap from "../../../ProForm/components";
|
|
5
|
+
import EditIcon from "./EditIcon";
|
|
6
|
+
import styles from "./index.less";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const EditableCell = ({
|
|
9
|
+
value,
|
|
10
|
+
record,
|
|
11
|
+
index,
|
|
12
|
+
dataIndex,
|
|
13
|
+
editConfig,
|
|
14
|
+
displayContent
|
|
15
|
+
}) => {
|
|
16
|
+
const [editing, setEditing] = useState(false);
|
|
17
|
+
const [form] = Form.useForm();
|
|
18
|
+
const snapshotRef = useRef(value);
|
|
19
|
+
const fieldName = Array.isArray(dataIndex) ? dataIndex.join('_') : dataIndex;
|
|
20
|
+
const {
|
|
21
|
+
type = 'Input',
|
|
22
|
+
fieldProps = {},
|
|
23
|
+
rules,
|
|
24
|
+
required,
|
|
25
|
+
valueType
|
|
26
|
+
} = editConfig;
|
|
27
|
+
const {
|
|
28
|
+
onBlur: onBlurCallback,
|
|
29
|
+
...restFieldProps
|
|
30
|
+
} = fieldProps;
|
|
31
|
+
const Component = componentMap[type] ?? componentMap.Input;
|
|
32
|
+
const handleEditIconClick = () => {
|
|
33
|
+
snapshotRef.current = value;
|
|
34
|
+
form.setFieldsValue({
|
|
35
|
+
[fieldName]: value
|
|
36
|
+
});
|
|
37
|
+
setEditing(true);
|
|
38
|
+
};
|
|
39
|
+
const handleBlur = async () => {
|
|
40
|
+
try {
|
|
41
|
+
const values = await form.validateFields();
|
|
42
|
+
const newValue = values[fieldName];
|
|
43
|
+
if (_isEqual(newValue, snapshotRef.current)) {
|
|
44
|
+
setEditing(false);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!onBlurCallback) {
|
|
48
|
+
setEditing(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const result = onBlurCallback(newValue, record, index);
|
|
52
|
+
if (result instanceof Promise) {
|
|
53
|
+
await result;
|
|
54
|
+
}
|
|
55
|
+
setEditing(false);
|
|
56
|
+
} catch {
|
|
57
|
+
// Validation failed - stay in edit mode
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
if (!editing) {
|
|
61
|
+
return /*#__PURE__*/_jsxs("span", {
|
|
62
|
+
className: styles['editable-cell-wrapper'],
|
|
63
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
64
|
+
children: displayContent ?? '-'
|
|
65
|
+
}), /*#__PURE__*/_jsx(EditIcon, {
|
|
66
|
+
onClick: handleEditIconClick
|
|
67
|
+
})]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return /*#__PURE__*/_jsx(Form, {
|
|
71
|
+
form: form,
|
|
72
|
+
component: false,
|
|
73
|
+
children: /*#__PURE__*/_jsx(Form.Item, {
|
|
74
|
+
name: fieldName,
|
|
75
|
+
rules: rules,
|
|
76
|
+
required: required,
|
|
77
|
+
noStyle: true,
|
|
78
|
+
children: /*#__PURE__*/_jsx(Component, {
|
|
79
|
+
...restFieldProps,
|
|
80
|
+
valueType: valueType,
|
|
81
|
+
autoFocus: true,
|
|
82
|
+
onBlur: handleBlur
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
export default EditableCell;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.editable-cell-wrapper {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
width: 100%;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
gap: 4px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.editable-cell-edit-icon {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
visibility: hidden;
|
|
14
|
+
color: rgba(0, 0, 0, 0.65);
|
|
15
|
+
flex-shrink: 0;
|
|
16
|
+
|
|
17
|
+
svg path {
|
|
18
|
+
fill: currentColor !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
color: var(--zaui-brand, #006aff);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
td:hover .editable-cell-edit-icon {
|
|
27
|
+
visibility: visible;
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { EditTableCellConfig } from '../../propsType';
|
|
3
|
+
export interface EditableCellProps {
|
|
4
|
+
value: any;
|
|
5
|
+
record: any;
|
|
6
|
+
index: number;
|
|
7
|
+
dataIndex: string | string[];
|
|
8
|
+
editConfig: EditTableCellConfig;
|
|
9
|
+
displayContent: ReactNode;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -16,6 +16,7 @@ import { Space } from 'antd';
|
|
|
16
16
|
import { tools } from '@zat-design/utils';
|
|
17
17
|
import TooltipTitle from "../TooltipTitle";
|
|
18
18
|
import RenderColumn from "../RenderColumn";
|
|
19
|
+
import EditableCell from "../EditableCell";
|
|
19
20
|
import { getDecimalDigits, getOriginalValue } from "../../utils";
|
|
20
21
|
import { isEmpty } from "../../../utils";
|
|
21
22
|
import getEnumLabel from "../../../ProEnum/utils/getEnumLabel";
|
|
@@ -24,7 +25,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
24
25
|
const isReactElement = element => /*#__PURE__*/isValidElement(element);
|
|
25
26
|
|
|
26
27
|
// 需要从 otherProps 中排除的属性(不应传递给 RenderColumn)
|
|
27
|
-
const EXCLUDED_PROPS = ['key', 'render', 'children', 'onCell', 'onHeaderCell'];
|
|
28
|
+
const EXCLUDED_PROPS = ['key', 'render', 'children', 'onCell', 'onHeaderCell', 'editConfig'];
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* 获取安全的 props(排除 key 等不应通过 spread 传递的属性)
|
|
@@ -206,7 +207,7 @@ export const formatColumn = ({
|
|
|
206
207
|
};
|
|
207
208
|
|
|
208
209
|
// 自定义 render 处理
|
|
209
|
-
if (column?.render) {
|
|
210
|
+
if (column?.render && !column.editConfig) {
|
|
210
211
|
const originalRender = column.render;
|
|
211
212
|
column.render = (value, record, index) => {
|
|
212
213
|
const isFn = _isFunction(originalRender);
|
|
@@ -695,6 +696,29 @@ export const formatColumn = ({
|
|
|
695
696
|
});
|
|
696
697
|
};
|
|
697
698
|
}
|
|
699
|
+
if (column.editConfig) {
|
|
700
|
+
const editConfig = column.editConfig;
|
|
701
|
+
const titleStr = typeof title === 'string' ? title : String(dataIndex ?? '');
|
|
702
|
+
const mergedEditConfig = {
|
|
703
|
+
...editConfig,
|
|
704
|
+
fieldProps: {
|
|
705
|
+
placeholder: titleStr,
|
|
706
|
+
...editConfig.fieldProps
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const prevRender = column.render;
|
|
710
|
+
column.render = (value, record, index) => {
|
|
711
|
+
const displayContent = prevRender ? prevRender(value, record, index) : value ?? '-';
|
|
712
|
+
return /*#__PURE__*/_jsx(EditableCell, {
|
|
713
|
+
value: value,
|
|
714
|
+
record: record,
|
|
715
|
+
index: index,
|
|
716
|
+
dataIndex: dataIndex,
|
|
717
|
+
editConfig: mergedEditConfig,
|
|
718
|
+
displayContent: displayContent
|
|
719
|
+
});
|
|
720
|
+
};
|
|
721
|
+
}
|
|
698
722
|
};
|
|
699
723
|
|
|
700
724
|
/**
|
|
@@ -32,4 +32,5 @@ export { default as RenderFooter } from "./RenderFooter";
|
|
|
32
32
|
* 表格标签页组件
|
|
33
33
|
* @description 实现表格顶部的标签页切换功能,用于数据分类展示
|
|
34
34
|
*/
|
|
35
|
-
export { default as RenderTabs } from "./RenderTabs";
|
|
35
|
+
export { default as RenderTabs } from "./RenderTabs";
|
|
36
|
+
export { default as EditableCell } from "./EditableCell";
|
|
@@ -4,6 +4,20 @@ import { NamePath } from 'antd/es/form/interface';
|
|
|
4
4
|
import { FormInstance } from 'antd/es/form/Form';
|
|
5
5
|
import { ColumnType, GetRowKey, RowSelectionType } from 'antd/es/table/interface';
|
|
6
6
|
import type { TabsProps } from 'antd/es/tabs';
|
|
7
|
+
import type { ProFormColumnType } from '../ProForm/components/render/propsType';
|
|
8
|
+
/**
|
|
9
|
+
* 单元格行内编辑时 onBlur 的回调函数类型
|
|
10
|
+
*/
|
|
11
|
+
export type TableOnBlurFn = (value: any, record: any, index: number) => void | Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* 单元格行内编辑配置
|
|
14
|
+
*/
|
|
15
|
+
export type EditTableCellConfig<Values = any> = Omit<ProFormColumnType<Values>, 'name' | 'label' | 'names' | 'fieldProps'> & {
|
|
16
|
+
fieldProps?: {
|
|
17
|
+
onBlur?: TableOnBlurFn;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
7
21
|
/**
|
|
8
22
|
* 合计栏的每列配置参数
|
|
9
23
|
*/
|
|
@@ -216,6 +230,12 @@ export interface ProTableColumnType extends Omit<ColumnType<any>, 'dataIndex'> {
|
|
|
216
230
|
* @default undefined
|
|
217
231
|
*/
|
|
218
232
|
scroll?: any;
|
|
233
|
+
/**
|
|
234
|
+
* 行内编辑配置
|
|
235
|
+
* @description 配置后单元格支持点击图标进入编辑态
|
|
236
|
+
* @default undefined
|
|
237
|
+
*/
|
|
238
|
+
editConfig?: EditTableCellConfig;
|
|
219
239
|
}
|
|
220
240
|
/**
|
|
221
241
|
* 从数组创建树形结构的选项
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zat-design/sisyphus-react",
|
|
3
|
-
"version": "4.3.0
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"es",
|
|
@@ -94,7 +94,6 @@
|
|
|
94
94
|
"react-resizable": {
|
|
95
95
|
"react-draggable": "<4.5.0"
|
|
96
96
|
},
|
|
97
|
-
"@ant-design/icons": "^6.1.0",
|
|
98
97
|
"@babel/core": "^7.26.0",
|
|
99
98
|
"react-docgen-typescript-dumi-tmp": {
|
|
100
99
|
"typescript": "^4.9.5"
|
|
@@ -104,7 +103,6 @@
|
|
|
104
103
|
},
|
|
105
104
|
"resolutions": {
|
|
106
105
|
"react-resizable/react-draggable": "<4.5.0",
|
|
107
|
-
"@ant-design/icons": "^6.1.0",
|
|
108
106
|
"@babel/core": "^7.26.0",
|
|
109
107
|
"react-docgen-typescript-dumi-tmp/typescript": "^4.9.5",
|
|
110
108
|
"esbuild": "0.21.4",
|