Upload上传

文件选择上传和拖拽上传控件。

何时使用

上传是将信息(网页、文字、图片、视频等)通过网页或者上传工具发布到远程服务器上的过程。

  • 当需要上传一个或一些文件时。

  • 当需要展现上传的进度时。

  • 当需要使用拖拽交互时。

代码演示

Upload上传 - 图1

点击上传

经典款式,用户点击按钮弹出文件选择框。

  1. import { Upload, message, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. const props = {
  4. name: 'file',
  5. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  6. headers: {
  7. authorization: 'authorization-text',
  8. },
  9. onChange(info) {
  10. if (info.file.status !== 'uploading') {
  11. console.log(info.file, info.fileList);
  12. }
  13. if (info.file.status === 'done') {
  14. message.success(`${info.file.name} file uploaded successfully`);
  15. } else if (info.file.status === 'error') {
  16. message.error(`${info.file.name} file upload failed.`);
  17. }
  18. },
  19. };
  20. ReactDOM.render(
  21. <Upload {...props}>
  22. <Button icon={<UploadOutlined />}>Click to Upload</Button>
  23. </Upload>,
  24. mountNode,
  25. );

Upload上传 - 图2

已上传的文件列表

使用 defaultFileList 设置已上传的内容。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. const props = {
  4. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  5. onChange({ file, fileList }) {
  6. if (file.status !== 'uploading') {
  7. console.log(file, fileList);
  8. }
  9. },
  10. defaultFileList: [
  11. {
  12. uid: '1',
  13. name: 'xxx.png',
  14. status: 'done',
  15. response: 'Server Error 500', // custom error message to show
  16. url: 'http://www.baidu.com/xxx.png',
  17. },
  18. {
  19. uid: '2',
  20. name: 'yyy.png',
  21. status: 'done',
  22. url: 'http://www.baidu.com/yyy.png',
  23. },
  24. {
  25. uid: '3',
  26. name: 'zzz.png',
  27. status: 'error',
  28. response: 'Server Error 500', // custom error message to show
  29. url: 'http://www.baidu.com/zzz.png',
  30. },
  31. ],
  32. };
  33. ReactDOM.render(
  34. <Upload {...props}>
  35. <Button icon={<UploadOutlined />}>Upload</Button>
  36. </Upload>,
  37. mountNode,
  38. );

Upload上传 - 图3

完全控制的上传列表

使用 fileList 对列表进行完全控制,可以实现各种自定义功能,以下演示二种情况:

  1. 上传列表数量的限制。

  2. 读取远程路径并显示链接。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. class MyUpload extends React.Component {
  4. state = {
  5. fileList: [
  6. {
  7. uid: '-1',
  8. name: 'xxx.png',
  9. status: 'done',
  10. url: 'http://www.baidu.com/xxx.png',
  11. },
  12. ],
  13. };
  14. handleChange = info => {
  15. let fileList = [...info.fileList];
  16. // 1. Limit the number of uploaded files
  17. // Only to show two recent uploaded files, and old ones will be replaced by the new
  18. fileList = fileList.slice(-2);
  19. // 2. Read from response and show file link
  20. fileList = fileList.map(file => {
  21. if (file.response) {
  22. // Component will show file.url as link
  23. file.url = file.response.url;
  24. }
  25. return file;
  26. });
  27. this.setState({ fileList });
  28. };
  29. render() {
  30. const props = {
  31. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  32. onChange: this.handleChange,
  33. multiple: true,
  34. };
  35. return (
  36. <Upload {...props} fileList={this.state.fileList}>
  37. <Button icon={<UploadOutlined />}>Upload</Button>
  38. </Upload>
  39. );
  40. }
  41. }
  42. ReactDOM.render(<MyUpload />, mountNode);

Upload上传 - 图4

文件夹上传

支持上传一个文件夹里的所有文件。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. ReactDOM.render(
  4. <Upload action="https://www.mocky.io/v2/5cc8019d300000980a055e76" directory>
  5. <Button icon={<UploadOutlined />}>Upload Directory</Button>
  6. </Upload>,
  7. mountNode,
  8. );

Upload上传 - 图5

只上传 png 图片

beforeUpload 返回 falsePromise.reject 时,只用于拦截上传行为,不会阻止文件进入上传列表(原因)。如果需要阻止列表展现,可以通过返回 Upload.LIST_IGNORE 实现。

  1. import React, { useState } from 'react';
  2. import { Upload, Button, message } from 'antd';
  3. import { UploadOutlined } from '@ant-design/icons';
  4. const Uploader = () => {
  5. const props = {
  6. beforeUpload: file => {
  7. if (file.type !== 'image/png') {
  8. message.error(`${file.name} is not a png file`);
  9. }
  10. return file.type === 'image/png' ? true : Upload.LIST_IGNORE;
  11. },
  12. onChange: info => {
  13. console.log(info.fileList);
  14. },
  15. };
  16. return (
  17. <Upload {...props}>
  18. <Button icon={<UploadOutlined />}>Upload png only</Button>
  19. </Upload>
  20. );
  21. };
  22. ReactDOM.render(<Uploader />, mountNode);

Upload上传 - 图6

自定义预览

自定义本地预览,用于处理非图片格式文件(例如视频文件)。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. const props = {
  4. action: '//jsonplaceholder.typicode.com/posts/',
  5. listType: 'picture',
  6. previewFile(file) {
  7. console.log('Your upload file:', file);
  8. // Your process logic. Here we just mock to the same file
  9. return fetch('https://next.json-generator.com/api/json/get/4ytyBoLK8', {
  10. method: 'POST',
  11. body: file,
  12. })
  13. .then(res => res.json())
  14. .then(({ thumbnail }) => thumbnail);
  15. },
  16. };
  17. ReactDOM.render(
  18. <Upload {...props}>
  19. <Button icon={<UploadOutlined />}>Upload</Button>
  20. </Upload>,
  21. mountNode,
  22. );

Upload上传 - 图7

上传前转换文件

使用 beforeUpload 转换上传的文件(例如添加水印)。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. const props = {
  4. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  5. listType: 'picture',
  6. beforeUpload(file) {
  7. return new Promise(resolve => {
  8. const reader = new FileReader();
  9. reader.readAsDataURL(file);
  10. reader.onload = () => {
  11. const img = document.createElement('img');
  12. img.src = reader.result;
  13. img.onload = () => {
  14. const canvas = document.createElement('canvas');
  15. canvas.width = img.naturalWidth;
  16. canvas.height = img.naturalHeight;
  17. const ctx = canvas.getContext('2d');
  18. ctx.drawImage(img, 0, 0);
  19. ctx.fillStyle = 'red';
  20. ctx.textBaseline = 'middle';
  21. ctx.font = '33px Arial';
  22. ctx.fillText('Ant Design', 20, 20);
  23. canvas.toBlob(resolve);
  24. };
  25. };
  26. });
  27. },
  28. };
  29. ReactDOM.render(
  30. <>
  31. <Upload {...props}>
  32. <Button icon={<UploadOutlined />}>Upload</Button>
  33. </Upload>
  34. </>,
  35. mountNode,
  36. );

Upload上传 - 图8

自定义交互图标

使用 showUploadList 设置列表交互图标。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined, StarOutlined } from '@ant-design/icons';
  3. const props = {
  4. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  5. onChange({ file, fileList }) {
  6. if (file.status !== 'uploading') {
  7. console.log(file, fileList);
  8. }
  9. },
  10. defaultFileList: [
  11. {
  12. uid: '1',
  13. name: 'xxx.png',
  14. status: 'done',
  15. response: 'Server Error 500', // custom error message to show
  16. url: 'http://www.baidu.com/xxx.png',
  17. },
  18. {
  19. uid: '2',
  20. name: 'yyy.png',
  21. status: 'done',
  22. url: 'http://www.baidu.com/yyy.png',
  23. },
  24. {
  25. uid: '3',
  26. name: 'zzz.png',
  27. status: 'error',
  28. response: 'Server Error 500', // custom error message to show
  29. url: 'http://www.baidu.com/zzz.png',
  30. },
  31. ],
  32. showUploadList: {
  33. showDownloadIcon: true,
  34. downloadIcon: 'download ',
  35. showRemoveIcon: true,
  36. removeIcon: <StarOutlined onClick={e => console.log(e, 'custom removeIcon event')} />,
  37. },
  38. };
  39. ReactDOM.render(
  40. <Upload {...props}>
  41. <Button icon={<UploadOutlined />}>Upload</Button>
  42. </Upload>,
  43. mountNode,
  44. );

Upload上传 - 图9

上传前裁切图片

配合 antd-img-crop 实现上传前裁切图片。

  1. import React, { useState } from 'react';
  2. import { Upload } from 'antd';
  3. import ImgCrop from 'antd-img-crop';
  4. const Demo = () => {
  5. const [fileList, setFileList] = useState([
  6. {
  7. uid: '-1',
  8. name: 'image.png',
  9. status: 'done',
  10. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  11. },
  12. ]);
  13. const onChange = ({ fileList: newFileList }) => {
  14. setFileList(newFileList);
  15. };
  16. const onPreview = async file => {
  17. let src = file.url;
  18. if (!src) {
  19. src = await new Promise(resolve => {
  20. const reader = new FileReader();
  21. reader.readAsDataURL(file.originFileObj);
  22. reader.onload = () => resolve(reader.result);
  23. });
  24. }
  25. const image = new Image();
  26. image.src = src;
  27. const imgWindow = window.open(src);
  28. imgWindow.document.write(image.outerHTML);
  29. };
  30. return (
  31. <ImgCrop rotate>
  32. <Upload
  33. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  34. listType="picture-card"
  35. fileList={fileList}
  36. onChange={onChange}
  37. onPreview={onPreview}
  38. >
  39. {fileList.length < 5 && '+ Upload'}
  40. </Upload>
  41. </ImgCrop>
  42. );
  43. };
  44. ReactDOM.render(<Demo />, mountNode);

Upload上传 - 图10

用户头像

点击上传用户头像,并使用 beforeUpload 限制用户上传的图片格式和大小。

beforeUpload 的返回值可以是一个 Promise 以支持异步处理,如服务端校验等:示例

  1. import { Upload, message } from 'antd';
  2. import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
  3. function getBase64(img, callback) {
  4. const reader = new FileReader();
  5. reader.addEventListener('load', () => callback(reader.result));
  6. reader.readAsDataURL(img);
  7. }
  8. function beforeUpload(file) {
  9. const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  10. if (!isJpgOrPng) {
  11. message.error('You can only upload JPG/PNG file!');
  12. }
  13. const isLt2M = file.size / 1024 / 1024 < 2;
  14. if (!isLt2M) {
  15. message.error('Image must smaller than 2MB!');
  16. }
  17. return isJpgOrPng && isLt2M;
  18. }
  19. class Avatar extends React.Component {
  20. state = {
  21. loading: false,
  22. };
  23. handleChange = info => {
  24. if (info.file.status === 'uploading') {
  25. this.setState({ loading: true });
  26. return;
  27. }
  28. if (info.file.status === 'done') {
  29. // Get this url from response in real world.
  30. getBase64(info.file.originFileObj, imageUrl =>
  31. this.setState({
  32. imageUrl,
  33. loading: false,
  34. }),
  35. );
  36. }
  37. };
  38. render() {
  39. const { loading, imageUrl } = this.state;
  40. const uploadButton = (
  41. <div>
  42. {loading ? <LoadingOutlined /> : <PlusOutlined />}
  43. <div style={{ marginTop: 8 }}>Upload</div>
  44. </div>
  45. );
  46. return (
  47. <Upload
  48. name="avatar"
  49. listType="picture-card"
  50. className="avatar-uploader"
  51. showUploadList={false}
  52. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  53. beforeUpload={beforeUpload}
  54. onChange={this.handleChange}
  55. >
  56. {imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : uploadButton}
  57. </Upload>
  58. );
  59. }
  60. }
  61. ReactDOM.render(<Avatar />, mountNode);
  1. .avatar-uploader > .ant-upload {
  2. width: 128px;
  3. height: 128px;
  4. }

Upload上传 - 图11

照片墙

用户可以上传图片并在列表中显示缩略图。当上传照片数到达限制后,上传按钮消失。

  1. import { Upload, Modal } from 'antd';
  2. import { PlusOutlined } from '@ant-design/icons';
  3. function getBase64(file) {
  4. return new Promise((resolve, reject) => {
  5. const reader = new FileReader();
  6. reader.readAsDataURL(file);
  7. reader.onload = () => resolve(reader.result);
  8. reader.onerror = error => reject(error);
  9. });
  10. }
  11. class PicturesWall extends React.Component {
  12. state = {
  13. previewVisible: false,
  14. previewImage: '',
  15. previewTitle: '',
  16. fileList: [
  17. {
  18. uid: '-1',
  19. name: 'image.png',
  20. status: 'done',
  21. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  22. },
  23. {
  24. uid: '-2',
  25. name: 'image.png',
  26. status: 'done',
  27. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  28. },
  29. {
  30. uid: '-3',
  31. name: 'image.png',
  32. status: 'done',
  33. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  34. },
  35. {
  36. uid: '-4',
  37. name: 'image.png',
  38. status: 'done',
  39. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  40. },
  41. {
  42. uid: '-xxx',
  43. percent: 50,
  44. name: 'image.png',
  45. status: 'uploading',
  46. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  47. },
  48. {
  49. uid: '-5',
  50. name: 'image.png',
  51. status: 'error',
  52. },
  53. ],
  54. };
  55. handleCancel = () => this.setState({ previewVisible: false });
  56. handlePreview = async file => {
  57. if (!file.url && !file.preview) {
  58. file.preview = await getBase64(file.originFileObj);
  59. }
  60. this.setState({
  61. previewImage: file.url || file.preview,
  62. previewVisible: true,
  63. previewTitle: file.name || file.url.substring(file.url.lastIndexOf('/') + 1),
  64. });
  65. };
  66. handleChange = ({ fileList }) => this.setState({ fileList });
  67. render() {
  68. const { previewVisible, previewImage, fileList, previewTitle } = this.state;
  69. const uploadButton = (
  70. <div>
  71. <PlusOutlined />
  72. <div style={{ marginTop: 8 }}>Upload</div>
  73. </div>
  74. );
  75. return (
  76. <>
  77. <Upload
  78. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  79. listType="picture-card"
  80. fileList={fileList}
  81. onPreview={this.handlePreview}
  82. onChange={this.handleChange}
  83. >
  84. {fileList.length >= 8 ? null : uploadButton}
  85. </Upload>
  86. <Modal
  87. visible={previewVisible}
  88. title={previewTitle}
  89. footer={null}
  90. onCancel={this.handleCancel}
  91. >
  92. <img alt="example" style={{ width: '100%' }} src={previewImage} />
  93. </Modal>
  94. </>
  95. );
  96. }
  97. }
  98. ReactDOM.render(<PicturesWall />, mountNode);

Upload上传 - 图12

拖拽上传

把文件拖入指定区域,完成上传,同样支持点击上传。

设置 multiple 后,在 IE10+ 可以一次上传多个文件。

  1. import { Upload, message } from 'antd';
  2. import { InboxOutlined } from '@ant-design/icons';
  3. const { Dragger } = Upload;
  4. const props = {
  5. name: 'file',
  6. multiple: true,
  7. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  8. onChange(info) {
  9. const { status } = info.file;
  10. if (status !== 'uploading') {
  11. console.log(info.file, info.fileList);
  12. }
  13. if (status === 'done') {
  14. message.success(`${info.file.name} file uploaded successfully.`);
  15. } else if (status === 'error') {
  16. message.error(`${info.file.name} file upload failed.`);
  17. }
  18. },
  19. onDrop(e) {
  20. console.log('Dropped files', e.dataTransfer.files);
  21. },
  22. };
  23. ReactDOM.render(
  24. <Dragger {...props}>
  25. <p className="ant-upload-drag-icon">
  26. <InboxOutlined />
  27. </p>
  28. <p className="ant-upload-text">Click or drag file to this area to upload</p>
  29. <p className="ant-upload-hint">
  30. Support for a single or bulk upload. Strictly prohibit from uploading company data or other
  31. band files
  32. </p>
  33. </Dragger>,
  34. mountNode,
  35. );

Upload上传 - 图13

手动上传

beforeUpload 返回 false 后,手动上传文件。

  1. import { Upload, Button, message } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. import reqwest from 'reqwest';
  4. class Demo extends React.Component {
  5. state = {
  6. fileList: [],
  7. uploading: false,
  8. };
  9. handleUpload = () => {
  10. const { fileList } = this.state;
  11. const formData = new FormData();
  12. fileList.forEach(file => {
  13. formData.append('files[]', file);
  14. });
  15. this.setState({
  16. uploading: true,
  17. });
  18. // You can use any AJAX library you like
  19. reqwest({
  20. url: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  21. method: 'post',
  22. processData: false,
  23. data: formData,
  24. success: () => {
  25. this.setState({
  26. fileList: [],
  27. uploading: false,
  28. });
  29. message.success('upload successfully.');
  30. },
  31. error: () => {
  32. this.setState({
  33. uploading: false,
  34. });
  35. message.error('upload failed.');
  36. },
  37. });
  38. };
  39. render() {
  40. const { uploading, fileList } = this.state;
  41. const props = {
  42. onRemove: file => {
  43. this.setState(state => {
  44. const index = state.fileList.indexOf(file);
  45. const newFileList = state.fileList.slice();
  46. newFileList.splice(index, 1);
  47. return {
  48. fileList: newFileList,
  49. };
  50. });
  51. },
  52. beforeUpload: file => {
  53. this.setState(state => ({
  54. fileList: [...state.fileList, file],
  55. }));
  56. return false;
  57. },
  58. fileList,
  59. };
  60. return (
  61. <>
  62. <Upload {...props}>
  63. <Button icon={<UploadOutlined />}>Select File</Button>
  64. </Upload>
  65. <Button
  66. type="primary"
  67. onClick={this.handleUpload}
  68. disabled={fileList.length === 0}
  69. loading={uploading}
  70. style={{ marginTop: 16 }}
  71. >
  72. {uploading ? 'Uploading' : 'Start Upload'}
  73. </Button>
  74. </>
  75. );
  76. }
  77. }
  78. ReactDOM.render(<Demo />, mountNode);

Upload上传 - 图14

图片列表样式

上传文件为图片,可展示本地缩略图。IE8/9 不支持浏览器本地缩略图展示(Ref),可以写 thumbUrl 属性来代替。

  1. import { Upload, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. const fileList = [
  4. {
  5. uid: '-1',
  6. name: 'xxx.png',
  7. status: 'done',
  8. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  9. thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  10. },
  11. {
  12. uid: '-2',
  13. name: 'yyy.png',
  14. status: 'error',
  15. },
  16. ];
  17. ReactDOM.render(
  18. <>
  19. <Upload
  20. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  21. listType="picture"
  22. defaultFileList={[...fileList]}
  23. >
  24. <Button icon={<UploadOutlined />}>Upload</Button>
  25. </Upload>
  26. <br />
  27. <br />
  28. <Upload
  29. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  30. listType="picture"
  31. defaultFileList={[...fileList]}
  32. className="upload-list-inline"
  33. >
  34. <Button icon={<UploadOutlined />}>Upload</Button>
  35. </Upload>
  36. </>,
  37. mountNode,
  38. );
  1. /* tile uploaded pictures */
  2. .upload-list-inline .ant-upload-list-item {
  3. float: left;
  4. width: 200px;
  5. margin-right: 8px;
  6. }
  7. .upload-list-inline [class*='-upload-list-rtl'] .ant-upload-list-item {
  8. float: right;
  9. }

Upload上传 - 图15

限制数量

通过 maxCount 限制上传数量。当为 1 时,始终用最新上传的代替当前。

  1. import { Upload, Button, Space } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. ReactDOM.render(
  4. <Space direction="vertical" style={{ width: '100%' }} size="large">
  5. <Upload
  6. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  7. listType="picture"
  8. maxCount={1}
  9. >
  10. <Button icon={<UploadOutlined />}>Upload (Max: 1)</Button>
  11. </Upload>
  12. <Upload
  13. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  14. listType="picture"
  15. maxCount={3}
  16. multiple
  17. >
  18. <Button icon={<UploadOutlined />}>Upload (Max: 3)</Button>
  19. </Upload>
  20. </Space>,
  21. mountNode,
  22. );

Upload上传 - 图16

阿里云 OSS

使用阿里云 OSS 上传示例.

  1. import { Form, Upload, message, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. class AliyunOSSUpload extends React.Component {
  4. state = {
  5. OSSData: {},
  6. };
  7. async componentDidMount() {
  8. await this.init();
  9. }
  10. init = async () => {
  11. try {
  12. const OSSData = await this.mockGetOSSData();
  13. this.setState({
  14. OSSData,
  15. });
  16. } catch (error) {
  17. message.error(error);
  18. }
  19. };
  20. // Mock get OSS api
  21. // https://help.aliyun.com/document_detail/31988.html
  22. mockGetOSSData = () => ({
  23. dir: 'user-dir/',
  24. expire: '1577811661',
  25. host: '//www.mocky.io/v2/5cc8019d300000980a055e76',
  26. accessId: 'c2hhb2RhaG9uZw==',
  27. policy: 'eGl4aWhhaGFrdWt1ZGFkYQ==',
  28. signature: 'ZGFob25nc2hhbw==',
  29. });
  30. onChange = ({ fileList }) => {
  31. const { onChange } = this.props;
  32. console.log('Aliyun OSS:', fileList);
  33. if (onChange) {
  34. onChange([...fileList]);
  35. }
  36. };
  37. onRemove = file => {
  38. const { value, onChange } = this.props;
  39. const files = value.filter(v => v.url !== file.url);
  40. if (onChange) {
  41. onChange(files);
  42. }
  43. };
  44. getExtraData = file => {
  45. const { OSSData } = this.state;
  46. return {
  47. key: file.url,
  48. OSSAccessKeyId: OSSData.accessId,
  49. policy: OSSData.policy,
  50. Signature: OSSData.signature,
  51. };
  52. };
  53. beforeUpload = async file => {
  54. const { OSSData } = this.state;
  55. const expire = OSSData.expire * 1000;
  56. if (expire < Date.now()) {
  57. await this.init();
  58. }
  59. const suffix = file.name.slice(file.name.lastIndexOf('.'));
  60. const filename = Date.now() + suffix;
  61. file.url = OSSData.dir + filename;
  62. return file;
  63. };
  64. render() {
  65. const { value } = this.props;
  66. const props = {
  67. name: 'file',
  68. fileList: value,
  69. action: this.state.OSSData.host,
  70. onChange: this.onChange,
  71. onRemove: this.onRemove,
  72. data: this.getExtraData,
  73. beforeUpload: this.beforeUpload,
  74. };
  75. return (
  76. <Upload {...props}>
  77. <Button icon={<UploadOutlined />}>Click to Upload</Button>
  78. </Upload>
  79. );
  80. }
  81. }
  82. const FormPage = () => (
  83. <Form labelCol={{ span: 4 }}>
  84. <Form.Item label="Photos" name="photos">
  85. <AliyunOSSUpload />
  86. </Form.Item>
  87. </Form>
  88. );
  89. ReactDOM.render(<FormPage />, mountNode);

Upload上传 - 图17

上传列表拖拽排序

使用 itemRender ,我们可以集成 react-dnd 来实现对上传列表拖拽排序。

  1. import React, { useState, useCallback, useRef } from 'react';
  2. import { Upload, Button, Tooltip } from 'antd';
  3. import { DndProvider, useDrag, useDrop } from 'react-dnd';
  4. import { HTML5Backend } from 'react-dnd-html5-backend';
  5. import update from 'immutability-helper';
  6. import { UploadOutlined } from '@ant-design/icons';
  7. const type = 'DragableUploadList';
  8. const DragableUploadListItem = ({ originNode, moveRow, file, fileList }) => {
  9. const ref = React.useRef();
  10. const index = fileList.indexOf(file);
  11. const [{ isOver, dropClassName }, drop] = useDrop({
  12. accept: type,
  13. collect: monitor => {
  14. const { index: dragIndex } = monitor.getItem() || {};
  15. if (dragIndex === index) {
  16. return {};
  17. }
  18. return {
  19. isOver: monitor.isOver(),
  20. dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
  21. };
  22. },
  23. drop: item => {
  24. moveRow(item.index, index);
  25. },
  26. });
  27. const [, drag] = useDrag({
  28. type,
  29. item: { index },
  30. collect: monitor => ({
  31. isDragging: monitor.isDragging(),
  32. }),
  33. });
  34. drop(drag(ref));
  35. const errorNode = <Tooltip title="Upload Error">{originNode.props.children}</Tooltip>;
  36. return (
  37. <div
  38. ref={ref}
  39. className={`ant-upload-draggable-list-item ${isOver ? dropClassName : ''}`}
  40. style={{ cursor: 'move' }}
  41. >
  42. {file.status === 'error' ? errorNode : originNode}
  43. </div>
  44. );
  45. };
  46. const DragSortingUpload: React.FC = () => {
  47. const [fileList, setFileList] = useState([
  48. {
  49. uid: '-1',
  50. name: 'image1.png',
  51. status: 'done',
  52. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  53. },
  54. {
  55. uid: '-2',
  56. name: 'image2.png',
  57. status: 'done',
  58. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  59. },
  60. {
  61. uid: '-3',
  62. name: 'image3.png',
  63. status: 'done',
  64. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  65. },
  66. {
  67. uid: '-4',
  68. name: 'image4.png',
  69. status: 'done',
  70. url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  71. },
  72. {
  73. uid: '-5',
  74. name: 'image.png',
  75. status: 'error',
  76. },
  77. ]);
  78. const moveRow = useCallback(
  79. (dragIndex, hoverIndex) => {
  80. const dragRow = fileList[dragIndex];
  81. setFileList(
  82. update(fileList, {
  83. $splice: [
  84. [dragIndex, 1],
  85. [hoverIndex, 0, dragRow],
  86. ],
  87. }),
  88. );
  89. },
  90. [fileList],
  91. );
  92. const onChange = ({ fileList: newFileList }) => {
  93. setFileList(newFileList);
  94. };
  95. return (
  96. <DndProvider backend={HTML5Backend}>
  97. <Upload
  98. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  99. fileList={fileList}
  100. onChange={onChange}
  101. itemRender={(originNode, file, currFileList) => (
  102. <DragableUploadListItem
  103. originNode={originNode}
  104. file={file}
  105. fileList={currFileList}
  106. moveRow={moveRow}
  107. />
  108. )}
  109. >
  110. <Button icon={<UploadOutlined />}>Click to Upload</Button>
  111. </Upload>
  112. </DndProvider>
  113. );
  114. };
  115. ReactDOM.render(<DragSortingUpload />, mountNode);
  1. #components-upload-demo-drag-sorting .ant-upload-draggable-list-item {
  2. border-top: 2px dashed rgba(0, 0, 0, 0);
  3. border-bottom: 2px dashed rgba(0, 0, 0, 0);
  4. }
  5. #components-upload-demo-drag-sorting .ant-upload-draggable-list-item.drop-over-downward {
  6. border-bottom-color: #1890ff;
  7. }
  8. #components-upload-demo-drag-sorting .ant-upload-draggable-list-item.drop-over-upward {
  9. border-top-color: #1890ff;
  10. }

Upload上传 - 图18

自定义进度条样式

使用 progress 属性自定义进度条样式。

  1. import { Upload, message, Button } from 'antd';
  2. import { UploadOutlined } from '@ant-design/icons';
  3. const props = {
  4. name: 'file',
  5. action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  6. headers: {
  7. authorization: 'authorization-text',
  8. },
  9. onChange(info) {
  10. if (info.file.status !== 'uploading') {
  11. console.log(info.file, info.fileList);
  12. }
  13. if (info.file.status === 'done') {
  14. message.success(`${info.file.name} file uploaded successfully`);
  15. } else if (info.file.status === 'error') {
  16. message.error(`${info.file.name} file upload failed.`);
  17. }
  18. },
  19. progress: {
  20. strokeColor: {
  21. '0%': '#108ee9',
  22. '100%': '#87d068',
  23. },
  24. strokeWidth: 3,
  25. format: percent => `${parseFloat(percent.toFixed(2))}%`,
  26. },
  27. };
  28. ReactDOM.render(
  29. <Upload {...props}>
  30. <Button icon={<UploadOutlined />}>Click to Upload</Button>
  31. </Upload>,
  32. mountNode,
  33. );

API

参数说明类型默认值版本
accept接受上传的文件类型, 详见 input accept Attributestring-
action上传的地址string | (file) => Promise<string>-
beforeUpload上传文件之前的钩子,参数为上传的文件,若返回 false 则停止上传。支持返回一个 Promise 对象,Promise 对象 reject 时则停止上传,resolve 时开始上传( resolve 传入 FileBlob 对象则上传 resolve 传入对象);也可以返回 Upload.LIST_IGNORE,此时列表中将不展示此文件。 注意:IE9 不支持该方法(file, fileList) => boolean | Promise<File> | Upload.LIST_IGNORE-
customRequest通过覆盖默认的上传行为,可以自定义自己的上传实现function-
data上传所需额外参数或返回上传额外参数的方法object|(file) => object | Promise<object>-
defaultFileList默认已经上传的文件列表object[]-
directory支持上传文件夹(caniusebooleanfalse
disabled是否禁用booleanfalse
fileList已经上传的文件列表(受控),使用此参数时,如果遇到 onChange 只调用一次的问题,请参考 #2423UploadFile[]-
headers设置上传的请求头部,IE10 以上有效object-
iconRender自定义显示 icon(file: UploadFile, listType?: UploadListType) => ReactNode-
isImageUrl自定义缩略图是否使用 <img /> 标签进行显示(file: UploadFile) => boolean(内部实现)
itemRender自定义上传列表项(originNode: ReactElement, file: UploadFile, fileList: object[], actions: { download: function, preview: function, remove: function }) => React.ReactNode-4.16.0
listType上传列表的内建样式,支持三种基本样式 text, picturepicture-cardstringtext
maxCount限制上传数量。当为 1 时,始终用最新上传的文件代替当前文件number-4.10.0
method上传请求的 http methodstringpost
multiple是否支持多选文件,ie10+ 支持。开启后按住 ctrl 可选择多个文件booleanfalse
name发到后台的文件参数名stringfile
openFileDialogOnClick点击打开文件对话框booleantrue
previewFile自定义文件预览逻辑(file: File | Blob) => Promise<dataURL: string>-
progress自定义进度条样式ProgressProps(仅支持 type=”line”{ strokeWidth: 2, showInfo: false }4.3.0
showUploadList是否展示文件列表, 可设为一个对象,用于单独设定 showPreviewIcon, showRemoveIcon, showDownloadIcon, removeIcondownloadIconboolean | { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, removeIcon?: ReactNode | (file: UploadFile) => ReactNode, downloadIcon?: ReactNode | (file: UploadFile) => ReactNode }truefunction: 4.7.0
withCredentials上传请求时是否携带 cookiebooleanfalse
onChange上传文件改变时的状态,详见 onChangefunction-
onDrop当文件被拖入上传区域时执行的回调功能(event: React.DragEvent) => void-4.16.0
onDownload点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页function(file): void(跳转新标签页)
onPreview点击文件链接或预览图标时的回调function(file)-
onRemove  点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象,Promise 对象 resolve(false) 或 reject 时不移除              function(file): boolean | Promise-  

UploadFile

继承自 File,附带额外属性用于渲染。

参数说明类型默认值
name文件名string-
percent上传进度number-
status上传状态,不同状态展示颜色也会有所不同error | success | done | uploading | removed-
thumbUrl缩略图地址string-
uid唯一标识符,不设置时会自动生成string-
url下载地址string-

onChange

上传中、完成、失败都会调用这个函数。

文件状态改变的回调,返回为:

  1. {
  2. file: { /* ... */ },
  3. fileList: [ /* ... */ ],
  4. event: { /* ... */ },
  5. }
  1. file 当前操作的文件对象。

    1. {
    2. uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
    3. name: 'xx.png' // 文件名
    4. status: 'done', // 状态有:uploading done error removed,被 beforeUpload 拦截的文件没有 status 属性
    5. response: '{"status": "success"}', // 服务端响应内容
    6. linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
    7. }
  2. fileList 当前的文件列表。

  3. event 上传中的服务端响应内容,包含了上传进度等信息,高级浏览器支持。

FAQ

服务端如何实现?

如何显示下载链接?

请使用 fileList 属性设置数组项的 url 属性进行展示控制。

customRequest 怎么使用?

请参考 https://github.com/react-component/upload#customrequest

为何 fileList 受控时,上传不在列表中的文件不会触发 onChange 后续的 status 更新事件?

onChange 事件仅会作用于在列表中的文件,因而 fileList 不存在对应文件时后续事件会被忽略。请注意,在 4.13.0 版本之前受控状态存在 bug 导致不在列表中的文件也会触发。

onChange 为什么有时候返回 File 有时候返回 { originFileObj: File }?

历史原因,在 beforeUpload 返回 false 时,会返回 File 对象。在下个大版本我们会统一返回 { originFileObj: File } 对象。当前版本已经兼容所有场景下 info.file.originFileObj 获取原 File 写法。你可以提前切换。