@fe-free/file 4.0.5 → 5.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @ones-wb/file
2
2
 
3
+ ## 5.0.0
4
+
3
5
  ## 4.0.5
4
6
 
5
7
  ## 4.0.4
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'i18next-cli';
2
+
3
+ export default defineConfig({
4
+ locales: ['zh-CN', 'en-US'],
5
+ extract: {
6
+ primaryLanguage: 'zh-CN',
7
+ secondaryLanguages: ['en-US'],
8
+ input: ['./src/**/*.{js,jsx,ts,tsx}', './todo/**/*.{js,jsx,ts,tsx}'],
9
+ output: './src/locales/{{language}}/{{namespace}}.json',
10
+ },
11
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/file",
3
- "version": "4.0.5",
3
+ "version": "5.0.0",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -10,17 +10,22 @@
10
10
  "registry": "https://registry.npmjs.org/"
11
11
  },
12
12
  "dependencies": {
13
- "@ant-design/icons": "^5.4.0",
14
- "@ant-design/x": "^1.4.0",
15
13
  "classnames": "^2.5.1",
16
14
  "exceljs": "^4.4.0",
17
15
  "file-saver": "^2.0.5",
18
16
  "lodash-es": "^4.17.21"
19
17
  },
20
18
  "peerDependencies": {
21
- "dayjs": "~1.11.10"
19
+ "@ant-design/x": "^1.4.0",
20
+ "dayjs": "~1.11.10",
21
+ "i18next": "^25.7.2",
22
+ "i18next-browser-languagedetector": "^8.2.0",
23
+ "i18next-icu": "^2.4.1",
24
+ "react": "^19.2.0",
25
+ "react-i18next": "^16.4.0"
22
26
  },
23
27
  "scripts": {
24
- "test": "echo \"Error: no test specified\" && exit 1"
28
+ "test": "echo \"Error: no test specified\" && exit 1",
29
+ "i18n-extract": "rm -rf ./src/locales/zh-CN && npx i18next-cli extract"
25
30
  }
26
31
  }
@@ -1,159 +0,0 @@
1
- import type { StepsProps, UploadFile } from 'antd';
2
- import { Button, Modal, Steps } from 'antd';
3
- import React, { useCallback, useMemo, useState } from 'react';
4
- import { StepImport } from './step_import';
5
- import { StepPreview } from './step_preview';
6
- import { StepProcess } from './step_process';
7
- import './style.scss';
8
- import type { ImportXlsxProps, XlsxDataItem } from './types';
9
-
10
- function ImportXlsx(props: ImportXlsxProps) {
11
- const { onTemplateDownload, size, onImport, onOk } = props;
12
-
13
- const [current, setCurrent] = useState(0);
14
-
15
- const [fileList, setFileList] = useState<UploadFile[]>([]);
16
- const [xlsxData, setXlsxData] = useState<XlsxDataItem[]>([]);
17
-
18
- const [sheetIndex, setSheetIndex] = useState(0);
19
- const [headerIndex, setHeaderIndex] = useState(0);
20
- const [dataStartIndex, setDataStartIndex] = useState(1);
21
-
22
- const [status, setStatus] = useState<StepsProps['status']>(undefined);
23
-
24
- const sheetData = xlsxData[sheetIndex];
25
-
26
- const uploadData = useMemo(() => {
27
- if (!sheetData) {
28
- return;
29
- }
30
-
31
- return [sheetData.data[headerIndex], ...sheetData.data.slice(dataStartIndex)];
32
- }, [dataStartIndex, headerIndex, sheetData]);
33
-
34
- return (
35
- <div className="fec-import-xlsx flex h-full flex-col gap-4">
36
- <Steps
37
- items={[
38
- {
39
- title: '上传',
40
- },
41
- {
42
- title: '预览',
43
- },
44
- {
45
- title: '数据导入',
46
- status,
47
- },
48
- ]}
49
- current={current}
50
- />
51
- <div className="flex-1 overflow-y-auto">
52
- {current === 0 && (
53
- <StepImport
54
- fileList={fileList}
55
- setFileList={setFileList}
56
- setXlsxData={setXlsxData}
57
- onTemplateDownload={onTemplateDownload}
58
- size={size}
59
- />
60
- )}
61
- {current === 1 && (
62
- <StepPreview
63
- xlsxData={xlsxData}
64
- sheetIndex={sheetIndex}
65
- setSheetIndex={setSheetIndex}
66
- headerIndex={headerIndex}
67
- setHeaderIndex={setHeaderIndex}
68
- dataStartIndex={dataStartIndex}
69
- setDataStartIndex={setDataStartIndex}
70
- />
71
- )}
72
- {current === 2 && (
73
- <StepProcess
74
- fileList={fileList}
75
- status={status}
76
- setStatus={setStatus}
77
- uploadData={uploadData!}
78
- onImport={onImport}
79
- />
80
- )}
81
- </div>
82
- <div className="flex justify-end gap-2">
83
- {current === 0 && (
84
- <>
85
- <Button disabled onClick={() => setCurrent(current - 1)}>
86
- 上一步
87
- </Button>
88
-
89
- <Button
90
- type="primary"
91
- disabled={!fileList.length}
92
- onClick={() => setCurrent(current + 1)}
93
- >
94
- 下一步
95
- </Button>
96
- </>
97
- )}
98
- {current === 1 && (
99
- <>
100
- <Button onClick={() => setCurrent(current - 1)}>上一步</Button>
101
-
102
- <Button type="primary" onClick={() => setCurrent(current + 1)}>
103
- 下一步
104
- </Button>
105
- </>
106
- )}
107
- {current === 2 && (
108
- <>
109
- <Button onClick={() => setCurrent(current - 1)}>上一步</Button>
110
-
111
- <Button disabled={status !== 'finish'} type="primary" onClick={onOk}>
112
- 导入
113
- </Button>
114
- </>
115
- )}
116
- </div>
117
- </div>
118
- );
119
- }
120
-
121
- interface ModalImportXlsxProps extends Omit<ImportXlsxProps, 'onOk'> {
122
- children: React.ReactElement;
123
- }
124
- function ModalImportXlsx(props: ModalImportXlsxProps) {
125
- const [open, setOpen] = useState(false);
126
- const { children, ...rest } = props;
127
-
128
- const handleOk = useCallback(() => {
129
- setOpen(false);
130
- }, []);
131
-
132
- return (
133
- <>
134
- {open && (
135
- <Modal title="批量导入" onCancel={() => setOpen(false)} footer={null} open width={850}>
136
- <div className="h-[450px]">
137
- <ImportXlsx {...rest} onOk={handleOk} />
138
- </div>
139
- </Modal>
140
- )}
141
- {React.cloneElement(children, {
142
- onClick: () => {
143
- console.log('click');
144
- setOpen(true);
145
- },
146
- })}
147
- </>
148
- );
149
- }
150
-
151
- function ImportXlsxButton(props: Omit<ModalImportXlsxProps, 'children'>) {
152
- return (
153
- <ModalImportXlsx {...props}>
154
- <Button>批量导入</Button>
155
- </ModalImportXlsx>
156
- );
157
- }
158
-
159
- export { ImportXlsxButton, ModalImportXlsx };
@@ -1,89 +0,0 @@
1
- import { LoadingButton } from '@fe-free/core';
2
- import type { UploadFile } from 'antd';
3
- import { Button, Modal } from 'antd';
4
- import React, { useState } from 'react';
5
- import { StepImport } from './step_import';
6
- import './style.scss';
7
- import type { ImportXlsxFileProps } from './types';
8
- import { EnumImportType } from './types';
9
-
10
- function ImportXlsxFile(props: ImportXlsxFileProps) {
11
- const { onTemplateDownload, size, onCancel, onOk, enableImportType } = props;
12
-
13
- const [fileList, setFileList] = useState<UploadFile[]>([]);
14
-
15
- const [importType, setImportType] = useState<EnumImportType>(EnumImportType.FULL);
16
-
17
- return (
18
- <div className="fec-import-xlsx flex h-full flex-col gap-4">
19
- <div className="flex-1 overflow-y-auto">
20
- <StepImport
21
- fileList={fileList}
22
- setFileList={setFileList}
23
- onTemplateDownload={onTemplateDownload}
24
- size={size}
25
- onImportType={enableImportType ? setImportType : undefined}
26
- />
27
- </div>
28
- <div className="flex justify-end gap-2">
29
- <Button onClick={() => onCancel?.()}>取消</Button>
30
- <LoadingButton
31
- disabled={!fileList.length}
32
- type="primary"
33
- onClick={() => {
34
- if (enableImportType) {
35
- onOk({ uploadFile: fileList[0], importType });
36
- } else {
37
- onOk({ uploadFile: fileList[0] });
38
- }
39
- }}
40
- >
41
- 导入
42
- </LoadingButton>
43
- </div>
44
- </div>
45
- );
46
- }
47
-
48
- interface ModalImportXlsxFileProps extends ImportXlsxFileProps {
49
- children: React.ReactElement;
50
- }
51
- function ModalImportXlsxFile(props: ModalImportXlsxFileProps) {
52
- const [open, setOpen] = useState(false);
53
- const { children, onOk, ...rest } = props;
54
-
55
- return (
56
- <>
57
- {open && (
58
- <Modal title="批量导入" onCancel={() => setOpen(false)} footer={null} open>
59
- <div>
60
- <ImportXlsxFile
61
- {...rest}
62
- onOk={async (data) => {
63
- await onOk(data);
64
- setOpen(false);
65
- }}
66
- onCancel={() => setOpen(false)}
67
- />
68
- </div>
69
- </Modal>
70
- )}
71
- {React.cloneElement(children, {
72
- onClick: () => {
73
- console.log('click');
74
- setOpen(true);
75
- },
76
- })}
77
- </>
78
- );
79
- }
80
-
81
- function ImportXlsxFileButton(props: Omit<ModalImportXlsxFileProps, 'children'>) {
82
- return (
83
- <ModalImportXlsxFile {...props}>
84
- <Button>批量导入</Button>
85
- </ModalImportXlsxFile>
86
- );
87
- }
88
-
89
- export { ImportXlsxFileButton, ModalImportXlsxFile };
@@ -1,2 +0,0 @@
1
- export { ImportXlsxButton, ModalImportXlsx } from './import_xlsx';
2
- export { ImportXlsxFileButton, ModalImportXlsxFile } from './import_xlsx_file';
@@ -1,120 +0,0 @@
1
- import { Attachments } from '@ant-design/x';
2
- import { xlsxToJSON } from '@fe-free/file';
3
- import { DeleteOutlined, InboxOutlined } from '@fe-free/icons';
4
- import type { UploadFile } from 'antd';
5
- import { App, Button, Radio, Upload } from 'antd';
6
- import classNames from 'classnames';
7
- import './style.scss';
8
- import type { ImportXlsxProps, XlsxDataItem } from './types';
9
- import { EnumImportType } from './types';
10
-
11
- function ItemRender({ file, right }: { file: UploadFile; right?: React.ReactNode }) {
12
- return (
13
- <div className="flex items-center gap-2 rounded-md border border-01">
14
- <div className="flex-1">
15
- <Attachments.FileCard key={file.name} item={file} />
16
- </div>
17
- {right}
18
- </div>
19
- );
20
- }
21
-
22
- function StepImport({
23
- fileList,
24
- setFileList,
25
- setXlsxData,
26
- onTemplateDownload,
27
- onImportType,
28
- size,
29
- }: {
30
- fileList: UploadFile[];
31
- setFileList: (fileList: UploadFile[]) => void;
32
- setXlsxData?: (xlsxData: XlsxDataItem[]) => void;
33
- onTemplateDownload?: ImportXlsxProps['onTemplateDownload'];
34
- onImportType?: (importType: EnumImportType) => void;
35
- size: ImportXlsxProps['size'];
36
- }) {
37
- const { message } = App.useApp();
38
- const mbSize = size && (size / 1024 / 1024).toFixed(1);
39
-
40
- const handleBeforeUpload = async (file: File) => {
41
- if (size) {
42
- if (file.size > size) {
43
- message.error(`文件大小不能超过${mbSize}MB`);
44
- }
45
- }
46
-
47
- if (setXlsxData) {
48
- const xlsxData = await xlsxToJSON(file);
49
- setXlsxData(xlsxData);
50
- }
51
-
52
- return false;
53
- };
54
-
55
- return (
56
- <div
57
- className={classNames('flex flex-col gap-2', 'fec-import-xlsx', {
58
- 'fec-import-xlsx-has-file': fileList.length > 0,
59
- })}
60
- >
61
- {onImportType && (
62
- <div className="py-4">
63
- <Radio.Group
64
- defaultValue={EnumImportType.FULL}
65
- onChange={(event) => onImportType(event.target.value)}
66
- >
67
- <Radio value={EnumImportType.FULL}>全量覆盖</Radio>
68
- <Radio value={EnumImportType.APPEND}>追加数据</Radio>
69
- </Radio.Group>
70
- </div>
71
- )}
72
- <Upload.Dragger
73
- accept=".xlsx,.xls,.csv"
74
- multiple={false}
75
- pastable
76
- fileList={fileList}
77
- itemRender={(_, file) => (
78
- <ItemRender
79
- file={file}
80
- right={
81
- <Button
82
- icon={<DeleteOutlined />}
83
- type="text"
84
- onClick={() => {
85
- setFileList([]);
86
- }}
87
- />
88
- }
89
- />
90
- )}
91
- onChange={(info) => {
92
- setFileList(info.fileList);
93
- }}
94
- beforeUpload={handleBeforeUpload}
95
- >
96
- <>
97
- <p className="ant-upload-drag-icon">
98
- <InboxOutlined />
99
- </p>
100
- <p className="ant-upload-text">点击上传或拖拽文档到这里</p>
101
- <p className="ant-upload-hint">
102
- 上传一份 Excel 格式或者 CSV 格式的文档{mbSize && `,文件大小限制 ${mbSize}MB 以内`}
103
- </p>
104
- </>
105
- </Upload.Dragger>
106
-
107
- {onTemplateDownload && (
108
- <div className="flex items-center gap-1">
109
- <span>可下载模板,根据指引在模板中编辑</span>
110
- <Button type="link" className="px-0" onClick={onTemplateDownload}>
111
- 下载模板
112
- </Button>
113
- <span>。</span>
114
- </div>
115
- )}
116
- </div>
117
- );
118
- }
119
-
120
- export { ItemRender, StepImport };
@@ -1,106 +0,0 @@
1
- import { Table } from '@fe-free/core';
2
- import { Select } from 'antd';
3
- import { isDate, range } from 'lodash-es';
4
- import type { XlsxDataItem } from './types';
5
-
6
- function StepPreview({
7
- xlsxData,
8
- sheetIndex,
9
- setSheetIndex,
10
- headerIndex,
11
- setHeaderIndex,
12
- dataStartIndex,
13
- setDataStartIndex,
14
- }: {
15
- xlsxData: XlsxDataItem[];
16
- sheetIndex: number;
17
- setSheetIndex: (index: number) => void;
18
- headerIndex: number;
19
- setHeaderIndex: (index: number) => void;
20
- dataStartIndex: number;
21
- setDataStartIndex: (index: number) => void;
22
- }) {
23
- const sheetData = xlsxData[sheetIndex] || [];
24
-
25
- const sheetOptions = xlsxData.map((item, index) => ({ label: item.name, value: index }));
26
- const lineOptions = range(sheetData.data.length).map((line) => ({
27
- label: `第 ${line + 1} 行`,
28
- value: line,
29
- }));
30
-
31
- const columns = sheetData.data[headerIndex].map((v, index) => {
32
- if (isDate(v)) {
33
- return {
34
- title: v.toString(),
35
- dataIndex: index,
36
- };
37
- }
38
-
39
- return {
40
- title: v,
41
- dataIndex: index,
42
- };
43
- });
44
-
45
- const dataSource = sheetData.data.slice(dataStartIndex).map((item, line) => {
46
- const result = {
47
- id: line,
48
- };
49
-
50
- item.forEach((v, index) => {
51
- if (isDate(v)) {
52
- result[index] = v.toString();
53
- } else {
54
- result[index] = v;
55
- }
56
- });
57
-
58
- return result;
59
- });
60
-
61
- return (
62
- <div className="flex flex-col gap-2">
63
- <div className="flex gap-5">
64
- <div className="flex items-center gap-2">
65
- 导入表
66
- <Select
67
- options={sheetOptions}
68
- value={sheetIndex}
69
- onChange={(index) => {
70
- setSheetIndex(index);
71
- setHeaderIndex(0);
72
- setDataStartIndex(1);
73
- }}
74
- className="w-[150px]"
75
- />
76
- </div>
77
- <div className="flex items-center gap-2">
78
- 表头
79
- <Select
80
- options={lineOptions}
81
- value={headerIndex}
82
- onChange={(index) => {
83
- setHeaderIndex(index);
84
- setDataStartIndex(index + 1);
85
- }}
86
- className="w-[150px]"
87
- />
88
- </div>
89
- <div className="flex items-center gap-2">
90
- 数据起始行
91
- <Select
92
- options={lineOptions}
93
- value={dataStartIndex}
94
- onChange={(index) => {
95
- setDataStartIndex(index);
96
- }}
97
- className="w-[150px]"
98
- />
99
- </div>
100
- </div>
101
- <Table columns={columns} dataSource={dataSource} pagination={false} />
102
- </div>
103
- );
104
- }
105
-
106
- export { StepPreview };
@@ -1,75 +0,0 @@
1
- import { LoadingButton } from '@fe-free/core';
2
- import { CheckOutlined, LoadingOutlined } from '@fe-free/icons';
3
- import type { StepsProps, UploadFile } from 'antd';
4
- import { Button } from 'antd';
5
- import { useEffect } from 'react';
6
- import { ItemRender } from './step_import';
7
- import type { ImportXlsxProps } from './types';
8
-
9
- function StepProcess({
10
- fileList,
11
- status,
12
- setStatus,
13
- uploadData,
14
- onImport,
15
- }: {
16
- fileList: UploadFile[];
17
- status: StepsProps['status'];
18
- setStatus: (status: StepsProps['status']) => void;
19
- uploadData: any[][];
20
- onImport: ImportXlsxProps['onImport'];
21
- }) {
22
- const handleImport = async () => {
23
- setStatus('process');
24
- try {
25
- const records: Record<string, any>[] = [];
26
- const fields = uploadData[0];
27
- uploadData.slice(1).forEach((record) => {
28
- const recordObj: Record<string, any> = {};
29
- record.forEach((cell, index) => {
30
- recordObj[fields[index]] = cell;
31
- });
32
- records.push(recordObj);
33
- });
34
-
35
- await onImport(records);
36
- setStatus('finish');
37
- } catch (error) {
38
- setStatus('error');
39
- throw error;
40
- }
41
- };
42
-
43
- let right;
44
- if (status === 'process') {
45
- right = (
46
- <Button icon={<LoadingOutlined />} type="text">
47
- 导入中
48
- </Button>
49
- );
50
- } else if (status === 'finish') {
51
- right = (
52
- <Button icon={<CheckOutlined />} type="text">
53
- 导入完成
54
- </Button>
55
- );
56
- } else if (status === 'error') {
57
- right = (
58
- <LoadingButton type="text" danger onClick={() => handleImport()}>
59
- 重新导入
60
- </LoadingButton>
61
- );
62
- }
63
-
64
- useEffect(() => {
65
- handleImport();
66
- }, []);
67
-
68
- return (
69
- <div>
70
- <ItemRender file={fileList[0]} right={right} />
71
- </div>
72
- );
73
- }
74
-
75
- export { StepProcess };
@@ -1,12 +0,0 @@
1
- .fec-import-xlsx {
2
- &.fec-import-xlsx-has-file {
3
- .ant-upload-drag {
4
- display: none;
5
- }
6
- }
7
-
8
- .ant-attachment-list-card {
9
- width: auto;
10
- background-color: white;
11
- }
12
- }
@@ -1,37 +0,0 @@
1
- import type { UploadFile } from 'antd';
2
-
3
- export enum EnumImportType {
4
- FULL = 'Full',
5
- APPEND = 'Append',
6
- }
7
-
8
- interface XlsxDataItem {
9
- name: string;
10
- data: any[][];
11
- }
12
-
13
- interface ImportXlsxProps {
14
- /** 点击 下载模板 */
15
- onTemplateDownload?: () => void;
16
- /** 文件大小限制 */
17
- size?: number;
18
- /** 导入数据 API */
19
- onImport: (data: Record<string, any>[]) => Promise<void>;
20
- /** 最后的确认 */
21
- onOk: () => void;
22
- }
23
-
24
- interface ImportXlsxFileProps {
25
- /** 点击 下载模板 */
26
- onTemplateDownload?: () => void;
27
- /** 文件大小限制 */
28
- size?: number;
29
- /** 导入文件 */
30
- onOk: (data: { uploadFile: UploadFile; importType?: EnumImportType }) => Promise<void>;
31
- /** 取消 */
32
- onCancel?: () => void;
33
- /** 是否显示导入类型 */
34
- enableImportType?: boolean;
35
- }
36
-
37
- export type { ImportXlsxFileProps, ImportXlsxProps, XlsxDataItem };