@cfmm/umi-plugins-ui-v2 0.0.16 → 0.0.18

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.
Files changed (43) hide show
  1. package/dist/cjs/components/CrudTable.tpl +27 -6
  2. package/dist/cjs/components/ImportExecl.tpl +53 -5
  3. package/dist/cjs/hooks/useAction.tpl +38 -23
  4. package/dist/cjs/locales/en-US.d.ts +3 -0
  5. package/dist/cjs/locales/enUS/File.d.ts +3 -0
  6. package/dist/cjs/locales/enUS/File.js +3 -0
  7. package/dist/cjs/locales/enUS/index.d.ts +3 -0
  8. package/dist/cjs/locales/th-TH.d.ts +3 -0
  9. package/dist/cjs/locales/zh-CN.d.ts +3 -0
  10. package/dist/cjs/locales/zh-TW.d.ts +3 -0
  11. package/dist/cjs/locales/zhCN/File.d.ts +3 -0
  12. package/dist/cjs/locales/zhCN/File.js +3 -0
  13. package/dist/cjs/locales/zhCN/index.d.ts +3 -0
  14. package/dist/cjs/locales/zhTW/File.d.ts +3 -0
  15. package/dist/cjs/locales/zhTW/File.js +3 -0
  16. package/dist/cjs/locales/zhTW/index.d.ts +3 -0
  17. package/dist/cjs/types/CrudTableTypes.d.ts +1 -1
  18. package/dist/cjs/types/CrudTableTypes.js +1 -1
  19. package/dist/cjs/types/ImportExeclTypes.d.ts +1 -1
  20. package/dist/cjs/types/ImportExeclTypes.js +1 -1
  21. package/dist/cjs/utils/importHelper.tpl +18 -7
  22. package/dist/esm/components/CrudTable.tpl +27 -6
  23. package/dist/esm/components/ImportExecl.tpl +53 -5
  24. package/dist/esm/hooks/useAction.tpl +38 -23
  25. package/dist/esm/locales/en-US.d.ts +3 -0
  26. package/dist/esm/locales/enUS/File.d.ts +3 -0
  27. package/dist/esm/locales/enUS/File.js +3 -0
  28. package/dist/esm/locales/enUS/index.d.ts +3 -0
  29. package/dist/esm/locales/th-TH.d.ts +3 -0
  30. package/dist/esm/locales/zh-CN.d.ts +3 -0
  31. package/dist/esm/locales/zh-TW.d.ts +3 -0
  32. package/dist/esm/locales/zhCN/File.d.ts +3 -0
  33. package/dist/esm/locales/zhCN/File.js +3 -0
  34. package/dist/esm/locales/zhCN/index.d.ts +3 -0
  35. package/dist/esm/locales/zhTW/File.d.ts +3 -0
  36. package/dist/esm/locales/zhTW/File.js +3 -0
  37. package/dist/esm/locales/zhTW/index.d.ts +3 -0
  38. package/dist/esm/types/CrudTableTypes.d.ts +1 -1
  39. package/dist/esm/types/CrudTableTypes.js +1 -1
  40. package/dist/esm/types/ImportExeclTypes.d.ts +1 -1
  41. package/dist/esm/types/ImportExeclTypes.js +1 -1
  42. package/dist/esm/utils/importHelper.tpl +18 -7
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- export declare const ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{ success: boolean; failMsg?: string; data?: any } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
1
+ export declare const ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
@@ -4,4 +4,4 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.ImportExeclTypes = void 0;
7
- var ImportExeclTypes = exports.ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{ success: boolean; failMsg?: string; data?: any } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
7
+ var ImportExeclTypes = exports.ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
@@ -1,10 +1,10 @@
1
1
  // replaceTo:tsx
2
- import { UTC_FORMAT } from '../constants';
3
2
  import { getIntl } from '@umijs/max';
4
3
  import { Modal, Tag } from 'antd';
5
4
  import dayjs from 'dayjs';
6
5
  import { parse } from 'querystring';
7
6
  import * as XLSX from 'xlsx';
7
+ import { UTC_FORMAT } from '../constants';
8
8
 
9
9
  /**
10
10
  * 提取括号中的值
@@ -22,6 +22,8 @@ export const getParenthesisValue = (str: string) => {
22
22
  * @param max 最大读取行数
23
23
  * @param header 哪行是表头,默认第一行
24
24
  * @param rangeStart 开始读取行数{ c: 1, r: 2 } 从第2列第3行开始读取
25
+ * @param defval 缺省单元格占位值;传入后空单元格也会出现在每行对象中(如 `''` / `null`),不传则 SheetJS 默认会省略空字段的 key
26
+ * @param blankrows 是否输出完全空白的行,与 SheetJS `sheet_to_json` 的 `blankrows` 一致;不传则使用库默认
25
27
  * @return Promise<T = any[]>
26
28
  */
27
29
  export function importExexl<T = any>({
@@ -29,11 +31,17 @@ export function importExexl<T = any>({
29
31
  max = 1,
30
32
  rangeStart,
31
33
  header,
34
+ defval = null,
35
+ blankrows,
32
36
  }: {
33
37
  file: any;
34
38
  max?: number;
35
39
  header?: number;
36
40
  rangeStart?: { c: number; r: number };
41
+ /** 空单元格占位,传入后不过滤空字段(与表头对齐的 key 都会存在) */
42
+ defval?: unknown;
43
+ /** 是否输出空白行 */
44
+ blankrows?: boolean;
37
45
  }): Promise<T[]> {
38
46
  return new Promise((resolve, reject) => {
39
47
  // 通过FileReader对象读取文件
@@ -61,6 +69,9 @@ export function importExexl<T = any>({
61
69
  XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {
62
70
  header,
63
71
  dateNF: 'YYYY-MM-DDTHH:mm:ss[Z]',
72
+ ...(blankrows !== undefined ? { blankrows } : {}),
73
+ // 有 defval 时空单元格也会生成对应字段,否则缺省单元格不会出现在对象里
74
+ ...(defval !== undefined ? { defval } : {}),
64
75
  // 属性s从第1列第3行开始读取,属性c到最后一行最后一列结束(Excel 行号从1开始)
65
76
  range: rangeStart
66
77
  ? {
@@ -145,23 +156,23 @@ function convertIndexedPropertiesToArrays(obj: Record<string, any>): any {
145
156
  * @returns
146
157
  */
147
158
  export const handleImportExeclCellValue = (value: unknown, type?: string) => {
148
- if (!value && value !== 0 && value !== '0') return;
159
+ if (!value && value !== 0 && value !== '0') return value;
149
160
 
150
161
  if (type === 'parenthesisCode') {
151
162
  const newValue = getParenthesisValue(value as string);
152
163
  return typeof newValue === 'string' ? newValue.trim() : newValue;
153
164
  }
154
- if (value instanceof Date || (type === 'date' && typeof value === 'string')) {
165
+ if (value instanceof Date || (type === 'date' && typeof value === 'string')) {
155
166
  if (value instanceof Date) {
156
167
  const corrected = new Date(value);
157
168
  const seconds = corrected.getSeconds();
158
-
169
+
159
170
  // 修正读取execl日期精度问题,如果秒数大于10,加1分钟并清零分钟和秒
160
171
  if (seconds > 10) {
161
172
  corrected.setMinutes(corrected.getMinutes() + 1);
162
173
  corrected.setSeconds(0, 0); // 清零秒和毫秒
163
174
  }
164
-
175
+
165
176
  const dateStr = dayjs.utc(corrected).format(UTC_FORMAT);
166
177
  return dateStr;
167
178
  }
@@ -187,7 +198,7 @@ export const createNestedObjectAndSetValue = (
187
198
  keys: string | string[],
188
199
  value: any,
189
200
  obj: Record<string, any>,
190
- options: Omit<HandleImportListOptions, 'hasRowNum'>
201
+ options: Omit<HandleImportListOptions, 'hasRowNum'>,
191
202
  ) => {
192
203
  if (!keys.length) return obj;
193
204
  // 过滤没有定义code的字段
@@ -207,7 +218,7 @@ export const createNestedObjectAndSetValue = (
207
218
  const handleValue = needHandleCenterlineValue ? (value === '-' ? null : value) : value;
208
219
 
209
220
  // 转为嵌套对象
210
- newKeys.forEach((key) => {
221
+ newKeys.forEach((key, idx) => {
211
222
  if (current[key] === undefined) {
212
223
  current[key] = {};
213
224
  }
@@ -2,7 +2,7 @@ import { CloudDownloadOutlined, DownloadOutlined, PlusOutlined, UploadOutlined }
2
2
  import { ActionType, ColumnsState, ProColumns, ProFormInstance, ProTable } from '@ant-design/pro-components';
3
3
  import { cfmmUtils, ImportExecl, useFormatLocale, useIntl, useModel } from '@umijs/max';
4
4
  import { Button, Drawer, Dropdown, MenuProps, message, Modal, Progress, Radio } from 'antd';
5
- import React, { ForwardedRef, forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
+ import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
6
6
  import useAction from '../hooks/useAction';
7
7
  import useAuthority from '../hooks/useAuthority';
8
8
  import useMemoizedFn from '../hooks/useMemoizedFn';
@@ -716,6 +716,11 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
716
716
  ];
717
717
  }, [rowKey, columns, multiLang, defaultAuthCodes, actionColumnConfig, updateFormRef]);
718
718
 
719
+ const viewColumns = useMemo<ProColumns<T>[]>(() => {
720
+ // 如果没有配置hideInExport,说明是正常的表格字段,配置了hideInExport,说明是导出隐藏的列
721
+ return columns.filter((column) => (column as any).hideInExport ?? true);
722
+ }, [columns]);
723
+
719
724
  const editColumns = useMemo<ProColumns<T>[]>(() => {
720
725
  return columns.map((column) => {
721
726
  if (updateConfig.disabledColumnKeys?.includes(column.dataIndex as keyof T)) {
@@ -764,6 +769,11 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
764
769
  onDelete: handleDelete,
765
770
  updateModalOpen: () => updateDrawerRef.current?.show(),
766
771
  updateModalClose: () => updateDrawerRef.current?.close(),
772
+ handlePercentChange,
773
+ downloadButtonLoading,
774
+ setDownloadButtonLoading,
775
+ percent,
776
+ setPercent,
767
777
  }),
768
778
  [
769
779
  actionRef,
@@ -788,6 +798,11 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
788
798
  setEditLangModalOpen,
789
799
  setCurrentLangInfo,
790
800
  handleDelete,
801
+ handlePercentChange,
802
+ downloadButtonLoading,
803
+ setDownloadButtonLoading,
804
+ percent,
805
+ setPercent,
791
806
  ],
792
807
  );
793
808
 
@@ -866,7 +881,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
866
881
  }
867
882
  key="add"
868
883
  >
869
- <PlusOutlined style={{ marginRight: 6 }} />
884
+ <PlusOutlined style={{ marginRight: 8 }} />
870
885
  {formatMessage({ id: 'cfmmUI.common.button.add', defaultMessage: '新增' })}
871
886
  </a>
872
887
  ),
@@ -876,7 +891,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
876
891
  key: 'exportMultiLang',
877
892
  label: (
878
893
  <a onClick={handleExportMultiLang} key="exportMultiLang">
879
- <CloudDownloadOutlined style={{ marginRight: 6 }} />
894
+ <CloudDownloadOutlined style={{ marginRight: 8 }} />
880
895
  {formatMessage({ id: 'cfmmUI.common.button.exportMultiLang', defaultMessage: '导出多语言' })}
881
896
  </a>
882
897
  ),
@@ -898,7 +913,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
898
913
  key: 'templateDownload',
899
914
  label: (
900
915
  <a onClick={handleDownloadTemplate} key="templateDownload">
901
- <DownloadOutlined style={{ marginRight: 6 }} />
916
+ <DownloadOutlined style={{ marginRight: 8 }} />
902
917
  {formatMessage({ id: 'cfmmUI.common.button.templateDownload', defaultMessage: '下载模板' })}
903
918
  </a>
904
919
  ),
@@ -909,7 +924,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
909
924
  disabled: !tableList.length || loading,
910
925
  label: (
911
926
  <a onClick={handleDownloadExcel} key="export">
912
- <CloudDownloadOutlined style={{ marginRight: 6 }} />
927
+ <CloudDownloadOutlined style={{ marginRight: 8 }} />
913
928
  {formatMessage({ id: 'cfmmUI.common.button.exportData', defaultMessage: '导出数据' })}
914
929
  </a>
915
930
  ),
@@ -940,6 +955,12 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
940
955
  return allButtons!.filter((button) => button.auth).map(({ auth, ...rest }) => rest);
941
956
  }, [tableList, loading, columnsStateMap]);
942
957
 
958
+ useEffect(() => {
959
+ return () => {
960
+ timer.current && clearTimeout(timer.current);
961
+ };
962
+ }, []);
963
+
943
964
  return (
944
965
  <>
945
966
  {downloadButtonLoading ? <Progress percent={percent} size="small" /> : null}
@@ -1149,7 +1170,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
1149
1170
  />
1150
1171
 
1151
1172
  {/* 查看详情 */}
1152
- <ViewTableItemDrawer<T> row={viewRow} onClose={useMemoizedFn(() => setViewRow(undefined))} columns={columns} />
1173
+ <ViewTableItemDrawer<T> row={viewRow} onClose={useMemoizedFn(() => setViewRow(undefined))} columns={viewColumns} />
1153
1174
 
1154
1175
  {/* 操作日志 */}
1155
1176
  <ActionLogDrawer row={actionLogRow} onClose={useMemoizedFn(() => setActionLogRow(undefined))} />
@@ -1,19 +1,21 @@
1
- import { importExexl } from '../utils/importHelper';
2
1
  import { UploadOutlined } from '@ant-design/icons';
3
2
  import { useIntl } from '@umijs/max';
4
3
  import type { UploadFile, UploadProps } from 'antd';
5
- import { Button, message, Upload } from 'antd';
4
+ import { Button, message, Modal, Upload } from 'antd';
6
5
  import React from 'react';
7
6
  import { ImportExeclProps } from '../types';
7
+ import { importExexl } from '../utils/importHelper';
8
8
  import styles from './ImportExecl.less';
9
9
 
10
10
  const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
11
11
  const {
12
- accept = ".xlsx, .xls",
12
+ accept = '.xlsx, .xls',
13
13
  title,
14
14
  max,
15
15
  header,
16
16
  rangeStart,
17
+ defval,
18
+ blankrows,
17
19
  uploadProps,
18
20
  buttonProps,
19
21
  children,
@@ -36,7 +38,7 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
36
38
  0,
37
39
  );
38
40
 
39
- const data = await importExexl({ file: info.file, rangeStart, max, header });
41
+ const data = await importExexl({ file: info.file, rangeStart, max, header, defval, blankrows });
40
42
  const result = await onChange?.(data as T, info.file as UploadFile);
41
43
 
42
44
  hide?.();
@@ -52,6 +54,52 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
52
54
  { count: result.data },
53
55
  ),
54
56
  );
57
+ } else if (
58
+ result.data &&
59
+ typeof result.data === 'object' &&
60
+ (result.data.insertCount || result.data.updateCount || result.data.deleteCount)
61
+ ) {
62
+ const { insertCount = 0, updateCount = 0, deleteCount = 0 } = result.data;
63
+ Modal.info({
64
+ title: formatMessage(
65
+ {
66
+ id: 'cfmmUI.File.import.exceed.success',
67
+ defaultMessage: '成功导入 {count} 条数据',
68
+ },
69
+ { count: insertCount + updateCount + deleteCount },
70
+ ),
71
+ content: (
72
+ <div>
73
+ <p>
74
+ {formatMessage(
75
+ {
76
+ id: 'cfmmUI.File.import.add.success',
77
+ defaultMessage: '新增 {insertCount} 条数据',
78
+ },
79
+ { count: insertCount },
80
+ )}
81
+ </p>
82
+ <p>
83
+ {formatMessage(
84
+ {
85
+ id: 'cfmmUI.File.import.update.success',
86
+ defaultMessage: '更新 {updateCount} 条数据',
87
+ },
88
+ { count: updateCount },
89
+ )}
90
+ </p>
91
+ <p>
92
+ {formatMessage(
93
+ {
94
+ id: 'cfmmUI.File.import.delete.success',
95
+ defaultMessage: '删除 {deleteCount} 条数据',
96
+ },
97
+ { count: deleteCount },
98
+ )}
99
+ </p>
100
+ </div>
101
+ ),
102
+ });
55
103
  } else {
56
104
  message.success(
57
105
  formatMessage({
@@ -105,4 +153,4 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
105
153
  );
106
154
  };
107
155
 
108
- export default React.memo(ImportExecl) as <T,>(props: ImportExeclProps<T>) => React.ReactElement;
156
+ export default React.memo(ImportExecl) as <T>(props: ImportExeclProps<T>) => React.ReactElement;
@@ -4,10 +4,16 @@ import { useState } from 'react';
4
4
  import { API } from '../types';
5
5
  import useActionLocales from './useActionLocales';
6
6
 
7
- // 支持两种函数签名的ActionFn类型
7
+ /** 与 queryList 中 queryFn 一致:用泛型 K 绑定接口返回的 data/rows 载荷类型 */
8
+ type ListOrBasePromise<K> = Promise<API.Result_Base<K>> | Promise<API.Result_Base_List<K>>;
9
+
10
+ /**
11
+ * 支持多种 service 签名;K 由返回的 Result_Base<K> / Result_Base_List<K> 推断(与 useQueryTableList 思路一致)
12
+ */
8
13
  export type ActionFn<T, K> =
9
- | ((params: T, url?: string) => Promise<API.Result_Base<K>>)
10
- | ((method: API.MethodTypes, params: T, url?: string) => Promise<API.Result_Base<K>>);
14
+ | (() => ListOrBasePromise<K>)
15
+ | ((params: T, url?: string) => ListOrBasePromise<K>)
16
+ | ((method: API.MethodTypes, params: T, url?: string) => ListOrBasePromise<K>);
11
17
 
12
18
  type Options<T, K> = {
13
19
  // loading时的提示内容,false时不显示
@@ -24,6 +30,13 @@ type Options<T, K> = {
24
30
  showMsg?: boolean;
25
31
  };
26
32
 
33
+ /** doAction 返回值:`data` 恒为 `K | null`(无 undefined),便于多项目复用 */
34
+ export type DoActionResult<K> = {
35
+ success: boolean;
36
+ data: K | null;
37
+ error: AxiosError | null;
38
+ };
39
+
27
40
  interface IProps {
28
41
  baseLocalCode?: string;
29
42
  baseLocalName?: string;
@@ -38,24 +51,20 @@ const useAction = (props: IProps = {}) => {
38
51
  const [error, setError] = useState<AxiosError>();
39
52
 
40
53
  /**
41
- * 列表的操作方法
42
- * @param method 请求方式
43
- * @param params 传给后端的参数
44
- * @param {Options} options
45
- * @returns {{success: boolean; data?: K | null}}
54
+ * 列表的操作方法;`data` 的泛型 K 由 `options.actionFn` 的返回类型推断(未标注返回类型时 K 回退为 any,与旧行为一致)
46
55
  */
47
- async function doAction<T = API.TableListParams, K = any>(
56
+ async function doAction<TParams = API.TableListParams, KData = any>(
48
57
  method: API.AllMethodTypes,
49
- params: T,
50
- options: Omit<Options<T, K>, 'actionFn'> & { actionFn: any },
58
+ params: TParams,
59
+ options: Options<TParams, KData>,
51
60
  url?: string,
52
- ): Promise<{ success: boolean; data?: K | null; error?: AxiosError | null }> {
61
+ ): Promise<DoActionResult<KData>> {
53
62
  setLoading(true);
54
63
 
55
64
  const { actionFn, waitingMsg, successMsg, failMsg, showMsg = true, errCallback } = options;
56
65
 
57
66
  let success: boolean = false;
58
- let data: K | null = null;
67
+ let data: KData | null = null;
59
68
  let fnError: AxiosError | null = null;
60
69
  let hide;
61
70
 
@@ -76,14 +85,13 @@ const useAction = (props: IProps = {}) => {
76
85
  }
77
86
 
78
87
  try {
79
- // 根据actionFn的参数数量决定如何调用
80
- let result: API.Result_Base<K>;
88
+ type AnyResult = API.Result_Base<KData> | API.Result_Base_List<KData>;
89
+ let result: AnyResult;
90
+ const callFn = actionFn as (...args: unknown[]) => Promise<AnyResult>;
81
91
  if (actionFn.length === 1 || (actionFn.length === 2 && url)) {
82
- // actionFn只接受一个参数(params)或两个参数(params, url)
83
- result = await (actionFn as (params: T, url?: string) => Promise<API.Result_Base<K>>)(params, url);
92
+ result = await (callFn as (p: TParams, u?: string) => Promise<AnyResult>)(params, url);
84
93
  } else {
85
- // actionFn接受method参数
86
- result = await (actionFn as (method: API.MethodTypes, params: T, url?: string) => Promise<API.Result_Base<K>>)(
94
+ result = await (callFn as (m: API.MethodTypes, p: TParams, u?: string) => Promise<AnyResult>)(
87
95
  requestMethod,
88
96
  params,
89
97
  url,
@@ -92,16 +100,23 @@ const useAction = (props: IProps = {}) => {
92
100
 
93
101
  // 返回数据处理
94
102
  if (result && result.code === 200) {
95
- if(requestMethod === 'GET' && successMsg && showMsg) {
103
+ if (requestMethod === 'GET' && successMsg && showMsg) {
96
104
  message.success(successMsg);
97
105
  }
98
- if(requestMethod !== 'GET' && showMsg && successMsg !== false) {
106
+ if (requestMethod !== 'GET' && showMsg && successMsg !== false) {
99
107
  const { successMsg: successMessage } = getActionLocales(method);
100
108
  message.success(successMsg || successMessage);
101
109
  }
102
110
  success = true;
103
- if (result.data) {
104
- data = result.data;
111
+ if ('rows' in result) {
112
+ const r = result as API.Result_Base_List<KData>;
113
+ const raw = r.data != null ? r.data : r.rows;
114
+ // 与历史逻辑一致:仅 truthy 时写入(0 / false / 空串 等仍不写入,避免行为变化)
115
+ if (raw) {
116
+ data = raw as KData;
117
+ }
118
+ } else if ((result as API.Result_Base<KData>).data) {
119
+ data = (result as API.Result_Base<KData>).data;
105
120
  }
106
121
  }
107
122
 
@@ -52,6 +52,9 @@ declare const _default: {
52
52
  'cfmmUI.File.import': string;
53
53
  'cfmmUI.File.import.exceed.error.title': string;
54
54
  'cfmmUI.File.import.exceed.success': string;
55
+ 'cfmmUI.File.import.add.success': string;
56
+ 'cfmmUI.File.import.update.success': string;
57
+ 'cfmmUI.File.import.delete.success': string;
55
58
  'cfmmUI.File.button.excel.download': string;
56
59
  'cfmmUI.File.check.type.failMessage': string;
57
60
  'cfmmUI.File.check.size.failMessage': string;
@@ -35,6 +35,9 @@ declare const _default: {
35
35
  'cfmmUI.File.import': string;
36
36
  'cfmmUI.File.import.exceed.error.title': string;
37
37
  'cfmmUI.File.import.exceed.success': string;
38
+ 'cfmmUI.File.import.add.success': string;
39
+ 'cfmmUI.File.import.update.success': string;
40
+ 'cfmmUI.File.import.delete.success': string;
38
41
  'cfmmUI.File.button.excel.download': string;
39
42
  'cfmmUI.File.check.type.failMessage': string;
40
43
  'cfmmUI.File.check.size.failMessage': string;
@@ -35,6 +35,9 @@ export default {
35
35
  'cfmmUI.File.import': 'Import',
36
36
  'cfmmUI.File.import.exceed.error.title': 'Import Failed, Reason',
37
37
  'cfmmUI.File.import.exceed.success': 'Successfully Imported {count} Pieces Of Data',
38
+ 'cfmmUI.File.import.add.success': 'Added {count} Pieces Of Data',
39
+ 'cfmmUI.File.import.update.success': 'Updated {count} Pieces Of Data',
40
+ 'cfmmUI.File.import.delete.success': 'Deleted {count} Pieces Of Data',
38
41
  'cfmmUI.File.button.excel.download': 'Download As Excel',
39
42
  'cfmmUI.File.check.type.failMessage': 'This File Type Is Not Supported, It Can Only Be {type} Type',
40
43
  'cfmmUI.File.check.size.failMessage': 'The File Cannot Be Larger Than {size}',
@@ -52,6 +52,9 @@ declare const _default: {
52
52
  'cfmmUI.File.import': string;
53
53
  'cfmmUI.File.import.exceed.error.title': string;
54
54
  'cfmmUI.File.import.exceed.success': string;
55
+ 'cfmmUI.File.import.add.success': string;
56
+ 'cfmmUI.File.import.update.success': string;
57
+ 'cfmmUI.File.import.delete.success': string;
55
58
  'cfmmUI.File.button.excel.download': string;
56
59
  'cfmmUI.File.check.type.failMessage': string;
57
60
  'cfmmUI.File.check.size.failMessage': string;
@@ -52,6 +52,9 @@ declare const _default: {
52
52
  'cfmmUI.File.import': string;
53
53
  'cfmmUI.File.import.exceed.error.title': string;
54
54
  'cfmmUI.File.import.exceed.success': string;
55
+ 'cfmmUI.File.import.add.success': string;
56
+ 'cfmmUI.File.import.update.success': string;
57
+ 'cfmmUI.File.import.delete.success': string;
55
58
  'cfmmUI.File.button.excel.download': string;
56
59
  'cfmmUI.File.check.type.failMessage': string;
57
60
  'cfmmUI.File.check.size.failMessage': string;
@@ -52,6 +52,9 @@ declare const _default: {
52
52
  'cfmmUI.File.import': string;
53
53
  'cfmmUI.File.import.exceed.error.title': string;
54
54
  'cfmmUI.File.import.exceed.success': string;
55
+ 'cfmmUI.File.import.add.success': string;
56
+ 'cfmmUI.File.import.update.success': string;
57
+ 'cfmmUI.File.import.delete.success': string;
55
58
  'cfmmUI.File.button.excel.download': string;
56
59
  'cfmmUI.File.check.type.failMessage': string;
57
60
  'cfmmUI.File.check.size.failMessage': string;
@@ -51,6 +51,9 @@ declare const _default: {
51
51
  'cfmmUI.File.import': string;
52
52
  'cfmmUI.File.import.exceed.error.title': string;
53
53
  'cfmmUI.File.import.exceed.success': string;
54
+ 'cfmmUI.File.import.add.success': string;
55
+ 'cfmmUI.File.import.update.success': string;
56
+ 'cfmmUI.File.import.delete.success': string;
54
57
  'cfmmUI.File.button.excel.download': string;
55
58
  'cfmmUI.File.check.type.failMessage': string;
56
59
  'cfmmUI.File.check.size.failMessage': string;
@@ -35,6 +35,9 @@ declare const _default: {
35
35
  'cfmmUI.File.import': string;
36
36
  'cfmmUI.File.import.exceed.error.title': string;
37
37
  'cfmmUI.File.import.exceed.success': string;
38
+ 'cfmmUI.File.import.add.success': string;
39
+ 'cfmmUI.File.import.update.success': string;
40
+ 'cfmmUI.File.import.delete.success': string;
38
41
  'cfmmUI.File.button.excel.download': string;
39
42
  'cfmmUI.File.check.type.failMessage': string;
40
43
  'cfmmUI.File.check.size.failMessage': string;
@@ -35,6 +35,9 @@ export default {
35
35
  'cfmmUI.File.import': '导入',
36
36
  'cfmmUI.File.import.exceed.error.title': '导入失败,原因如下',
37
37
  'cfmmUI.File.import.exceed.success': '成功导入 {count} 条数据',
38
+ 'cfmmUI.File.import.add.success': '新增 {count} 条数据',
39
+ 'cfmmUI.File.import.update.success': '更新 {count} 条数据',
40
+ 'cfmmUI.File.import.delete.success': '删除 {count} 条数据',
38
41
  'cfmmUI.File.button.excel.download': '下载为Excel',
39
42
  'cfmmUI.File.check.type.failMessage': '不支持该文件类型,只能是 {type} 类型',
40
43
  'cfmmUI.File.check.size.failMessage': '文件不能大于 {size}',
@@ -52,6 +52,9 @@ declare const _default: {
52
52
  'cfmmUI.File.import': string;
53
53
  'cfmmUI.File.import.exceed.error.title': string;
54
54
  'cfmmUI.File.import.exceed.success': string;
55
+ 'cfmmUI.File.import.add.success': string;
56
+ 'cfmmUI.File.import.update.success': string;
57
+ 'cfmmUI.File.import.delete.success': string;
55
58
  'cfmmUI.File.button.excel.download': string;
56
59
  'cfmmUI.File.check.type.failMessage': string;
57
60
  'cfmmUI.File.check.size.failMessage': string;
@@ -34,6 +34,9 @@ declare const _default: {
34
34
  'cfmmUI.File.import': string;
35
35
  'cfmmUI.File.import.exceed.error.title': string;
36
36
  'cfmmUI.File.import.exceed.success': string;
37
+ 'cfmmUI.File.import.add.success': string;
38
+ 'cfmmUI.File.import.update.success': string;
39
+ 'cfmmUI.File.import.delete.success': string;
37
40
  'cfmmUI.File.button.excel.download': string;
38
41
  'cfmmUI.File.check.type.failMessage': string;
39
42
  'cfmmUI.File.check.size.failMessage': string;
@@ -34,6 +34,9 @@ export default {
34
34
  'cfmmUI.File.import': '導入',
35
35
  'cfmmUI.File.import.exceed.error.title': '導入失敗,原因如下',
36
36
  'cfmmUI.File.import.exceed.success': '成功導入 {count} 條數據',
37
+ 'cfmmUI.File.import.add.success': '新增 {count} 條數據',
38
+ 'cfmmUI.File.import.update.success': '更新 {count} 條數據',
39
+ 'cfmmUI.File.import.delete.success': '刪除 {count} 條數據',
37
40
  'cfmmUI.File.button.excel.download': '下載為Excel',
38
41
  'cfmmUI.File.check.type.failMessage': '不支持該檔案類型,只能是 {type} 類型',
39
42
  'cfmmUI.File.check.size.failMessage': '文件不能大於 {size}',
@@ -51,6 +51,9 @@ declare const _default: {
51
51
  'cfmmUI.File.import': string;
52
52
  'cfmmUI.File.import.exceed.error.title': string;
53
53
  'cfmmUI.File.import.exceed.success': string;
54
+ 'cfmmUI.File.import.add.success': string;
55
+ 'cfmmUI.File.import.update.success': string;
56
+ 'cfmmUI.File.import.delete.success': string;
54
57
  'cfmmUI.File.button.excel.download': string;
55
58
  'cfmmUI.File.check.type.failMessage': string;
56
59
  'cfmmUI.File.check.size.failMessage': string;