@fe-free/core 2.0.6 → 2.1.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,15 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat: upload
8
+
9
+ ### Patch Changes
10
+
11
+ - @fe-free/tool@2.1.0
12
+
3
13
  ## 2.0.6
4
14
 
5
15
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "2.0.6",
3
+ "version": "2.1.0",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -38,7 +38,7 @@
38
38
  "remark-gfm": "^4.0.1",
39
39
  "vanilla-jsoneditor": "^0.23.1",
40
40
  "zustand": "^4.5.4",
41
- "@fe-free/tool": "2.0.6"
41
+ "@fe-free/tool": "2.1.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@ant-design/pro-components": "^2.8.7",
@@ -1,11 +1,15 @@
1
1
  import { ProForm } from '@ant-design/pro-components';
2
2
  import {
3
3
  ProFormEditor,
4
+ ProFormImageUpload,
5
+ ProFormImageUploadDragger,
4
6
  ProFormJSON,
5
7
  ProFormJavascript,
6
8
  ProFormListNumber,
7
9
  ProFormListText,
8
10
  ProFormSwitchNumber,
11
+ ProFormUpload,
12
+ ProFormUploadDragger,
9
13
  } from '@fe-free/core';
10
14
  import type { Meta, StoryObj } from '@storybook/react-vite';
11
15
  import { useState } from 'react';
@@ -136,3 +140,97 @@ export const ProFormListNumberComponent: Story = {
136
140
  </ProFormBase>
137
141
  ),
138
142
  };
143
+
144
+ function customRequest(option: any) {
145
+ const { file, onProgress, onSuccess } = option;
146
+
147
+ // 模拟上传进度
148
+ let percent = 0;
149
+ const interval = setInterval(() => {
150
+ percent += 10;
151
+ onProgress({ percent });
152
+
153
+ if (percent >= 100) {
154
+ clearInterval(interval);
155
+ // 模拟上传成功
156
+ onSuccess({
157
+ data: {
158
+ url: `https://picsum.photos/200/300?random=${Date.now()}`,
159
+ name: file.name,
160
+ uid: file.uid,
161
+ },
162
+ });
163
+ }
164
+ }, 100);
165
+
166
+ // 返回 abort 方法,用于取消上传
167
+ return {
168
+ abort: () => {
169
+ clearInterval(interval);
170
+ console.log('上传已取消');
171
+ },
172
+ };
173
+ }
174
+
175
+ export const ProFormUploadComponent: Story = {
176
+ render: () => (
177
+ <ProFormBase>
178
+ <ProFormUpload label="file" name="file" fieldProps={{ customRequest }} />
179
+ <ProFormUploadDragger
180
+ label="file_dragger"
181
+ name="file_dragger"
182
+ fieldProps={{ customRequest }}
183
+ />
184
+ <ProFormUploadDragger
185
+ label="files_dragger"
186
+ name="files_dragger"
187
+ fieldProps={{ multiple: true, maxCount: 2, customRequest }}
188
+ />
189
+ <ProFormUpload
190
+ label="files"
191
+ name="files"
192
+ fieldProps={{ multiple: true, maxCount: 2, showCount: true, customRequest }}
193
+ />
194
+ <ProFormUpload
195
+ label="files_picture"
196
+ name="files_picture"
197
+ fieldProps={{ multiple: true, maxCount: 2, listType: 'picture', customRequest }}
198
+ />
199
+ <ProFormUpload
200
+ label="files_picture_card"
201
+ name="files_picture_card"
202
+ fieldProps={{ multiple: true, maxCount: 2, listType: 'picture-card', customRequest }}
203
+ />
204
+ </ProFormBase>
205
+ ),
206
+ };
207
+
208
+ export const ProFormImageUploadComponent: Story = {
209
+ render: () => (
210
+ <ProFormBase>
211
+ <ProFormImageUpload
212
+ label="image"
213
+ name="image"
214
+ fieldProps={{
215
+ customRequest,
216
+ }}
217
+ />
218
+ <ProFormImageUploadDragger
219
+ label="image_dragger"
220
+ name="image_dragger"
221
+ fieldProps={{
222
+ customRequest,
223
+ }}
224
+ />
225
+ <ProFormImageUploadDragger
226
+ label="images_dragger"
227
+ name="images_dragger"
228
+ fieldProps={{
229
+ multiple: true,
230
+ maxCount: 2,
231
+ customRequest,
232
+ }}
233
+ />
234
+ </ProFormBase>
235
+ ),
236
+ };
@@ -1,7 +1,6 @@
1
1
  export { ProFormListNumber, ProFormListText } from './form_list/form_list';
2
2
  export { ProFormListHelper } from './form_list/form_list_helper';
3
3
  export { ProFormListModalHelper } from './form_list/form_list_modal_helper';
4
-
5
4
  export { ProFormEditor } from './pro_form_editor';
6
5
  export { ProFormJavascript } from './pro_form_javascript';
7
6
  export { ProFormJSON } from './pro_form_json';
@@ -10,6 +9,12 @@ export {
10
9
  SwitchNumber,
11
10
  type SwitchNumberProps,
12
11
  } from './pro_form_switch_number';
12
+ export {
13
+ ProFormImageUpload,
14
+ ProFormImageUploadDragger,
15
+ ProFormUpload,
16
+ ProFormUploadDragger,
17
+ } from './pro_form_upload';
13
18
 
14
19
  import { pinyinMatch } from '@fe-free/tool';
15
20
 
@@ -0,0 +1,50 @@
1
+ // 避免循环引用
2
+ import { ProForm, type ProFormItemProps } from '@ant-design/pro-components';
3
+ import type {
4
+ ImageUploadDraggerProps,
5
+ ImageUploadProps,
6
+ UploadDraggerProps,
7
+ UploadProps,
8
+ } from '../upload';
9
+ import { ImageUpload, ImageUploadDragger, Upload, UploadDragger } from '../upload';
10
+
11
+ function ProFormUpload(props: ProFormItemProps<UploadProps>) {
12
+ const { fieldProps, ...rest } = props;
13
+
14
+ return (
15
+ <ProForm.Item {...rest}>
16
+ <Upload {...fieldProps} />
17
+ </ProForm.Item>
18
+ );
19
+ }
20
+
21
+ function ProFormUploadDragger(props: ProFormItemProps<UploadDraggerProps>) {
22
+ const { fieldProps, ...rest } = props;
23
+ return (
24
+ <ProForm.Item {...rest}>
25
+ <UploadDragger {...fieldProps} />
26
+ </ProForm.Item>
27
+ );
28
+ }
29
+
30
+ function ProFormImageUpload(props: ProFormItemProps<ImageUploadProps>) {
31
+ const { fieldProps, ...rest } = props;
32
+
33
+ return (
34
+ <ProForm.Item {...rest}>
35
+ <ImageUpload {...fieldProps} />
36
+ </ProForm.Item>
37
+ );
38
+ }
39
+
40
+ function ProFormImageUploadDragger(props: ProFormItemProps<ImageUploadDraggerProps>) {
41
+ const { fieldProps, ...rest } = props;
42
+
43
+ return (
44
+ <ProForm.Item {...rest}>
45
+ <ImageUploadDragger {...fieldProps} />
46
+ </ProForm.Item>
47
+ );
48
+ }
49
+
50
+ export { ProFormImageUpload, ProFormImageUploadDragger, ProFormUpload, ProFormUploadDragger };
package/src/index.ts CHANGED
@@ -13,6 +13,8 @@ export { EditorMention } from './editor_mention';
13
13
  export type { EditorMentionProps } from './editor_mention';
14
14
  export {
15
15
  ProFormEditor,
16
+ ProFormImageUpload,
17
+ ProFormImageUploadDragger,
16
18
  ProFormJSON,
17
19
  ProFormJavascript,
18
20
  ProFormListHelper,
@@ -20,6 +22,8 @@ export {
20
22
  ProFormListNumber,
21
23
  ProFormListText,
22
24
  ProFormSwitchNumber,
25
+ ProFormUpload,
26
+ ProFormUploadDragger,
23
27
  proFormSelectSearchProps,
24
28
  } from './form';
25
29
  export { Markdown } from './markdown';
@@ -0,0 +1,173 @@
1
+ // 避免循环引用
2
+ import { InboxOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
3
+ import type { UploadProps as AntdUploadProps, UploadFile } from 'antd';
4
+ import { Upload as AntdUpload, Button, message } from 'antd';
5
+ import classNames from 'classnames';
6
+ import { useCallback, useMemo, useState } from 'react';
7
+
8
+ interface UploadBaseProps {
9
+ value?: string[] | string;
10
+ onChange?: (value?: string[] | string) => void;
11
+ multiple?: boolean;
12
+ maxCount?: number;
13
+ action?: string;
14
+ customRequest?: AntdUploadProps['customRequest'];
15
+ listType?: AntdUploadProps['listType'];
16
+ accept?: string;
17
+ }
18
+
19
+ interface UploadProps extends UploadBaseProps {
20
+ showCount?: boolean;
21
+ }
22
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
23
+ interface UploadDraggerProps extends UploadBaseProps {}
24
+
25
+ function useUpload(props: ImageUploadProps) {
26
+ const { value, onChange, multiple, maxCount } = props;
27
+ // 转换成 Upload 格式。
28
+ const defaultFileList = useMemo(() => {
29
+ if (!value) {
30
+ return [];
31
+ }
32
+
33
+ const arr = (multiple ? value : [value]) as string[];
34
+ return arr.map((url) => ({ uid: url, url, name: url?.split('/').pop() || '' })) as UploadFile[];
35
+ }, [multiple, value]);
36
+
37
+ // 存起来,已选的文件。以便做一些判断。
38
+ const [fileList, setFileList] = useState<UploadFile[]>(defaultFileList);
39
+
40
+ const handleChange = useCallback(
41
+ (info) => {
42
+ setFileList(info.fileList);
43
+
44
+ // 找到真正上传成功的。
45
+ const newValue = info.fileList
46
+ .map((item) => item.url || item.response?.data.url)
47
+ .filter(Boolean);
48
+
49
+ onChange?.(multiple ? newValue : newValue[0]);
50
+ },
51
+ [multiple, onChange],
52
+ );
53
+
54
+ // 选文件还是可能多选,如果多选,则提示。
55
+ const handleBeforeUpload = useCallback(
56
+ (f, fl) => {
57
+ // 多选 >1 情况下,超出的则提示。
58
+ if (multiple && maxCount && maxCount > 1) {
59
+ const index = fl.findIndex((item) => item.uid === f.uid);
60
+ if (index >= maxCount - fileList.length) {
61
+ message.warning(`最多只能上传 ${maxCount} 个文件,超出部分会忽略。`);
62
+ }
63
+ }
64
+
65
+ return true;
66
+ },
67
+ [fileList, multiple, maxCount],
68
+ );
69
+
70
+ // 多选情况下,超出则上传按钮 disabled
71
+ const isDisabled = useMemo(() => {
72
+ if (multiple && maxCount && maxCount > 1) {
73
+ return fileList.length >= maxCount;
74
+ }
75
+ return false;
76
+ }, [fileList.length, maxCount, multiple]);
77
+
78
+ return {
79
+ onChange: handleChange,
80
+ beforeUpload: handleBeforeUpload,
81
+ isDisabled,
82
+ fileList,
83
+ };
84
+ }
85
+
86
+ function Upload(props: ImageUploadProps) {
87
+ const { multiple, maxCount, showCount, action, customRequest, listType, accept } = props;
88
+ const { onChange, beforeUpload, isDisabled, fileList } = useUpload(props);
89
+
90
+ return (
91
+ <AntdUpload
92
+ action={action}
93
+ customRequest={customRequest}
94
+ onChange={onChange}
95
+ accept={accept}
96
+ listType={listType}
97
+ defaultFileList={fileList}
98
+ maxCount={multiple ? maxCount : 1}
99
+ multiple={multiple}
100
+ beforeUpload={beforeUpload}
101
+ // 不可,否则会没法删除
102
+ // disabled={isDisabled}
103
+ >
104
+ {listType === 'picture-card' ? (
105
+ <button style={{ border: 0, background: 'none' }} type="button" disabled={isDisabled}>
106
+ <PlusOutlined />
107
+ <div style={{ marginTop: 8 }}>本地上传</div>
108
+ </button>
109
+ ) : (
110
+ <Button icon={<UploadOutlined />} disabled={isDisabled}>
111
+ 本地上传{showCount && multiple ? `(${fileList.length}/${maxCount})` : ''}
112
+ </Button>
113
+ )}
114
+ </AntdUpload>
115
+ );
116
+ }
117
+
118
+ function UploadDragger(props: ImageUploadDraggerProps) {
119
+ const { multiple, maxCount, action, customRequest, listType, accept } = props;
120
+ const { onChange, beforeUpload, isDisabled, fileList } = useUpload(props);
121
+
122
+ return (
123
+ <AntdUpload.Dragger
124
+ action={action}
125
+ customRequest={customRequest}
126
+ onChange={onChange}
127
+ accept={accept}
128
+ listType={listType}
129
+ defaultFileList={fileList}
130
+ maxCount={multiple ? maxCount : 1}
131
+ multiple={multiple}
132
+ beforeUpload={beforeUpload}
133
+ // 不可,否则会没法删除
134
+ // disabled={isDisabled}
135
+ >
136
+ <div
137
+ className={classNames({
138
+ 'cursor-not-allowed': isDisabled,
139
+ })}
140
+ >
141
+ <p className={classNames('ant-upload-drag-icon')}>
142
+ <InboxOutlined
143
+ className={classNames({
144
+ '!text-desc': isDisabled,
145
+ })}
146
+ />
147
+ </p>
148
+ <p
149
+ className={classNames('ant-upload-text', {
150
+ '!text-desc': isDisabled,
151
+ })}
152
+ >
153
+ 点击或拖拽到此区域进行上传
154
+ </p>
155
+ </div>
156
+ </AntdUpload.Dragger>
157
+ );
158
+ }
159
+
160
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
161
+ interface ImageUploadProps extends UploadProps {}
162
+ function ImageUpload(props: ImageUploadProps) {
163
+ return <Upload {...props} accept="image/*" listType="picture" />;
164
+ }
165
+
166
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
167
+ interface ImageUploadDraggerProps extends UploadProps {}
168
+ function ImageUploadDragger(props: ImageUploadDraggerProps) {
169
+ return <UploadDragger {...props} accept="image/*" listType="picture" />;
170
+ }
171
+
172
+ export { ImageUpload, ImageUploadDragger, Upload, UploadDragger };
173
+ export type { ImageUploadDraggerProps, ImageUploadProps, UploadDraggerProps, UploadProps };