Skip to main content
Glama

antd-components-mcp

examples.md37.2 kB
## Upload 组件示例 ### 点击上传 经典款式,用户点击按钮弹出文件选择框。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, message, Upload } from 'antd'; const props: UploadProps = { name: 'file', action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', headers: { authorization: 'authorization-text', }, onChange(info) { if (info.file.status !== 'uploading') { console.log(info.file, info.fileList); } if (info.file.status === 'done') { message.success(`${info.file.name} file uploaded successfully`); } else if (info.file.status === 'error') { message.error(`${info.file.name} file upload failed.`); } }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Click to Upload</Button> </Upload> ); export default App; ``` ### 用户头像 点击上传用户头像,并使用 `beforeUpload` 限制用户上传的图片格式和大小。 > `beforeUpload` 的返回值可以是一个 Promise 以支持异步处理,如服务端校验等:[示例](https://upload-react-component.vercel.app/demo/before-upload#beforeupload)。 ```tsx import React, { useState } from 'react'; import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'; import { Flex, message, Upload } from 'antd'; import type { GetProp, UploadProps } from 'antd'; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; const getBase64 = (img: FileType, callback: (url: string) => void) => { const reader = new FileReader(); reader.addEventListener('load', () => callback(reader.result as string)); reader.readAsDataURL(img); }; const beforeUpload = (file: FileType) => { const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; if (!isJpgOrPng) { message.error('You can only upload JPG/PNG file!'); } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { message.error('Image must smaller than 2MB!'); } return isJpgOrPng && isLt2M; }; const App: React.FC = () => { const [loading, setLoading] = useState(false); const [imageUrl, setImageUrl] = useState<string>(); const handleChange: UploadProps['onChange'] = (info) => { if (info.file.status === 'uploading') { setLoading(true); return; } if (info.file.status === 'done') { // Get this url from response in real world. getBase64(info.file.originFileObj as FileType, (url) => { setLoading(false); setImageUrl(url); }); } }; const uploadButton = ( <button style={{ border: 0, background: 'none' }} type="button"> {loading ? <LoadingOutlined /> : <PlusOutlined />} <div style={{ marginTop: 8 }}>Upload</div> </button> ); return ( <Flex gap="middle" wrap> <Upload name="avatar" listType="picture-card" className="avatar-uploader" showUploadList={false} action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" beforeUpload={beforeUpload} onChange={handleChange} > {imageUrl ? ( <img draggable={false} src={imageUrl} alt="avatar" style={{ width: '100%' }} /> ) : ( uploadButton )} </Upload> <Upload name="avatar" listType="picture-circle" className="avatar-uploader" showUploadList={false} action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" beforeUpload={beforeUpload} onChange={handleChange} > {imageUrl ? ( <img draggable={false} src={imageUrl} alt="avatar" style={{ width: '100%' }} /> ) : ( uploadButton )} </Upload> </Flex> ); }; export default App; ``` ### 已上传的文件列表 使用 `defaultFileList` 设置已上传的内容。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, Upload } from 'antd'; const props: UploadProps = { action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', onChange({ file, fileList }) { if (file.status !== 'uploading') { console.log(file, fileList); } }, defaultFileList: [ { uid: '1', name: 'xxx.png', status: 'uploading', url: 'http://www.baidu.com/xxx.png', percent: 33, }, { uid: '2', name: 'yyy.png', status: 'done', url: 'http://www.baidu.com/yyy.png', }, { uid: '3', name: 'zzz.png', status: 'error', response: 'Server Error 500', // custom error message to show url: 'http://www.baidu.com/zzz.png', }, ], }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Upload</Button> </Upload> ); export default App; ``` ### 照片墙 用户可以上传图片并在列表中显示缩略图。当上传照片数到达限制后,上传按钮消失。 ```tsx import React, { useState } from 'react'; import { PlusOutlined } from '@ant-design/icons'; import { Image, Upload } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd'; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; const getBase64 = (file: FileType): Promise<string> => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result as string); reader.onerror = (error) => reject(error); }); const App: React.FC = () => { const [previewOpen, setPreviewOpen] = useState(false); const [previewImage, setPreviewImage] = useState(''); const [fileList, setFileList] = useState<UploadFile[]>([ { uid: '-1', name: 'image.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-2', name: 'image.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-3', name: 'image.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-4', name: 'image.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-xxx', percent: 50, name: 'image.png', status: 'uploading', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-5', name: 'image.png', status: 'error', }, ]); const handlePreview = async (file: UploadFile) => { if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj as FileType); } setPreviewImage(file.url || (file.preview as string)); setPreviewOpen(true); }; const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => setFileList(newFileList); const uploadButton = ( <button style={{ border: 0, background: 'none' }} type="button"> <PlusOutlined /> <div style={{ marginTop: 8 }}>Upload</div> </button> ); return ( <> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture-card" fileList={fileList} onPreview={handlePreview} onChange={handleChange} > {fileList.length >= 8 ? null : uploadButton} </Upload> {previewImage && ( <Image wrapperStyle={{ display: 'none' }} preview={{ visible: previewOpen, onVisibleChange: (visible) => setPreviewOpen(visible), afterOpenChange: (visible) => !visible && setPreviewImage(''), }} src={previewImage} /> )} </> ); }; export default App; ``` ### 圆形照片墙 图片卡的替代显示。 ```tsx import React, { useState } from 'react'; import { PlusOutlined } from '@ant-design/icons'; import { Image, Upload } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd'; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; const getBase64 = (file: FileType): Promise<string> => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result as string); reader.onerror = (error) => reject(error); }); const App: React.FC = () => { const [previewOpen, setPreviewOpen] = useState(false); const [previewImage, setPreviewImage] = useState(''); const [fileList, setFileList] = useState<UploadFile[]>([ { uid: '-1', name: 'image.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-xxx', percent: 50, name: 'image.png', status: 'uploading', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-5', name: 'image.png', status: 'error', }, ]); const handlePreview = async (file: UploadFile) => { if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj as FileType); } setPreviewImage(file.url || (file.preview as string)); setPreviewOpen(true); }; const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => setFileList(newFileList); const uploadButton = ( <button style={{ border: 0, background: 'none' }} type="button"> <PlusOutlined /> <div style={{ marginTop: 8 }}>Upload</div> </button> ); return ( <> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture-circle" fileList={fileList} onPreview={handlePreview} onChange={handleChange} > {fileList.length >= 8 ? null : uploadButton} </Upload> {previewImage && ( <Image wrapperStyle={{ display: 'none' }} preview={{ visible: previewOpen, onVisibleChange: (visible) => setPreviewOpen(visible), afterOpenChange: (visible) => !visible && setPreviewImage(''), }} src={previewImage} /> )} </> ); }; export default App; ``` ### 完全控制的上传列表 使用 `fileList` 对列表进行完全控制,可以实现各种自定义功能,以下演示二种情况: 1. 上传列表数量的限制。 2. 读取远程路径并显示链接。 ```tsx import React, { useState } from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadFile, UploadProps } from 'antd'; import { Button, Upload } from 'antd'; const App: React.FC = () => { const [fileList, setFileList] = useState<UploadFile[]>([ { uid: '-1', name: 'xxx.png', status: 'done', url: 'http://www.baidu.com/xxx.png', }, ]); const handleChange: UploadProps['onChange'] = (info) => { let newFileList = [...info.fileList]; // 1. Limit the number of uploaded files // Only to show two recent uploaded files, and old ones will be replaced by the new newFileList = newFileList.slice(-2); // 2. Read from response and show file link newFileList = newFileList.map((file) => { if (file.response) { // Component will show file.url as link file.url = file.response.url; } return file; }); setFileList(newFileList); }; const props = { action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', onChange: handleChange, multiple: true, }; return ( <Upload {...props} fileList={fileList}> <Button icon={<UploadOutlined />}>Upload</Button> </Upload> ); }; export default App; ``` ### 拖拽上传 把文件拖入指定区域,完成上传,同样支持点击上传。 设置 `multiple` 后,在 `IE10+` 可以一次上传多个文件。 ```tsx import React from 'react'; import { InboxOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { message, Upload } from 'antd'; const { Dragger } = Upload; const props: UploadProps = { name: 'file', multiple: true, action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', onChange(info) { const { status } = info.file; if (status !== 'uploading') { console.log(info.file, info.fileList); } if (status === 'done') { message.success(`${info.file.name} file uploaded successfully.`); } else if (status === 'error') { message.error(`${info.file.name} file upload failed.`); } }, onDrop(e) { console.log('Dropped files', e.dataTransfer.files); }, }; const App: React.FC = () => ( <Dragger {...props}> <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text">Click or drag file to this area to upload</p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files. </p> </Dragger> ); export default App; ``` ### 粘贴上传 复制文件后,在页面任意位置粘贴即可完成上传。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, message, Upload } from 'antd'; const props: UploadProps = { name: 'file', pastable: true, action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', headers: { authorization: 'authorization-text', }, onChange(info) { if (info.file.status !== 'uploading') { console.log(info.file, info.fileList); } if (info.file.status === 'done') { message.success(`${info.file.name} file uploaded successfully`); } else if (info.file.status === 'error') { message.error(`${info.file.name} file upload failed.`); } }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Paste or click to upload</Button> </Upload> ); export default App; ``` ### 文件夹上传 支持上传一个文件夹里的所有文件。 [Safari 里仍然能选择文件?](#%E6%96%87%E4%BB%B6%E5%A4%B9%E4%B8%8A%E4%BC%A0%E5%9C%A8-safari-%E4%BB%8D%E7%84%B6%E5%8F%AF%E4%BB%A5%E9%80%89%E4%B8%AD%E6%96%87%E4%BB%B6) ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import { Button, Upload } from 'antd'; const App: React.FC = () => ( <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" directory> <Button icon={<UploadOutlined />}>Upload Directory</Button> </Upload> ); export default App; ``` ### 手动上传 `beforeUpload` 返回 `false` 后,手动上传文件。 ```tsx import React, { useState } from 'react'; import { UploadOutlined } from '@ant-design/icons'; import { Button, message, Upload } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd'; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; const App: React.FC = () => { const [fileList, setFileList] = useState<UploadFile[]>([]); const [uploading, setUploading] = useState(false); const handleUpload = () => { const formData = new FormData(); fileList.forEach((file) => { formData.append('files[]', file as FileType); }); setUploading(true); // You can use any AJAX library you like fetch('https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', { method: 'POST', body: formData, }) .then((res) => res.json()) .then(() => { setFileList([]); message.success('upload successfully.'); }) .catch(() => { message.error('upload failed.'); }) .finally(() => { setUploading(false); }); }; const props: UploadProps = { onRemove: (file) => { const index = fileList.indexOf(file); const newFileList = fileList.slice(); newFileList.splice(index, 1); setFileList(newFileList); }, beforeUpload: (file) => { setFileList([...fileList, file]); return false; }, fileList, }; return ( <> <Upload {...props}> <Button icon={<UploadOutlined />}>Select File</Button> </Upload> <Button type="primary" onClick={handleUpload} disabled={fileList.length === 0} loading={uploading} style={{ marginTop: 16 }} > {uploading ? 'Uploading' : 'Start Upload'} </Button> </> ); }; export default App; ``` ### 只上传 png 图片 `beforeUpload` 返回 `false` 或 `Promise.reject` 时,只用于拦截上传行为,不会阻止文件进入上传列表([原因](https://github.com/ant-design/ant-design/issues/15561#issuecomment-475108235))。如果需要阻止列表展现,可以通过返回 `Upload.LIST_IGNORE` 实现。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, message, Upload } from 'antd'; const props: UploadProps = { beforeUpload: (file) => { const isPNG = file.type === 'image/png'; if (!isPNG) { message.error(`${file.name} is not a png file`); } return isPNG || Upload.LIST_IGNORE; }, onChange: (info) => { console.log(info.fileList); }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Upload png only</Button> </Upload> ); export default App; ``` ### 图片列表样式 上传文件为图片,可展示本地缩略图。`IE8/9` 不支持浏览器本地缩略图展示([Ref](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL)),可以写 `thumbUrl` 属性来代替。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import { Button, Upload } from 'antd'; import type { UploadFile } from 'antd'; const fileList: UploadFile[] = [ { uid: '0', name: 'xxx.png', status: 'uploading', percent: 33, }, { uid: '-1', name: 'yyy.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-2', name: 'zzz.png', status: 'error', }, ]; const App: React.FC = () => ( <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture" defaultFileList={fileList} > <Button type="primary" icon={<UploadOutlined />}> Upload </Button> </Upload> ); export default App; ``` ### 自定义预览 自定义本地预览,用于处理非图片格式文件(例如视频文件)。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, Upload } from 'antd'; const props: UploadProps = { action: '//jsonplaceholder.typicode.com/posts/', listType: 'picture', previewFile(file) { console.log('Your upload file:', file); // Your process logic. Here we just mock to the same file return fetch('https://next.json-generator.com/api/json/get/4ytyBoLK8', { method: 'POST', body: file, }) .then((res) => res.json()) .then(({ thumbnail }) => thumbnail); }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Upload</Button> </Upload> ); export default App; ``` ### 限制数量 通过 `maxCount` 限制上传数量。当为 `1` 时,始终用最新上传的代替当前。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import { Button, Space, Upload } from 'antd'; const App: React.FC = () => ( <Space direction="vertical" style={{ width: '100%' }} size="large"> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture" maxCount={1} > <Button icon={<UploadOutlined />}>Upload (Max: 1)</Button> </Upload> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture" maxCount={3} multiple > <Button icon={<UploadOutlined />}>Upload (Max: 3)</Button> </Upload> </Space> ); export default App; ``` ### 上传前转换文件 使用 `beforeUpload` 转换上传的文件(例如添加水印)。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, Upload } from 'antd'; const props: UploadProps = { action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', listType: 'picture', beforeUpload(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { const img = document.createElement('img'); img.src = reader.result as string; img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d')!; ctx.drawImage(img, 0, 0); ctx.fillStyle = 'red'; ctx.textBaseline = 'middle'; ctx.font = '33px Arial'; ctx.fillText('Ant Design', 20, 20); canvas.toBlob((result) => resolve(result as Blob)); }; }; }); }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Upload</Button> </Upload> ); export default App; ``` ### 阿里云 OSS 使用阿里云 OSS 上传示例. ```tsx import React, { useEffect, useState } from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadFile, UploadProps } from 'antd'; import { App, Button, Form, Upload } from 'antd'; interface OSSDataType { dir: string; expire: string; host: string; accessId: string; policy: string; signature: string; } interface AliyunOSSUploadProps { value?: UploadFile[]; onChange?: (fileList: UploadFile[]) => void; } // Mock get OSS api // https://help.aliyun.com/document_detail/31988.html const mockOSSData = () => { const mockData = { dir: 'user-dir/', expire: '1577811661', host: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', accessId: 'c2hhb2RhaG9uZw==', policy: 'eGl4aWhhaGFrdWt1ZGFkYQ==', signature: 'ZGFob25nc2hhbw==', }; return Promise.resolve(mockData); }; const AliyunOSSUpload: React.FC<Readonly<AliyunOSSUploadProps>> = ({ value, onChange }) => { const { message } = App.useApp(); const [OSSData, setOSSData] = useState<OSSDataType>(); const init = async () => { try { const result = await mockOSSData(); setOSSData(result); } catch (err) { if (err instanceof Error) { message.error(err.message); } } }; useEffect(() => { init(); }, []); const handleChange: UploadProps['onChange'] = ({ fileList }) => { console.log('Aliyun OSS:', fileList); onChange?.([...fileList]); }; const onRemove = (file: UploadFile) => { const files = (value || []).filter((v) => v.url !== file.url); onChange?.(files); }; const getExtraData: UploadProps['data'] = (file) => ({ key: file.url, OSSAccessKeyId: OSSData?.accessId, policy: OSSData?.policy, Signature: OSSData?.signature, }); const beforeUpload: UploadProps['beforeUpload'] = async (file) => { if (!OSSData) { return false; } const expire = Number(OSSData.expire) * 1000; if (expire < Date.now()) { await init(); } const suffix = file.name.slice(file.name.lastIndexOf('.')); const filename = Date.now() + suffix; // @ts-ignore file.url = OSSData.dir + filename; return file; }; const uploadProps: UploadProps = { name: 'file', fileList: value, action: OSSData?.host, onChange: handleChange, onRemove, data: getExtraData, beforeUpload, }; return ( <Upload {...uploadProps}> <Button icon={<UploadOutlined />}>Click to Upload</Button> </Upload> ); }; const Demo: React.FC = () => ( <Form labelCol={{ span: 4 }}> <Form.Item label="Photos" name="photos"> <AliyunOSSUpload /> </Form.Item> </Form> ); export default Demo; ``` ### 自定义显示 icon 根据类型默认显示对应 icon ```tsx import React, { useState } from 'react'; import { FileExcelTwoTone, FilePdfTwoTone, FileWordTwoTone, LoadingOutlined, PaperClipOutlined, PictureTwoTone, PlusOutlined, } from '@ant-design/icons'; import { Image, Upload } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd'; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; const getBase64 = (file: FileType): Promise<string> => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result as string); reader.onerror = (error) => reject(error); }); const App: React.FC = () => { const [previewOpen, setPreviewOpen] = useState(false); const [previewImage, setPreviewImage] = useState(''); const [fileList, setFileList] = useState<UploadFile[]>([ { uid: '-2', name: 'pdf.pdf', status: 'done', url: 'http://cdn07.foxitsoftware.cn/pub/foxit/cpdf/FoxitCompanyProfile.pdf', }, { uid: '-3', name: 'doc.doc', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.doc', }, { uid: '-4', name: 'image.png', status: 'error', }, { uid: '-5', name: 'pdf.pdf', status: 'error', }, { uid: '-6', name: 'doc.doc', status: 'error', }, ]); const handlePreview = async (file: UploadFile) => { if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj as FileType); } setPreviewOpen(true); setPreviewImage(file.url || (file.preview as string)); }; const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => setFileList(newFileList); const handleIconRender: UploadProps['iconRender'] = (file, listType) => { const fileSufIconList = [ { type: <FilePdfTwoTone />, suf: ['.pdf'] }, { type: <FileExcelTwoTone />, suf: ['.xlsx', '.xls', '.csv'] }, { type: <FileWordTwoTone />, suf: ['.doc', '.docx'] }, { type: <PictureTwoTone />, suf: ['.webp', '.svg', '.png', '.gif', '.jpg', '.jpeg', '.jfif', '.bmp', '.dpg'], }, ]; // console.log(1, file, listType); let icon = file.status === 'uploading' ? <LoadingOutlined /> : <PaperClipOutlined />; if (listType === 'picture' || listType === 'picture-card' || listType === 'picture-circle') { if (listType === 'picture-card' && file.status === 'uploading') { icon = <LoadingOutlined />; // or icon = 'uploading...'; } else { fileSufIconList.forEach((item) => { if (item.suf.includes(file.name.slice(file.name.lastIndexOf('.')))) { icon = item.type; } }); } } return icon; }; const uploadButton = ( <button style={{ border: 0, background: 'none' }} type="button"> <PlusOutlined /> <div style={{ marginTop: 8 }}>Upload</div> </button> ); return ( <> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture-card" fileList={fileList} onPreview={handlePreview} onChange={handleChange} iconRender={handleIconRender} > {fileList.length >= 8 ? null : uploadButton} </Upload> {previewImage && ( <Image wrapperStyle={{ display: 'none' }} preview={{ visible: previewOpen, onVisibleChange: (visible) => setPreviewOpen(visible), afterOpenChange: (visible) => !visible && setPreviewImage(''), }} src={previewImage} /> )} </> ); }; export default App; ``` ### 自定义交互图标和文件信息 使用 `showUploadList` 设置列表交互图标和其他文件信息。 ```tsx import React from 'react'; import { StarOutlined, UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, Upload } from 'antd'; const props: UploadProps = { action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', onChange({ file, fileList }) { if (file.status !== 'uploading') { console.log(file, fileList); } }, defaultFileList: [ { uid: '1', name: 'xxx.png', size: 1234567, status: 'done', response: 'Server Error 500', // custom error message to show url: 'http://www.baidu.com/xxx.png', }, { uid: '2', name: 'yyy.png', size: 1234567, status: 'done', url: 'http://www.baidu.com/yyy.png', }, { uid: '3', name: 'zzz.png', size: 1234567, status: 'error', response: 'Server Error 500', // custom error message to show url: 'http://www.baidu.com/zzz.png', }, ], showUploadList: { extra: ({ size = 0 }) => ( <span style={{ color: '#cccccc' }}>({(size / 1024 / 1024).toFixed(2)}MB)</span> ), showDownloadIcon: true, downloadIcon: 'Download', showRemoveIcon: true, removeIcon: <StarOutlined onClick={(e) => console.log(e, 'custom removeIcon event')} />, }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Upload</Button> </Upload> ); export default App; ``` ### 上传列表拖拽排序 使用 `itemRender` ,我们可以集成 [dnd-kit](https://github.com/clauderic/dnd-kit) 来实现对上传列表拖拽排序。 ```tsx import React, { useState } from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { DragEndEvent } from '@dnd-kit/core'; import { DndContext, PointerSensor, useSensor } from '@dnd-kit/core'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Button, Upload } from 'antd'; import type { UploadFile, UploadProps } from 'antd'; interface DraggableUploadListItemProps { originNode: React.ReactElement<any, string | React.JSXElementConstructor<any>>; file: UploadFile<any>; } const DraggableUploadListItem = ({ originNode, file }: DraggableUploadListItemProps) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: file.uid, }); const style: React.CSSProperties = { transform: CSS.Translate.toString(transform), transition, cursor: 'move', }; return ( <div ref={setNodeRef} style={style} // prevent preview event when drag end className={isDragging ? 'is-dragging' : ''} {...attributes} {...listeners} > {/* hide error tooltip when dragging */} {file.status === 'error' && isDragging ? originNode.props.children : originNode} </div> ); }; const App: React.FC = () => { const [fileList, setFileList] = useState<UploadFile[]>([ { uid: '-1', name: 'image1.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-2', name: 'image2.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-3', name: 'image3.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-4', name: 'image4.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, { uid: '-5', name: 'image.png', status: 'error', }, ]); const sensor = useSensor(PointerSensor, { activationConstraint: { distance: 10 }, }); const onDragEnd = ({ active, over }: DragEndEvent) => { if (active.id !== over?.id) { setFileList((prev) => { const activeIndex = prev.findIndex((i) => i.uid === active.id); const overIndex = prev.findIndex((i) => i.uid === over?.id); return arrayMove(prev, activeIndex, overIndex); }); } }; const onChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { setFileList(newFileList); }; return ( <DndContext sensors={[sensor]} onDragEnd={onDragEnd}> <SortableContext items={fileList.map((i) => i.uid)} strategy={verticalListSortingStrategy}> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" fileList={fileList} onChange={onChange} itemRender={(originNode, file) => ( <DraggableUploadListItem originNode={originNode} file={file} /> )} > <Button icon={<UploadOutlined />}>Click to Upload</Button> </Upload> </SortableContext> </DndContext> ); }; export default App; ``` ### 上传前裁切图片 配合 [antd-img-crop](https://github.com/nanxiaobei/antd-img-crop) 实现上传前裁切图片。 ```tsx import React, { useState } from 'react'; import { Upload } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd'; import ImgCrop from 'antd-img-crop'; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; const App: React.FC = () => { const [fileList, setFileList] = useState<UploadFile[]>([ { uid: '-1', name: 'image.png', status: 'done', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', }, ]); const onChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { setFileList(newFileList); }; const onPreview = async (file: UploadFile) => { let src = file.url as string; if (!src) { src = await new Promise((resolve) => { const reader = new FileReader(); reader.readAsDataURL(file.originFileObj as FileType); reader.onload = () => resolve(reader.result as string); }); } const image = new Image(); image.src = src; const imgWindow = window.open(src); imgWindow?.document.write(image.outerHTML); }; return ( <ImgCrop rotationSlider> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" listType="picture-card" fileList={fileList} onChange={onChange} onPreview={onPreview} > {fileList.length < 5 && '+ Upload'} </Upload> </ImgCrop> ); }; export default App; ``` ### 自定义进度条样式 使用 `progress` 属性自定义进度条样式。 ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, message, Upload } from 'antd'; const props: UploadProps = { name: 'file', action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', headers: { authorization: 'authorization-text', }, onChange(info) { if (info.file.status !== 'uploading') { console.log(info.file, info.fileList); } if (info.file.status === 'done') { message.success(`${info.file.name} file uploaded successfully`); } else if (info.file.status === 'error') { message.error(`${info.file.name} file upload failed.`); } }, progress: { strokeColor: { '0%': '#108ee9', '100%': '#87d068', }, strokeWidth: 3, format: (percent) => percent && `${Number.parseFloat(percent.toFixed(2))}%`, }, }; const App: React.FC = () => ( <Upload {...props}> <Button icon={<UploadOutlined />}>Click to Upload</Button> </Upload> ); export default App; ``` ### 组件 Token Component Token Debug. ```tsx import React from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { Button, ConfigProvider, Upload } from 'antd'; const props: UploadProps = { action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', onChange({ file, fileList }) { if (file.status !== 'uploading') { console.log(file, fileList); } }, defaultFileList: [ { uid: '1', name: 'xxx.png', status: 'uploading', url: 'http://www.baidu.com/xxx.png', percent: 33, }, { uid: '2', name: 'yyy.png', status: 'done', url: 'http://www.baidu.com/yyy.png', }, { uid: '3', name: 'zzz.png', status: 'error', response: 'Server Error 500', // custom error message to show url: 'http://www.baidu.com/zzz.png', }, ], }; const App: React.FC = () => ( <ConfigProvider theme={{ components: { Upload: { actionsColor: 'yellow', }, }, }} > <Upload {...props}> <Button icon={<UploadOutlined />}>Upload</Button> </Upload> </ConfigProvider> ); export default App; ```

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zhixiaoqiang/antd-components-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server