@zat-design/sisyphus-react 4.5.8 → 4.5.9

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.
@@ -129,10 +129,19 @@ export const ProConfigProvider = props => {
129
129
  const lang = state?.locale ?? cacheLang ?? props?.locale ?? 'zh-CN';
130
130
  useEffect(() => {
131
131
  const raw = localStorage.getItem('localConfig');
132
- const localConfig = JSON.parse(raw || '{}');
132
+ let localConfig;
133
+ try {
134
+ localConfig = JSON.parse(raw || '{}');
135
+ } catch {
136
+ localStorage.removeItem('localConfig');
137
+ if (process.env.NODE_ENV !== 'production') {
138
+ console.warn('ProConfigProvider: invalid localConfig cache has been removed.');
139
+ }
140
+ return;
141
+ }
133
142
  dispatch({
134
143
  type: 'set',
135
- payload: merge(initialState, localConfig)
144
+ payload: merge({}, initialState, localConfig)
136
145
  });
137
146
  }, []);
138
147
  useEffect(() => {
@@ -147,7 +156,7 @@ export const ProConfigProvider = props => {
147
156
  return /*#__PURE__*/_jsx(ThemeProvider, {
148
157
  children: /*#__PURE__*/_jsx(ProConfigContext.Provider, {
149
158
  value: {
150
- state: merge(state, props.value),
159
+ state: merge({}, state, props.value),
151
160
  dispatch
152
161
  },
153
162
  children: /*#__PURE__*/_jsx(ThemeAwareConfigProvider, {
@@ -35,7 +35,7 @@ const ProDownload = props => {
35
35
  ...headers,
36
36
  ...defaultProps.headers
37
37
  };
38
- const handDownload = async () => {
38
+ const handDownload = async (_params = params) => {
39
39
  if (source === 'url') {
40
40
  try {
41
41
  fetch(url || action).then(response => {
@@ -58,13 +58,8 @@ const ProDownload = props => {
58
58
  setState({
59
59
  loading: true
60
60
  });
61
- let _params = params;
62
- if (_isFunction(params)) {
63
- _params = params.constructor.name === 'AsyncFunction' ? await params() : params();
64
- }
65
-
66
- // 发起请求
67
61
  try {
62
+ // 发起请求
68
63
  await DownloadRequest({
69
64
  url: url || action,
70
65
  method,
@@ -75,11 +70,6 @@ const ProDownload = props => {
75
70
  onFinish,
76
71
  onError: onError || onErrorConfig
77
72
  });
78
- } catch (error) {
79
- setState({
80
- loading: false
81
- });
82
- return Promise.reject();
83
73
  } finally {
84
74
  setState({
85
75
  loading: false
@@ -92,16 +82,24 @@ const ProDownload = props => {
92
82
  return /*#__PURE__*/_jsx(Button, {
93
83
  className: cls,
94
84
  onClick: async () => {
95
- if (_isBoolean(beforeDownload) && !beforeDownload) {
96
- return false;
97
- }
98
- if (beforeDownload) {
99
- const res = beforeDownload instanceof (async () => {}).constructor ? await beforeDownload() : beforeDownload();
100
- if (res === false) {
101
- return;
85
+ let resolvedParams = params;
86
+ try {
87
+ if (_isBoolean(beforeDownload) && !beforeDownload) {
88
+ return false;
89
+ }
90
+ if (beforeDownload) {
91
+ const res = await Promise.resolve(beforeDownload());
92
+ if (res === false) {
93
+ return;
94
+ }
102
95
  }
96
+ if (source !== 'url' && _isFunction(params)) {
97
+ resolvedParams = await Promise.resolve(params());
98
+ }
99
+ } catch {
100
+ return false;
103
101
  }
104
- handDownload();
102
+ return handDownload(resolvedParams);
105
103
  },
106
104
  ...defaultProps,
107
105
  loading: loading,
@@ -105,7 +105,11 @@ export const DownloadRequest = async ({
105
105
  ...headers
106
106
  };
107
107
  }
108
- const res = await fetch(url, config).then(async response => {
108
+ const response = await fetch(url, config);
109
+ const {
110
+ res,
111
+ responseData
112
+ } = await (async () => {
109
113
  try {
110
114
  const data = await response.clone().json();
111
115
  if (!data) {
@@ -120,16 +124,25 @@ export const DownloadRequest = async ({
120
124
  return Promise.reject(errorMsg);
121
125
  }
122
126
  if (_isFunction(transformResponse)) {
123
- return Promise.resolve(transformResponse(data));
127
+ return Promise.resolve(transformResponse(data)).then(transformedData => ({
128
+ res: transformedData,
129
+ responseData: data
130
+ }));
124
131
  }
125
- Promise.resolve(data);
132
+ return Promise.resolve({
133
+ res: response,
134
+ responseData: data
135
+ });
126
136
  } catch (error) {
127
137
  if (response.ok) {
128
- return Promise.resolve(response);
138
+ return Promise.resolve({
139
+ res: response,
140
+ responseData: null
141
+ });
129
142
  }
130
143
  }
131
144
  return Promise.reject(`${locale.ProDownload.errorMessage}`);
132
- });
145
+ })();
133
146
  let fileBlob;
134
147
  let data = null;
135
148
  let contentFileName = '';
@@ -137,19 +150,19 @@ export const DownloadRequest = async ({
137
150
  data = res;
138
151
  } else {
139
152
  // 优先获取Content-Disposition中filename
140
- contentFileName = getFilenameFromContentDisposition(res.headers.get('Content-Disposition'));
141
- data = _get(res, 'data');
153
+ contentFileName = getFilenameFromContentDisposition(response.headers.get('Content-Disposition'));
154
+ data = _get(responseData, 'data');
142
155
  }
143
156
  if (data) {
144
157
  fileBlob = convertBase64ToBlob(data);
145
158
  } else {
146
- fileBlob = await res.blob();
159
+ fileBlob = await response.blob();
147
160
  }
148
161
  // 完成回调
149
- onFinish?.(res, params);
162
+ onFinish?.(_isFunction(transformResponse) ? res : response, params);
150
163
  BlobMakeFile({
151
164
  blob: fileBlob,
152
165
  fileName: fileName || contentFileName
153
166
  });
154
- return Promise.resolve(res);
167
+ return Promise.resolve(_isFunction(transformResponse) ? res : response);
155
168
  };
@@ -26,7 +26,7 @@ import { getDefaultProps } from "../../utils/getDefaultProps";
26
26
  import ListChangedWrapper from "./ListChangedWrapper";
27
27
  import useShouldUpdateForTable from "../../utils/useShouldUpdateForTable";
28
28
  import { OMIT_FORM_ITEM_AND_DOM_KEYS, arePropsEqual, hasRowFunctionDependency, mergeOnFieldChangeRow } from "./tools";
29
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
29
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
30
30
  const RenderField = ({
31
31
  text: value,
32
32
  record,
@@ -90,6 +90,8 @@ const RenderField = ({
90
90
 
91
91
  // type类型 首字母转大写
92
92
  type = type?.replace?.(type[0], type[0].toUpperCase()) || 'Input';
93
+ const fallbackType = type;
94
+ const hasCustomComponent = 'component' in column || 'editRender' in column;
93
95
 
94
96
  // 自定义组件时 防止默认值影像后续判断
95
97
  if ('component' in column) {
@@ -176,7 +178,7 @@ const RenderField = ({
176
178
 
177
179
  // component 处理 - 优先使用 hook 返回的值
178
180
  if (_isFunction(lastComponent)) {
179
- lastComponent = dynamicProps.component ?? lastComponent(rowData, reactiveParams);
181
+ lastComponent = dynamicProps.componentResolved ? dynamicProps.component : lastComponent(rowData, reactiveParams);
180
182
  }
181
183
 
182
184
  // 更新 lastEditRender 为处理后的 lastComponent
@@ -198,6 +200,23 @@ const RenderField = ({
198
200
 
199
201
  // 多态:可能是内置组件类型、自定义 component,或已渲染的 ReactElement(lastEditRender),保留显式 any
200
202
  let TargetComponent;
203
+ const fallbackTargetComponent = typeof fallbackType === 'string' ? componentMap[fallbackType] : undefined;
204
+ const invalidComponentWarnedRef = useRef(false);
205
+ const resolveEditTargetComponent = candidate => {
206
+ if ( /*#__PURE__*/React.isValidElement(candidate)) {
207
+ invalidComponentWarnedRef.current = false;
208
+ return candidate;
209
+ }
210
+ if (hasCustomComponent && !invalidComponentWarnedRef.current && process.env.NODE_ENV !== 'production') {
211
+ const columnName = dataIndex ?? column?.name ?? title ?? originTitle ?? 'unknown';
212
+ console.warn('[ProEditTable] component 必须返回合法的 ReactElement,已回退到内置组件。', {
213
+ column: Array.isArray(columnName) ? columnName.join('.') : String(columnName),
214
+ received: candidate
215
+ });
216
+ invalidComponentWarnedRef.current = true;
217
+ }
218
+ return fallbackTargetComponent;
219
+ };
201
220
 
202
221
  // 将Hooks调用移到组件顶层
203
222
  const proConfig = useProConfig();
@@ -320,20 +339,9 @@ const RenderField = ({
320
339
  ...valueTypeTransform()
321
340
  }, form, names, namesStr, type);
322
341
  }, [names, baseName, index, dataIndex, lastFormItemProps, type]);
323
- if (!lastEditRender && typeof type === 'string') {
324
- // componentMap 为内置组件命名空间,按字符串 type 动态取用,保留显式 any 索引
325
- TargetComponent = componentMap[type] ?? /*#__PURE__*/_jsx(_Fragment, {});
326
- }
327
- if (isEditable && isEditing) {
328
- // lastEditRender 已经是处理后的值(通过 dynamicProps.component 或直接计算)
329
- // 如果原本是函数,此时 lastEditRender 已经是执行后的 ReactNode
330
- // 如果原本是 ReactElement,lastEditRender 就是 ReactElement
331
- if ( /*#__PURE__*/React.isValidElement(lastEditRender)) {
332
- TargetComponent = lastEditRender;
333
- } else if (lastEditRender) {
334
- // 其他情况(可能是字符串或其他类型的 ReactNode)
335
- TargetComponent = lastEditRender;
336
- }
342
+ TargetComponent = fallbackTargetComponent;
343
+ if (isEditable && isEditing && hasCustomComponent) {
344
+ TargetComponent = resolveEditTargetComponent(lastEditRender);
337
345
  }
338
346
 
339
347
  // 查看模式
@@ -729,10 +737,7 @@ const RenderField = ({
729
737
  onChange: handleChange,
730
738
  onBlur: handleBlur
731
739
  };
732
- let latestTargetComponent = TargetComponent;
733
- if (!canReuseCurrentState && !latestComponent && typeof type === 'string') {
734
- latestTargetComponent = componentMap[type] ?? /*#__PURE__*/_jsx(_Fragment, {});
735
- }
740
+ let latestTargetComponent = canReuseCurrentState ? TargetComponent : fallbackTargetComponent;
736
741
 
737
742
  // 然后处理自定义 component
738
743
  const latestIsView = !latestIsEditable || record?.['is-view'] || config.isView || virtualKey && !isEditing || getDisabled({
@@ -744,12 +749,8 @@ const RenderField = ({
744
749
  params: latestRowParams,
745
750
  rowDisabled: rowDisabled || 'empty'
746
751
  });
747
- if (!canReuseCurrentState && latestIsEditable && isEditing) {
748
- if ( /*#__PURE__*/React.isValidElement(latestComponent)) {
749
- latestTargetComponent = latestComponent;
750
- } else if (latestComponent) {
751
- latestTargetComponent = latestComponent;
752
- }
752
+ if (!canReuseCurrentState && latestIsEditable && isEditing && hasCustomComponent) {
753
+ latestTargetComponent = resolveEditTargetComponent(latestComponent);
753
754
  }
754
755
 
755
756
  // 查看模式
@@ -936,7 +937,7 @@ const RenderField = ({
936
937
  }) : /*#__PURE__*/_jsx(Container, {
937
938
  viewEmpty: viewEmpty
938
939
  });
939
- if (type === 'FormList') {
940
+ if (type === 'FormList' && !hasCustomComponent && TargetComponent) {
940
941
  FormItem = /*#__PURE__*/_jsx(Form.List, {
941
942
  name: formNamePath ? cellName.slice(formNamePath?.length - 1) : cellName,
942
943
  ..._omit(activeFinalFormItemProps, ['render', 'key', 'width', 'hiddenNames', 'name']),
@@ -1,5 +1,5 @@
1
1
  import { AffixProps, ModalFuncProps } from 'antd';
2
- import { Key, ReactNode } from 'react';
2
+ import { Key, ReactElement, ReactNode } from 'react';
3
3
  import { DragEndEvent } from '@dnd-kit/core';
4
4
  import type { ButtonProps } from 'antd/es/button';
5
5
  import type { TooltipProps } from 'antd/es/tooltip';
@@ -134,7 +134,7 @@ export type viewRenderFn<T = any> = (text?: any, record?: T, options?: OptionsPr
134
134
  * 组件渲染函数类型(与 ReactiveFunction 保持一致)
135
135
  * @template T 记录类型
136
136
  */
137
- export type ComponentRenderFn<T = any> = ReactiveFunction<T, string | number | ReactNode | void>;
137
+ export type ComponentRenderFn<T = any> = ReactiveFunction<T, ReactElement>;
138
138
  /**
139
139
  * 表格列属性接口
140
140
  * @template Values 值类型
@@ -157,8 +157,8 @@ export interface ProColumnsProps<Values = any, T = any> extends Omit<FormItemPro
157
157
  tooltip?: string | ({
158
158
  icon?: string | ReactNode;
159
159
  } & TooltipProps);
160
- /** 组件 */
161
- component?: string | number | ReactNode | ComponentRenderFn<T>;
160
+ /** 自定义组件,或返回合法 ReactElement 的响应式渲染函数 */
161
+ component?: ReactElement | ComponentRenderFn<T>;
162
162
  /** 视图渲染 */
163
163
  viewRender?: string | number | ReactNode | viewRenderFn<T>;
164
164
  /** 隐藏的字段名 */
@@ -11,6 +11,8 @@ interface Result {
11
11
  desensitization: any;
12
12
  valueType: any;
13
13
  component: any;
14
+ /** 动态 component 是否已经执行过,用于区分“返回 undefined”和“尚未计算”。 */
15
+ componentResolved: boolean;
14
16
  /** 当前缓存结果对应的行浅快照,供 RenderField 判断是否可安全复用。 */
15
17
  rowSnapshot: Record<string, any> | undefined;
16
18
  }
@@ -19,6 +19,7 @@ const useShouldUpdateForTable = props => {
19
19
  const desensitizationRef = useRef(undefined);
20
20
  const valueTypeRef = useRef(undefined);
21
21
  const componentRef = useRef(undefined);
22
+ const componentResolvedRef = useRef(false);
22
23
  const resolvedRowSnapshotRef = useRef(undefined);
23
24
  const [, reRender] = useState({});
24
25
  const debouncedUpdateRef = useRef(null);
@@ -101,6 +102,7 @@ const useShouldUpdateForTable = props => {
101
102
  const componentOrEditRender = column.component || column.editRender;
102
103
  if (_isFunction(componentOrEditRender)) {
103
104
  const newComponent = componentOrEditRender(values, reactiveParams);
105
+ componentResolvedRef.current = true;
104
106
  // 对于 ReactNode 类型的返回值,使用深度比较
105
107
  if (!_isEqualWith(componentRef.current, newComponent, customEqualForFunction)) {
106
108
  componentRef.current = newComponent;
@@ -170,7 +172,8 @@ const useShouldUpdateForTable = props => {
170
172
  fieldProps: fieldPropsRef.current ?? column.fieldProps,
171
173
  desensitization: desensitizationRef.current ?? column.desensitization,
172
174
  valueType: valueTypeRef.current ?? column.valueType,
173
- component: componentRef.current ?? (column.component || column.editRender),
175
+ component: componentResolvedRef.current ? componentRef.current : column.component || column.editRender,
176
+ componentResolved: componentResolvedRef.current,
174
177
  rowSnapshot: resolvedRowSnapshotRef.current
175
178
  };
176
179
  };
@@ -150,16 +150,15 @@ const ProEnum = props => {
150
150
  list = sourceList;
151
151
  }
152
152
  if ( /*#__PURE__*/React.isValidElement(component)) {
153
- const Component = component;
154
- return /*#__PURE__*/_jsx(Component, {
153
+ return /*#__PURE__*/React.cloneElement(component, {
155
154
  ...enumProps,
156
155
  dataSource: list,
157
156
  fieldNames: {
158
157
  label,
159
158
  value: fieldValue
160
159
  },
161
- value: value,
162
- onChange: onChange
160
+ value,
161
+ onChange
163
162
  });
164
163
  }
165
164
  switch (type) {
@@ -244,7 +244,7 @@ const InputNumber = props => {
244
244
  value = _min;
245
245
  }
246
246
  value = parser(String(value), props.precision ?? valueProps?.precision);
247
- rest?.onChange(value);
247
+ rest?.onChange?.(value);
248
248
  const limit = {
249
249
  min: Number(_min),
250
250
  max: Number(_max)
@@ -81,9 +81,11 @@ export function useRequestList(service, options, useRequestOptions) {
81
81
  });
82
82
  }, [onPageChange]);
83
83
  const onSearch = useCallback(values => {
84
+ if (page.pageNum !== 1) {
85
+ // 查询或重置都从第一页开始,保留当前每页条数
86
+ onChange(1, page.pageSize);
87
+ }
84
88
  if (!_isEqual(searchValues, values)) {
85
- // 保留当前页码和每页条数
86
- onChange(page.pageNum, page.pageSize);
87
89
  setSearchValues(values);
88
90
  }
89
91
  }, [onChange, searchValues, page]);
@@ -171,12 +171,11 @@
171
171
  padding: 4px 0 5px !important;
172
172
  margin-right: 8px !important;
173
173
  margin-left: 0;
174
- background: #e7e8ee;
174
+ background: #F5F5F5;
175
175
  transition: none;
176
176
  border-color: #e7e8ee;
177
177
  &.@{ant-prefix}-tabs-tab-active {
178
178
  background: #ffffff;
179
- border-top: 2px solid var(--zaui-brand, #006aff);
180
179
  }
181
180
  }
182
181
  .ant-tabs-nav {
Binary file
@@ -1,12 +1,13 @@
1
1
  import { EditOutlined } from '@ant-design/icons';
2
+ import classNames from 'classnames';
2
3
  import React from 'react';
3
- import styles from "./index.less";
4
4
  import { jsx as _jsx } from "react/jsx-runtime";
5
5
  const EditIcon = ({
6
6
  onClick
7
7
  }) => {
8
+ const cls = classNames('pro-table-editable-cell-edit-icon');
8
9
  return /*#__PURE__*/_jsx("span", {
9
- className: styles['editable-cell-edit-icon'],
10
+ className: cls,
10
11
  onClick: onClick,
11
12
  children: /*#__PURE__*/_jsx(EditOutlined, {})
12
13
  });
@@ -1,9 +1,9 @@
1
1
  import _isEqual from "lodash/isEqual";
2
2
  import React, { useState, useRef } from 'react';
3
3
  import { Form } from 'antd';
4
+ import classNames from 'classnames';
4
5
  import * as componentMap from "../../../ProForm/components";
5
6
  import EditIcon from "./EditIcon";
6
- import styles from "./index.less";
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  const InternalCell = ({
9
9
  value,
@@ -29,6 +29,7 @@ const InternalCell = ({
29
29
  ...restFieldProps
30
30
  } = fieldProps;
31
31
  const Component = componentMap[type] ?? componentMap.Input;
32
+ const wrapperClassName = classNames('pro-table-editable-cell-wrapper');
32
33
  const handleEditIconClick = () => {
33
34
  snapshotRef.current = value;
34
35
  form.setFieldValue(fieldName, value);
@@ -57,7 +58,7 @@ const InternalCell = ({
57
58
  };
58
59
  if (!editing) {
59
60
  return /*#__PURE__*/_jsxs("span", {
60
- className: styles['editable-cell-wrapper'],
61
+ className: wrapperClassName,
61
62
  children: [/*#__PURE__*/_jsx("span", {
62
63
  children: displayContent ?? '-'
63
64
  }), /*#__PURE__*/_jsx(EditIcon, {
@@ -624,6 +624,32 @@
624
624
  }
625
625
  }
626
626
  }
627
+
628
+ .pro-table-editable-cell-wrapper {
629
+ display: flex;
630
+ align-items: center;
631
+ width: 100%;
632
+ justify-content: space-between;
633
+ gap: 4px;
634
+ white-space: nowrap;
635
+ }
636
+
637
+ .pro-table-editable-cell-edit-icon {
638
+ display: inline-flex;
639
+ align-items: center;
640
+ color: #d8d8d8;
641
+ cursor: pointer;
642
+ visibility: hidden;
643
+ flex-shrink: 0;
644
+
645
+ &:hover {
646
+ color: var(--zaui-brand, #006aff);
647
+ }
648
+ }
649
+
650
+ td:hover .pro-table-editable-cell-edit-icon {
651
+ visibility: visible;
652
+ }
627
653
  }
628
654
 
629
655
  .@{ant-prefix}-dropdown-placement-bottomLeft {
@@ -277,7 +277,7 @@ const ProTreeModal = props => {
277
277
  if (draggable && span) {
278
278
  // 开启 draggable 使用拖拽后的顺序
279
279
  const checkedList = listRef.current?.onChange();
280
- onChange(checkedList);
280
+ onChange?.(checkedList);
281
281
  } else {
282
282
  let values = checkedValues;
283
283
  if (labelInValue) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zat-design/sisyphus-react",
3
- "version": "4.5.8",
3
+ "version": "4.5.9",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -88,6 +88,9 @@
88
88
  "start": "cross-env NODE_OPTIONS=\"--openssl-legacy-provider\" dumi dev",
89
89
  "test": "umi-test",
90
90
  "test:coverage": "umi-test --coverage",
91
+ "test:coverage:focused": "jest --config=jest.coverage.focused.js --coverage",
92
+ "coverage:report": "node ./scripts/coverage-gap-report.mjs --target=90 --scope=full",
93
+ "coverage:gate": "node ./scripts/coverage-directory-gate.mjs",
91
94
  "tsc": "tsc --noEmit"
92
95
  },
93
96
  "lint-staged": {
@@ -1,24 +0,0 @@
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
- &:hover {
18
- color: var(--zaui-brand, #006aff);
19
- }
20
- }
21
-
22
- td:hover .editable-cell-edit-icon {
23
- visibility: visible;
24
- }