@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.
@@ -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
- status?: "" | "success" | "warning" | "error" | "validating";
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?: "" | "success" | "warning" | "error" | "validating";
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, "valueType" | "precision" | "format" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "clearNotShow">;
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
- // 如果 forceNew = false(默认),检查是否已存在相同的标签页
160
- if (!forceNew) {
161
- const menuRoute = getMenuRoute(menuItem);
162
- const existingTab = prevState.tabsList.find(tab => tab.menuItem?.code === menuItem.code || tab.url === menuRoute);
163
- if (existingTab) {
164
- // 如果已存在,直接切换到该标签页
165
- const newState = {
166
- ...prevState,
167
- activeKey: existingTab.id,
168
- activeTabInfo: existingTab,
169
- activeComponent: existingTab.menuItem?.code || existingTab.id
170
- };
171
- return newState;
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
- const newState = {
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
- // 如果关闭后没有标签页了,直接调用 onTabChange
323
- if (newTabs.length === 0 && finalConfig.onTabChange) {
313
+ // 直接调用 onTabChange(useEffect 因 shouldSkipSaveRef 会跳过)
314
+ if (finalConfig.onTabChange) {
315
+ const firstTab = newTabs.length > 0 ? newTabs[0] : undefined;
324
316
  setTimeout(() => {
325
- finalConfig.onTabChange('', undefined, []);
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
- if (!canOpenAsTab(targetMenu)) {
384
- return;
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 menuItem = {
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
- }), [handleMenuClick, canAddTab]);
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
- * 规则:叶子节点可打开;有 children 时仅根节点可打开
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
- return {
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
- * 规则:叶子节点可打开;有 children 时仅根节点可打开
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
- if (isLeafMenuItem(menuItem)) {
143
- return true;
144
- }
145
- return isRootMenuItem(menuItem);
143
+ return isLeafMenuItem(menuItem);
146
144
  };
@@ -82,9 +82,22 @@ const ProLayout = props => {
82
82
  menus: menuData
83
83
  });
84
84
  }, [dataSource]);
85
- const menuDataSource = useMemo(() => Array.isArray(menus) ? {
86
- menus
87
- } : menus, [menus]);
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
- // 根节点可入签;中间层级(有 children)不激活;叶子入签
154
+ // 只有叶子节点(无 children)可入签;有 children 的节点不激活
142
155
  if (!canOpenAsTab(params.item)) {
143
- return true;
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,6 @@
1
+ import React from 'react';
2
+ interface EditIconProps {
3
+ onClick: () => void;
4
+ }
5
+ declare const EditIcon: React.FC<EditIconProps>;
6
+ export default EditIcon;
@@ -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,4 @@
1
+ import React from 'react';
2
+ import type { EditableCellProps } from './propsType';
3
+ declare const EditableCell: React.FC<EditableCellProps>;
4
+ export default EditableCell;
@@ -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
  /**
@@ -28,3 +28,4 @@ export { default as RenderFooter } from './RenderFooter';
28
28
  * @description 实现表格顶部的标签页切换功能,用于数据分类展示
29
29
  */
30
30
  export { default as RenderTabs } from './RenderTabs';
31
+ export { default as EditableCell } from './EditableCell';
@@ -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-beta.8",
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",