输入类

上传

文件选择上传

代码演示

如何引入

import { Avatar, Button, Image, ImagePreview, Radio, RadioGroup, Toast, Tooltip, Upload } from '@fibrous/semi-ui-solid';

基本

最基本的用法,在触发区内放置一个 Button,点击它激活文件选择框,选择完成后自动开始上传

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload">
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

文件名超长省略

通过 showTooltip 属性,自定义设置文件名弹出提示

当类型为 boolean 时,控制是否弹出提示

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" showTooltip={false}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

当类型为 object 时,可以通过 renderTooltip 自定义弹出层,参数为 content(完整文件名)与 children(文件名元素);type 与 opts 配置提示的类型与透传给浮层的属性

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Tooltip, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            showTooltip={{
                renderTooltip: (content, children) => (
                    <Tooltip content={content} position="bottom">
                        {children}
                    </Tooltip>
                ),
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

添加提示文本

通过 prompt 插槽,设置自定义提示文本
通过 promptPosition 设置插槽位置,可选 left、right、bottom,默认为 right

请上传资格认证材料
请上传资格认证材料
请上传资格认证材料
代码
import { For, Show } from 'solid-js';
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type PromptPositionType, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';

const positions: PromptPositionType[] = ['right', 'left', 'bottom'];

const getPromptStyle = (pos: PromptPositionType) => ({
    display: 'flex',
    'align-items': 'center',
    color: 'var(--semi-color-text-2)',
    height: '32px',
    ...(pos === 'left' ? { 'margin-right': '10px' } : pos === 'right' ? { 'margin-left': '10px' } : {}),
});

export default function UploadDemo() {
    return (
        <For each={positions}>
            {(pos, index) => (
                <>
                    <Show when={index() > 0}>
                        <div style={{ 'margin-bottom': '12px', 'margin-top': '12px', 'border-bottom': '1px solid var(--semi-color-border)' }} />
                    </Show>
                    <Upload action={action} prompt={<div style={getPromptStyle(pos)}>请上传资格认证材料</div>} promptPosition={pos}>
                        <Button icon={<IconUpload />} theme="light">
                            点击上传
                        </Button>
                    </Upload>
                </>
            )}
        </For>
    );
}

当 listType 为 picture 时,promptPosition 位置的参照对象为图片墙列表整体

请上传认证材料
dy.jpeg
resso.jpeg
请上传认证材料
dy.jpeg
resso.jpeg
代码
import { For, Show } from 'solid-js';
import { IconPlus } from '@fibrous/semi-icons-solid';
import { type FileItem, type PromptPositionType, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dy.jpeg',
        status: 'success',
        size: '130kb',
        url: '/assets/placeholder/avatar-a.svg',
    },
    {
        uid: '5',
        name: 'resso.jpeg',
        status: 'wait',
        percent: 50,
        size: '222kb',
        url: '/assets/placeholder/avatar-b.svg',
    },
];

const positions: PromptPositionType[] = ['right', 'bottom'];

const getPromptStyle = (pos: PromptPositionType) => ({
    display: 'flex',
    'align-items': 'center',
    color: 'var(--semi-color-text-2)',
    height: '100%',
    ...(pos === 'right' ? { 'margin-left': '10px' } : {}),
});

export default function UploadDemo() {
    return (
        <For each={positions}>
            {(pos, index) => (
                <>
                    <Show when={index() > 0}>
                        <div style={{ 'margin-bottom': '12px', 'margin-top': '12px', 'border-bottom': '1px solid var(--semi-color-border)' }} />
                    </Show>
                    <Upload action={action} prompt={<div style={getPromptStyle(pos)}>请上传认证材料</div>} promptPosition={pos} listType="picture" defaultFileList={defaultFileList}>
                        <IconPlus aria-label="上传图片" size="extra-large" />
                    </Upload>
                </>
            )}
        </For>
    );
}

点击头像触发上传

上传头像
代码
import { createSignal } from 'solid-js';
import { IconCamera } from '@fibrous/semi-icons-solid';
import { Avatar, Toast, Upload } from '@fibrous/semi-ui-solid';

const api = 'https://api.semi.design/upload';
const imageOnly = 'image/*';

const maskStyle = {
    'background-color': 'var(--semi-color-overlay-bg)',
    height: '100%',
    width: '100%',
    display: 'flex',
    'align-items': 'center',
    'justify-content': 'center',
    color: 'var(--semi-color-white)',
};

export default function UploadDemo() {
    const [url, setUrl] = createSignal('/assets/placeholder/avatar-a.svg');

    const onSuccess = () => {
        Toast.success('头像更新成功');
        setUrl('/assets/ttmoment.jpeg');
    };

    return (
        <Upload class="avatar-upload" action={api} onSuccess={onSuccess} accept={imageOnly} showUploadList={false} onError={() => Toast.error('上传失败')}>
            {/* the avatar is the whole content of the upload button, so its alt names the button */}
            <Avatar
                src={url()}
                alt="上传头像"
                style={{ margin: '4px' }}
                hoverMask={
                    <div style={maskStyle}>
                        <IconCamera />
                    </div>
                }
            />
        </Upload>
    );
}
.avatar-upload .semi-upload-add {
    border-radius: 50%; // 确保只有圆是点击热区
}

自定义上传属性

通过设置 data、headers 可添加自定义上传属性

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';
// A data function is evaluated per upload, so time is the upload moment
const data = () => ({
    role: 'ies',
    time: new Date().getTime(),
});
const headers = {
    'x-tt-semi': 'semi-upload',
};

export default function UploadDemo() {
    return (
        <Upload action={action} data={data} headers={headers}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

上传文件类型

通过 accept 属性(input 的原生 html 属性)可以限制上传的文件类型。

accept 支持传入以下两种类型字符串:

  • 文件后缀名集合(推荐),如 .jpg、.png 等;
  • 文件类型的 MIME types 集合,可参考MDN 文档

例如只允许用户上传 PNG 和 PDF 文件,accept 可以这样写: accept = '.pdf,.png' 或 accept = 'application/pdf,image/png'(将 PNG 与 PDF 的 MIME type 通过,连接起来即可)。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';
const imageOnly = 'image/*';
const videoOnly = 'video/*';
const fileLimit = '.pdf,.png,.jpeg';

export default function UploadDemo() {
    return (
        <>
            <Upload action={action} accept={imageOnly} style={{ 'margin-bottom': '12px' }}>
                <Button icon={<IconUpload />} theme="light">
                    上传图片
                </Button>
            </Upload>
            <Upload action={action} accept={videoOnly} style={{ 'margin-bottom': '12px' }}>
                <Button icon={<IconUpload />} theme="light">
                    上传视频
                </Button>
            </Upload>
            <Upload action={action} accept={fileLimit}>
                <Button icon={<IconUpload />} theme="light">
                    上传 PDF, PNG, JPEG
                </Button>
            </Upload>
        </>
    );
}

上传文件夹

通过传入 directory 为 true,可以支持上传文件夹下的所有文件

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" directory>
            <Button icon={<IconUpload />} theme="light">
                上传文件夹
            </Button>
        </Upload>
    );
}

一次选中多个文件

通过设置 multiple 属性可以支持同时选中多个文件上传。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" multiple>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

限制文件总数量

通过设置 limit 属性可以限制最大可上传的文件数
当 limit 为1时,始终用最新上传的代替当前,并不会触发 onExceed 回调

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload, type UploadOnChangeProps } from '@fibrous/semi-ui-solid';

const limit = 1;

export default function UploadDemo() {
    const onChange = ({ fileList }: UploadOnChangeProps) => {
        console.log(fileList);
    };

    return (
        <Upload action="https://api.semi.design/upload" limit={limit} onChange={onChange}>
            <Button icon={<IconUpload />} theme="light">
                点击上传(最多{limit}项)
            </Button>
        </Upload>
    );
}

达到上限时禁用触发控件而不是 Upload,这样仍能移除已上传的文件。

代码
import { createSignal } from 'solid-js';
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Toast, Upload, type UploadOnChangeProps } from '@fibrous/semi-ui-solid';

const limit = 2;

export default function UploadDemo() {
    const [disabled, setDisabled] = createSignal(false);

    const onChange = ({ fileList }: UploadOnChangeProps) => {
        setDisabled(fileList.length === limit);
    };

    return (
        <Upload action="https://api.semi.design/upload" limit={limit} onExceed={() => Toast.warning(`最多只允许上传${limit}个文件`)} onChange={onChange}>
            <Button icon={<IconUpload />} theme="light" disabled={disabled()}>
                点击上传(最多{limit}项)
            </Button>
        </Upload>
    );
}

照片墙模式下,当已上传文件数量等于 limit 时,会自动隐藏上传入口

dyBag.png
dyBag2.jpeg
代码
import { IconPlus } from '@fibrous/semi-icons-solid';
import { type FileItem, Toast, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dyBag.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
    {
        uid: '2',
        name: 'dyBag2.jpeg',
        status: 'success',
        size: '222KB',
        preview: true,
        fileInstance: new File([new ArrayBuffer(2048)], 'dyBag2.jpeg', { type: 'image/png' }),
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" limit={2} listType="picture" accept="image/*" defaultFileList={defaultFileList} onExceed={() => Toast.warning('最多只允许上传2个文件')}>
            <IconPlus aria-label="上传图片" size="extra-large" />
        </Upload>
    );
}

限制上传文件大小

通过 maxSize 和 minSize 属性可以自定义上传文件大小的限制,通过设置 onSizeError 可以设置超出限制时的回调。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Toast, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" maxSize={1024} minSize={200} onSizeError={(file) => Toast.error(`${file.name} size invalid`)}>
            <Button icon={<IconUpload />} theme="light">
                点击上传(最小 200KB,最大 1MB)
            </Button>
        </Upload>
    );
}

自定义列表操作区

listType 为 list 时,可以通过传入 renderFileOperation 来自定义列表操作区

已选择文件
dyBag.png130KB
代码
import { IconDelete, IconDownload, IconEyeOpened, IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, type RenderFileItemProps, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dyBag.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
];

const renderFileOperation = (fileItem: RenderFileItemProps) => (
    <div style={{ display: 'flex', 'column-gap': '8px', padding: '0 8px' }}>
        <Button aria-label="预览" icon={<IconEyeOpened />} type="tertiary" theme="borderless" size="small" />
        <Button aria-label="下载" icon={<IconDownload />} type="tertiary" theme="borderless" size="small" />
        <Button onClick={() => fileItem.onRemove?.()} aria-label="删除" icon={<IconDelete />} type="tertiary" theme="borderless" size="small" />
    </div>
);

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" defaultFileList={defaultFileList} itemStyle={{ width: '300px' }} renderFileOperation={renderFileOperation}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

自定义文件列表标题

listType 为 list 时,可以通过 fileListTitle 自定义文件列表顶部的标题区域。支持两种形式:

传入 JSX 形式

当传入 JSX 元素时,仅替换标题文字,清空按钮保持默认样式:

已上传文件
document.pdf130KB
report.xlsx222KB
📁 重要文件
document.pdf130KB
report.xlsx222KB
代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'document.pdf',
        status: 'success',
        size: '130KB',
    },
    {
        uid: '2',
        name: 'report.xlsx',
        status: 'success',
        size: '222KB',
    },
];

export default function UploadDemo() {
    return (
        <>
            <Upload action={action} defaultFileList={defaultFileList} fileListTitle="已上传文件" style={{ 'margin-bottom': '20px' }}>
                <Button icon={<IconUpload />} theme="light">
                    自定义标题文字
                </Button>
            </Upload>
            <Upload action={action} defaultFileList={defaultFileList} fileListTitle={<span style={{ color: 'var(--semi-color-primary)', 'font-weight': '600' }}>📁 重要文件</span>}>
                <Button icon={<IconUpload />} theme="light">
                    带样式的标题
                </Button>
            </Upload>
        </>
    );
}

函数形式

当传入函数时,可以完全自定义标题区域,包括清空按钮。函数会接收以下参数:

  • fileList: 当前文件列表
  • onClear: 清空文件的回调函数
  • clearText: 清空按钮的默认文案(根据当前语言环境)
📂 共 2 个文件
document.pdf130KB
report.xlsx222KB
已选择 2 个文件
document.pdf130KB
report.xlsx222KB
代码
import { IconClose, IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'document.pdf',
        status: 'success',
        size: '130KB',
    },
    {
        uid: '2',
        name: 'report.xlsx',
        status: 'success',
        size: '222KB',
    },
];

export default function UploadDemo() {
    return (
        <>
            <Upload
                action={action}
                defaultFileList={defaultFileList}
                style={{ 'margin-bottom': '20px' }}
                fileListTitle={({ fileList, onClear, clearText }) => (
                    <div
                        style={{
                            display: 'flex',
                            'align-items': 'center',
                            'justify-content': 'space-between',
                            width: '100%',
                        }}
                    >
                        <span style={{ color: 'var(--semi-color-primary)' }}>📂 共 {fileList.length} 个文件</span>
                        <Button size="small" type="danger" icon={<IconClose />} onClick={onClear}>
                            {clearText}
                        </Button>
                    </div>
                )}
            >
                <Button icon={<IconUpload />} theme="light">
                    自定义清空按钮样式
                </Button>
            </Upload>
            <Upload
                action={action}
                defaultFileList={defaultFileList}
                fileListTitle={({ fileList, onClear }) => (
                    <div
                        style={{
                            display: 'flex',
                            'align-items': 'center',
                            'justify-content': 'space-between',
                            width: '100%',
                        }}
                    >
                        <span>
                            已选择 <strong style={{ color: 'var(--semi-color-danger)' }}>{fileList.length}</strong> 个文件
                        </span>
                        <button
                            type="button"
                            onClick={onClear}
                            style={{
                                padding: '0',
                                border: 'none',
                                background: 'none',
                                font: 'inherit',
                                cursor: 'pointer',
                                color: 'var(--semi-color-link)',
                                'font-size': '12px',
                            }}
                        >
                            全部移除
                        </button>
                    </div>
                )}
            >
                <Button icon={<IconUpload />} theme="light">
                    完全自定义标题区域
                </Button>
            </Upload>
        </>
    );
}

自定义预览逻辑

listType 为 list 时,可以通过传入 previewFile 自定义预览逻辑
例如你不需要对图片类型进行缩略图预览时,可以在 previewFile 中恒定返回一个 <IconFile />
假如你希望点击图片时放大预览,则可以在 previewFile 中使用 Image 组件
或者你希望使用额外的操作区来实现点击放大预览,你也可以结合 renderFileOperation 放置一些自定义元素例如 Icon 图标实现点击放大

已选择文件
dyBag.png130KB
dyBag2.png130KB
代码
import { IconFile, IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Image, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dyBag.png',
        status: 'success',
        size: '130KB',
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
    {
        uid: '2',
        name: 'dyBag2.png',
        status: 'success',
        size: '130KB',
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
];

export default function UploadDemo() {
    return (
        <Upload defaultFileList={defaultFileList} action="https://api.semi.design/upload" previewFile={(file) => (file.uid === '1' ? <IconFile size="large" /> : <Image src={file.url} alt="" />)}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

结合 renderFileOperation 与 ImagePreview 的示例,以下示例点击右侧第一个 Icon 可放大图片预览

已选择文件
dyBag.png130KB
代码
import { createSignal } from 'solid-js';
import { IconDelete, IconDownload, IconExpand, IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, ImagePreview, type RenderFileItemProps, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dyBag.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
];

export default function UploadDemo() {
    const [visible, setVisible] = createSignal(false);

    const renderFileOperation = (fileItem: RenderFileItemProps) => (
        <div style={{ display: 'flex', 'column-gap': '8px', padding: '0 8px' }}>
            <Button aria-label="预览" icon={<IconExpand />} type="tertiary" theme="borderless" size="small" onClick={() => setVisible(true)} />
            <Button aria-label="下载" icon={<IconDownload />} type="tertiary" theme="borderless" size="small" />
            <Button aria-label="删除" onClick={() => fileItem.onRemove?.()} icon={<IconDelete />} type="tertiary" theme="borderless" size="small" />
            <ImagePreview src={fileItem.url} visible={visible()} onVisibleChange={(value) => setVisible(value)} />
        </div>
    );

    return (
        <Upload action="https://api.semi.design/upload" defaultFileList={defaultFileList} itemStyle={{ width: '300px' }} renderFileOperation={renderFileOperation}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

默认文件列表

通过 defaultFileList 可以展示已上传的文件。当需要预览默认文件的缩略图时,你可以将 defaultFileList 内对应 item 的 preview 属性设为 true

已选择文件
dyBag.jpeg130KB
dyBag2.jpeg222KB
上传失败
代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dyBag.jpeg',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
    {
        uid: '2',
        name: 'dyBag2.jpeg',
        status: 'uploadFail',
        size: '222KB',
        preview: true,
        fileInstance: new File([new ArrayBuffer(2048)], 'dyBag2.jpeg', { type: 'image/png' }),
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action={action} defaultFileList={defaultFileList}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

受控组件

当传入fileList时,作为受控组件使用。需要监听 onChange 回调,并且将 fileList 回传给 Upload(注意需传入一个新的数组对象)

已选择文件
dyBag.jpeg130KB
dy.jpeg222KB
代码
import { createSignal } from 'solid-js';
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, Upload, type UploadOnChangeProps } from '@fibrous/semi-ui-solid';

const initList: FileItem[] = [
    {
        uid: '1',
        name: 'dyBag.jpeg',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/root-web-sites/edit-bag.jpeg',
    },
    {
        uid: '2',
        name: 'dy.jpeg',
        status: 'uploading',
        size: '222KB',
        percent: 50,
        preview: true,
        fileInstance: new File([new ArrayBuffer(2048)], 'dy.jpeg', { type: 'image/jpeg' }),
        url: '/assets/placeholder/avatar-a.svg',
    },
];

export default function UploadDemo() {
    const [list, updateList] = createSignal(initList);

    const onChange = ({ fileList, currentFile }: UploadOnChangeProps) => {
        console.log('onChange');
        console.log(fileList);
        console.log(currentFile);
        // 展开得到新数组
        updateList([...fileList]);
    };

    return (
        <Upload action="https://api.semi.design/upload" onChange={onChange} fileList={list()} showRetry={false}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

图片墙

设置 listType = 'picture',用户可以上传图片并在列表中显示缩略图
如果通过 defaultFileList 或 fileList 设置已上传的文件列表时,会自动读取对象数组中的 url 属性用于展示图片

dy.png
代码
import { IconPlus } from '@fibrous/semi-icons-solid';
import { type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'dy.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-a.svg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" listType="picture" accept="image/*" multiple defaultFileList={defaultFileList}>
            <IconPlus aria-label="上传图片" size="extra-large" />
        </Upload>
    );
}

设置 showPicInfo,可以查看图片基础信息

music.png
1
brand.png
2
代码
import { IconPlus } from '@fibrous/semi-icons-solid';
import { type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'music.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-b.svg',
    },
    {
        uid: '2',
        name: 'brand.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-b.svg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" listType="picture" showPicInfo accept="image/*" multiple defaultFileList={defaultFileList}>
            <IconPlus aria-label="上传图片" size="extra-large" />
        </Upload>
    );
}

图片墙放大预览

配合 Image 组件,通过 renderThumbnail 属性,可以实现点击图片放大预览

music.png
代码
import { IconPlus } from '@fibrous/semi-icons-solid';
import { Image, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'music.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-b.svg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" listType="picture" accept="image/*" multiple defaultFileList={defaultFileList} renderThumbnail={(file) => <Image src={file.url} alt={file.name} />}>
            <IconPlus aria-label="Upload picture" size="extra-large" />
        </Upload>
    );
}

可以通过 renderPicPreviewIcon、onPreviewClick 来自定义预览图标,当显示替换图标 showReplace 时,不会再显示预览图标
当需要自定义预览/替换功能时,需要关闭替换功能,使用 renderPicPreviewIcon 监听图标点击事件即可。
onPreviewClick 监听的是单张图片容器的点击事件

resso.png
1
代码
import { IconEyeOpened, IconPlus } from '@fibrous/semi-icons-solid';
import { type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'resso.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-b.svg',
    },
];

const handlePreview = (file: FileItem) => {
    window.open(file.url, 'imagePreview', 'width=300,height=300');
};

export default function UploadDemo() {
    return (
        <>
            <Upload action="https://api.semi.design/upload" listType="picture" showPicInfo accept="image/*" multiple defaultFileList={defaultFileList} onPreviewClick={handlePreview} renderPicPreviewIcon={() => <IconEyeOpened style={{ color: 'var(--semi-color-white)', 'font-size': '24px' }} />}>
                <IconPlus aria-label="上传图片" size="extra-large" />
            </Upload>
        </>
    );
}

图片墙设置宽高

通过设置 picHeight, picWidth,可以统一设置图片墙元素的宽高。如果同时使用 renderThumbnail 返回 Image 组件来实现点击放大预览,你需要同时指定 Image 组件的 width 和 height

image-1.jpg
点击添加图片
代码
import { IconPlus } from '@fibrous/semi-icons-solid';
import { Image, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'image-1.jpg',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/root-web-sites/abstract.jpg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" listType="picture" accept="image/*" multiple defaultFileList={defaultFileList} picHeight={110} picWidth={200} renderThumbnail={(file) => <Image src={file.url} alt={file.name} width={200} height={110} />}>
            <IconPlus aria-label="上传图片" size="extra-large" style={{ margin: '4px' }} />
            点击添加图片
        </Upload>
    );
}

设置 hotSpotLocation 自定义点击热区的顺序,默认在照片墙列表结尾


resso.png
1
代码
import { createSignal } from 'solid-js';
import { IconPlus } from '@fibrous/semi-icons-solid';
import { type FileItem, Radio, RadioGroup, Upload, type UploadProps } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'resso.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-b.svg',
    },
];

const handlePreview = (file: FileItem) => {
    window.open(file.url, 'imagePreview', 'width=300,height=300');
};

export default function UploadDemo() {
    const [hotSpotLocation, setLocation] = createSignal<UploadProps['hotSpotLocation']>('end');
    return (
        <>
            <RadioGroup value={hotSpotLocation()} type="button" onChange={(event) => setLocation(event.target.value)}>
                <Radio value="start">start</Radio>
                <Radio value="end">end</Radio>
            </RadioGroup>
            <hr />
            <Upload action="https://api.semi.design/upload" listType="picture" showPicInfo accept="image/*" multiple hotSpotLocation={hotSpotLocation()} defaultFileList={defaultFileList} onPreviewClick={handlePreview}>
                <IconPlus aria-label="上传图片" size="extra-large" />
            </Upload>
        </>
    );
}

禁用

已选择文件
first.png130KB
second.png222KB
代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type FileItem, Upload } from '@fibrous/semi-ui-solid';

const defaultFileList: FileItem[] = [
    {
        uid: '1',
        name: 'first.png',
        status: 'success',
        size: '130KB',
        preview: true,
        url: '/assets/placeholder/avatar-a.svg',
    },
    {
        uid: '2',
        name: 'second.png',
        status: 'validateFail',
        size: '222KB',
        preview: true,
        url: '/assets/placeholder/avatar-a.svg',
    },
];

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" disabled defaultFileList={defaultFileList}>
            <Button icon={<IconUpload />} theme="light" disabled>
                点击上传
            </Button>
        </Upload>
    );
}

手动触发上传

uploadTrigger='custom',选中文件后将不会自动触发上传。需要通过 ref 拿到上传组件,调用其上的 upload 方法触发

代码
import { IconPlus, IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload, type UploadElement } from '@fibrous/semi-ui-solid';

const action = 'https://api.semi.design/upload';

export default function UploadDemo() {
    let uploadRef: UploadElement | undefined;

    const manualUpload = () => {
        uploadRef?.upload();
    };

    return (
        <div>
            <Upload accept="image/gif, image/png, image/jpeg, image/bmp, image/webp" action={action} uploadTrigger="custom" ref={(element) => (uploadRef = element)} onSuccess={(response, file) => console.log(response, file)} onError={(error, file) => console.log(error, file)}>
                <Button icon={<IconPlus />} theme="light" style={{ 'margin-right': '8px' }}>
                    选择文件
                </Button>
            </Upload>
            <Button icon={<IconUpload />} theme="light" onClick={manualUpload}>
                开始上传
            </Button>
        </div>
    );
}

拖拽上传

draggable='true',可以使用拖拽功能

点击上传文件或拖拽文件到这里
支持任意类型文件
代码
import { Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return <Upload action="https://api.semi.design/upload" draggable dragMainText="点击上传文件或拖拽文件到这里" dragSubText="支持任意类型文件" />;
}

可以通过 dragIcon、dragMainText、dragSubText 快捷设置拖拽区内容

点击上传文件或拖拽文件到这里
仅支持jpeg、pdf
代码
import { IconBolt } from '@fibrous/semi-icons-solid';
import { Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return <Upload action="https://api.semi.design/upload" dragIcon={<IconBolt />} draggable accept="application/pdf,.jpeg" dragMainText="点击上传文件或拖拽文件到这里" dragSubText="仅支持jpeg、pdf" style={{ 'margin-top': '10px' }} />;
}

还可以通过 children 传入 JSX 元素,完全自定义拖拽区的显示

demo img
Wow, you can really dance.
代码
import { IconBolt } from '@fibrous/semi-icons-solid';
import { Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" dragIcon={<IconBolt />} draggable accept="application/pdf,.jpeg" style={{ 'margin-top': '10px' }}>
            <div class="components-upload-demo-drag-area">
                <img src="/assets/placeholder/avatar-a.svg" height="96" alt="demo img" style={{ 'border-radius': '4px' }} />
                <div
                    style={{
                        'font-size': '14px',
                        'margin-top': '8px',
                        'flex-basis': '100%',
                        'text-align': 'center',
                        color: 'var(--semi-color-tertiary)',
                    }}
                >
                    Wow, you can really dance.
                </div>
            </div>
        </Upload>
    );
}

Scss 样式如下

.components-upload-demo-drag-area {
    border-radius: var(--semi-border-radius-small);
    border: 2px dashed var(--semi-color-border);
    width: 100%;
    padding: 12px;
    background-color: var(--semi-color-tertiary-light-default);
    display: flex;
    cursor: pointer;
    flex-wrap: wrap;
    justify-content: center;
    &:hover {
        background-color: var(--semi-color-primary-light-default);
        border-color: var(--semi-color-primary);
    }
}

粘贴上传

设置 addOnPasting 后,在页面任意位置粘贴(paste 事件)的文件会加入文件列表,不需要剪贴板授权。读取粘贴的文件失败时触发 onPastingError。

复制截图或文件后,在页面上按 Ctrl/Cmd + V
也可以点击或拖拽上传
代码
import { Toast, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return <Upload action="https://api.semi.design/upload" addOnPasting draggable dragMainText="复制截图或文件后,在页面上按 Ctrl/Cmd + V" dragSubText="也可以点击或拖拽上传" onPastingError={() => Toast.error('读取剪贴板中的文件失败')} />;
}

上传前自定义校验

可通过 beforeUpload 钩子,对文件状态进行更新,这是在网络上传前,选择文件后进行校验,({ file: FileItem, fileList: Array<FileItem> }) => beforeUploadResult | Promise | boolean 同步校验时需返回 boolean(true 为校验通过,false 为校验失败,校验失败会阻止文件网络上传)或者一个 Object 对象,具体结构如下

// beforeUploadResult:
{
    fileInstance?: File,
    status?: 'success' | 'uploadFail' | 'validateFail' | 'validating' | 'uploading' | 'wait',
    validateMessage?: string, // 文件的校验信息,需要节点时使用 renderValidateMessage
    shouldUpload: boolean, // 是否需要上传。默认为true,如果为false,该fileItem只会被展示在列表中,不会触发上传操作
    autoRemove: boolean, // 是否从fileList中移除该文件,默认为false
}
代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { type BeforeUploadObjectResult, type BeforeUploadProps, Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    let count = 0;

    const transformFile = (fileInstance: File) => {
        if (count === 0) {
            return new File([fileInstance], 'newFileName', { type: 'image/png' });
        }
        return fileInstance;
    };

    const beforeUpload = ({ file }: BeforeUploadProps) => {
        const result: BeforeUploadObjectResult = count > 0 ? { autoRemove: false, fileInstance: file.fileInstance, shouldUpload: true } : { autoRemove: false, fileInstance: file.fileInstance, status: 'validateFail', shouldUpload: false };
        count = count + 1;
        return result;
    };

    return (
        <Upload action="https://api.semi.design/upload" transformFile={transformFile} beforeUpload={beforeUpload}>
            <Button icon={<IconUpload />} theme="light">
                点击上传(上传前同步校验)
            </Button>
        </Upload>
    );
}

异步校验时,需返回 Promise,Promise resolve 代表检验通过,reject 代表校验失败,不会触发上传。
resolve/reject 时可以传入 object(结构同上 beforeUploadResult)

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { type BeforeUploadObjectResult, type BeforeUploadProps, Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    let count = 0;

    const beforeUpload = ({ file }: BeforeUploadProps) => {
        return new Promise<BeforeUploadObjectResult>((resolve, reject) => {
            if (count > 1) {
                const result = {
                    autoRemove: false,
                    shouldUpload: true,
                };
                count = count + 1;
                resolve(result);
            } else {
                const result = {
                    autoRemove: false,
                    fileInstance: file.fileInstance,
                    status: 'validateFail',
                    shouldUpload: false,
                    validateMessage: `第${count + 1}个注定失败`,
                };
                count = count + 1;
                reject(result);
            }
        });
    };

    return (
        <Upload action="https://api.semi.design/upload" beforeUpload={beforeUpload}>
            <Button icon={<IconUpload />} theme="light">
                点击上传(上传前异步校验)
            </Button>
        </Upload>
    );
}

上传后更新文件信息

可以通过 afterUpload 钩子,对文件状态,校验信息,文件名进行更新。
({ response: any, file: FileItem, fileList: Array<FileItem> }) => afterUploadResult
afterUpload 在上传完成后(xhr.onload)且没有发生错误的情况下触发,需返回一个 Object 对象(不支持异步返回),具体结构如下

// afterUploadResult:
{
    status?: 'success' | 'uploadFail' | 'validateFail' | 'validating' | 'uploading' | 'wait';
    // 文件的校验信息,需要节点时使用 renderValidateMessage
    validateMessage?: string;
    // 是否从fileList中移除该文件,默认为false
    autoRemove?: boolean;
    // 文件的名称
    name?: string;
    // 预览文件的url,一般为当次上传请求中 Server 接收到文件后返回的存储地址
    url?: string
}
代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { type AfterUploadProps, Button, Upload } from '@fibrous/semi-ui-solid';

const afterUpload = ({ response }: AfterUploadProps) => {
    if (response.status_code === 200) {
        return {
            autoRemove: false,
            status: 'uploadFail',
            validateMessage: '内容不合法',
            name: 'RenameByServer.jpg',
            url: '/assets/root-web-sites/edit-bag.jpeg',
        };
    }
    return {};
};

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" afterUpload={afterUpload}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

自定义请求

当传入 customRequest 时, 相当于使用的自定义的请求方法替换了 upload 内置的 xhr 请求,用户需要自行接管上传行为。
可在入参中获取到当前操作的 file 对象,用户自行实现上传过程,并且在适当的时候调用 customRequest 入参中的 onProgress、onError、onSuccess 以更新 Upload 组件内部状态进而驱动 UI 更新
customRequest 包含以下入参

{
    // 当前文件名称
    fileName: string,
    // 用户设置的props.data
    data: object,
    // FileItem,具体结构参考下面的文档
    file: FileItem,
    // original File Object which extends Blob, 浏览器实际获取到的文件对象(https://developer.mozilla.org/zh-CN/docs/Web/API/File)
    fileInstance: File,
    // 上传过程中应调用的函数,event需要包含 total、loaded属性
    onProgress: (event: { total: number, loaded: number }) => any,
    // 上传出错时应调用的函数
    onError: (userXhr: { status?: number }, e?: Event) => any,
    // 上传成功后应调用的函数, response为上传成功后的请求结果
    onSuccess: (response: any, e?: Event) => any,
    // 用户设置的props.withCredentials
    withCredentials: boolean,
    // 用户设置的props.action
    action: string,
}
代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, type UploadCustomRequestArgs, Upload } from '@fibrous/semi-ui-solid';

const mockRequest = ({ onProgress, onSuccess }: UploadCustomRequestArgs) => {
    let count = 0;
    const interval = setInterval(() => {
        if (count === 100) {
            clearInterval(interval);
            onSuccess({});
            return;
        }
        onProgress({ total: 100, loaded: count });
        count += 20;
    }, 500);
};

export default function UploadDemo() {
    return (
        <Upload action="https://api.semi.design/upload" customRequest={mockRequest}>
            <Button icon={<IconUpload />} theme="light">
                点击上传
            </Button>
        </Upload>
    );
}

图片裁切

通过 crop 属性启用图片裁切功能。支持点击选择、拖拽、粘贴、替换文件时进行裁切。

基本用法

裁切前会先按 accept 与 limit 过滤,被拒绝或超出数量的图片不会进入裁切。浏览器只能编码 PNG、JPEG、WebP,其他格式(GIF、BMP、SVG)的裁切结果为 PNG,文件的 type 与扩展名随之变为 image/png 与 .png。

设置 crop={true} 启用默认裁切配置。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            crop
            onSuccess={(response, file) => {
                console.log('上传成功:', response, file);
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                点击上传(启用裁切)
            </Button>
        </Upload>
    );
}

自定义裁切配置

通过对象形式配置裁切参数,包括宽高比、形状、质量等。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            crop={{
                aspectRatio: 16 / 9,
                shape: 'rect',
                quality: 0.8,
                modalTitle: '裁切图片',
                modalOkText: '确认裁切',
                modalCancelText: '取消',
            }}
            onSuccess={(response, file) => {
                console.log('上传成功:', response, file);
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                点击上传(16:9 裁切)
            </Button>
        </Upload>
    );
}

圆形裁切

适用于头像上传场景,设置 shape: 'round' 启用圆形裁切。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            crop={{
                aspectRatio: 1,
                shape: 'round',
                quality: 0.9,
                modalTitle: '裁切头像',
            }}
            onSuccess={(response, file) => {
                console.log('上传成功:', response, file);
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                点击上传(圆形裁切)
            </Button>
        </Upload>
    );
}

裁切前确认

通过 beforeCrop 回调,在裁切前进行确认或其他处理。返回 false 可跳过裁切直接上传。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            crop={{
                aspectRatio: 1,
                shape: 'round',
            }}
            beforeCrop={(file, fileList) => {
                console.log('beforeCrop:', file, fileList);
                // true continues cropping; false skips it and uploads directly
                return window.confirm('是否裁切图片?');
            }}
            onCropError={(error) => {
                console.error('裁切失败:', error);
            }}
            onSuccess={(response, file) => {
                console.log('上传成功:', response, file);
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                点击上传(裁切前确认)
            </Button>
        </Upload>
    );
}

拖拽上传裁切

拖拽上传时也会触发裁切功能。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            crop={{
                aspectRatio: 1,
                shape: 'rect',
            }}
            draggable
            onDrop={(_e, files) => {
                console.log('拖拽文件:', files);
            }}
            onSuccess={(response, file) => {
                console.log('上传成功:', response, file);
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                拖拽上传(启用裁切)
            </Button>
        </Upload>
    );
}

自定义裁切弹窗样式

通过 cropModalProps 自定义裁切弹窗的样式和属性。弹窗的 width 缺省为 600,传入的值优先。

cropModalProps 的类型为 UploadCropModalProps,不含 visible、title、okText、cancelText、onOk、onCancel:标题和按钮文字用 crop 的 modalTitle、modalOkText、modalCancelText 配置,其余由组件内置。

代码
import { IconUpload } from '@fibrous/semi-icons-solid';
import { Button, Upload } from '@fibrous/semi-ui-solid';

export default function UploadDemo() {
    return (
        <Upload
            action="https://api.semi.design/upload"
            crop={{
                aspectRatio: 1,
                shape: 'rect',
            }}
            cropModalProps={{
                width: 800,
                style: { height: '600px' },
                bodyStyle: { height: '500px', 'background-color': '#f5f5f5' },
            }}
            onSuccess={(response, file) => {
                console.log('上传成功:', response, file);
            }}
        >
            <Button icon={<IconUpload />} theme="light">
                自定义弹窗样式
            </Button>
        </Upload>
    );
}

API 参考


Upload

Props

属性说明类型默认值
accept接受上传的文件类型,写法同 input 的 accept:文件后缀(如 .jpg)或 MIME type,用逗号分隔。string-
action文件上传地址,必填。string-
addOnPasting粘贴(paste 事件)时是否把剪贴板中的文件加入文件列表,无需额外授权。boolean-
afterUpload上传完成且没有出错后的钩子,根据返回的对象更新文件状态、校验信息、名称和预览地址;不支持异步返回。Type(object: AfterUploadProps) => AfterUploadResult-
beforeClear清空文件前的回调,返回 false、resolve(false) 或 reject 会阻止清空。Type(fileList: Array<FileItem>) => boolean | Promise<boolean>-
beforeCrop图片裁切前的回调,只用第一张选中的图片调用一次,结果决定整批是否裁切。返回 false 跳过裁切直接上传,支持异步返回。Type(file: File, fileList: File[]) => boolean | Promise<boolean>-
beforeRemove移除文件前的回调,返回 false、resolve(false) 或 reject 会阻止移除。Type(file: FileItem, fileList: Array<FileItem>) => boolean | Promise<boolean>-
beforeUpload上传文件前的钩子,根据返回的对象更新文件状态并控制是否上传;返回 boolean 表示通过或失败,返回 Promise 时 resolve 为通过、reject 为失败。Type(object: BeforeUploadProps) => BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> | boolean-
capture媒体拍摄的方式,可选 user、environment;传 true 输出空值属性,传 false 不输出该属性。boolean | 'user' | 'environment'-
children触发区内容。拖拽模式下替换整个默认拖拽区内容。JSX.Element-
crop启用图片裁切。传 true 使用默认配置,传对象自定义裁切参数;点击选择、拖拽、粘贴、替换都会裁切。boolean | CropProps-
cropModalProps裁切弹窗 Modal 的属性;visible、title、okText、cancelText、onOk、onCancel 由组件内置,width 缺省为 600。UploadCropModalProps-
customRequest替换内置 XMLHttpRequest 的自定义请求方法,需自行调用入参的 onProgress、onError、onSuccess 更新状态。Type(object: customRequestArgs) => void-
data上传时附带的额外参数,或根据文件返回额外参数的函数。TypeRecord<string, any> | ((file: File) => Record<string, unknown>)-
defaultFileList初始的已上传文件列表,非受控。每项的 uid 必填且不能重复。TypeArray<FileItem>[]
directory是否上传文件夹。boolean-
disabled是否禁用。禁用后不再显示移除、重试、替换和清空操作。booleanfalse
draggable是否支持拖拽上传。boolean-
dragIcon拖拽区左侧的图标,不传时显示默认图标。JSX.Element-
dragMainText拖拽区的主文本,不传时显示语言包文案。JSX.Element-
dragSubText拖拽区的帮助文本。JSX.Element-
fileList受控的文件列表。传入后 Upload 即为受控组件,需在 onChange 中回写新数组。每项的 uid 必填且不能重复。TypeArray<FileItem>-
fileListTitle文件列表的标题区域。传入 JSX 时只替换标题文字,清空按钮保持默认;传入函数时可完全自定义标题区(含清空按钮),参数为 fileList、onClear、clearText。TypeJSX.Element | ((props: RenderFileListTitleProps) => JSX.Element)-
fileName作用与 name 相同,主要在 Form.Upload 中使用,避免与 Field 的 name 冲突。string-
headers上传时附带的 headers,或根据文件返回 headers 的函数。TypeRecord<string, any> | ((file: File) => Record<string, string>)-
hotSpotLocation照片墙添加按钮的位置,可选 start 或 end。'start' | 'end'"end"
itemStyle文件卡片的内联样式,对象形式,键使用 CSS 属性名。JSX.CSSProperties-
limit最多允许上传的文件个数。为 1 时始终用最新选择的文件替换当前文件,不触发 onExceed。number-
listType文件列表的展示类型,可选 list、picture、none;none 不渲染文件列表,只保留触发区。UploadListType"list"
maxSize文件体积上限,单位 KB。number-
minSize文件体积下限,单位 KB。number-
multiple是否允许一次选择多个文件。booleanfalse
name上传时使用的文件字段名。string-
onAcceptInvalid接收到的文件不符合 accept 时的回调,参数为不符合的文件(一般是选择文件夹或拖入了格式不符的文件)。Type(files: File[]) => void-
onChange文件状态变化时调用,包括上传中、成功、失败。参数为 { fileList, currentFile },通过 insert 插入文件时 currentFile 为 null。Type(object: OnChangeProps) => void-
onClear点击清空时的回调。Type() => void-
onCropError图片裁切失败时的回调,参数为错误对象。Type(error: Error) => void-
onDrop拖拽的元素在拖拽区上释放时调用,参数为原生 drop 事件、拖入的文件和当前文件列表。Type(e: DragEvent, files: Array<File>, fileList: Array<FileItem>) => void-
onError上传出错时的回调。使用 customRequest 时,第二个参数里的 xhr 是使用方传给 customRequest 的 onError 的对象。Type(e: CustomError, file: CustomFile, fileList: Array<FileItem>, xhr: XMLHttpRequest | { status?: number }) => void-
onExceed文件总数超出 limit 时的回调,参数为截断前本次选中的原始文件。Type(fileList: Array<File>) => void-
onFileChange选中文件后的回调,参数为选中的文件。Type(files: Array<File>) => void-
onOpenFileDialog打开系统文件选择窗口时的回调。Type() => void-
onPastingErroraddOnPasting 为 true 时,读取剪贴板中的文件失败(getAsFile() 返回 null 或抛错)的回调。Type(error: Error) => void-
onPreviewClick点击文件卡片时的回调。传入后文件卡片可点击,并显示预览按钮;是否传入会改变渲染。Type(fileItem: FileItem) => void-
onProgress上传过程中的回调,参数为百分比、文件和当前文件列表。Type(percent: number, file: CustomFile, fileList: Array<FileItem>) => void-
onRemove移除文件后的回调。来自 defaultFileList 且没有 File 对象的项,currentFile 为 undefined。Type(currentFile: CustomFile | undefined, fileList: Array<FileItem>, currentFileItem: FileItem) => void-
onRetry点击重试时的回调,参数为文件。Type(fileItem: FileItem) => void-
onSizeError文件体积不符合 maxSize 或 minSize 时的回调。Type(file: CustomFile, fileList: Array<FileItem>) => void-
onSuccess上传成功后的回调,参数为服务端响应、文件和当前文件列表。Type(responseBody: any, file: CustomFile, fileList: Array<FileItem>) => void-
picHeight照片墙模式下图片的展示高度。string | number-
picWidth照片墙模式下图片的展示宽度。string | number-
previewFile自定义预览逻辑,返回的内容替换原缩略图,只在 list 模式下有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
prompt提示文本。与直接写在 children 中不同,点击它不会触发上传。JSX.Element-
promptPosition提示文本的位置,可选 left、right、bottom。list 模式相对触发区,picture 模式相对图片列表。PromptPositionType"right"
ref根元素,挂载 insert、upload、openFileDialog 方法,用于命令式调用。TypeRef<UploadElement>-
renderFileItem自定义文件卡片的渲染,参数为 RenderFileItemProps。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderFileOperation自定义列表项的操作区,只在 list 模式下有效。Type(fileItem: RenderFileItemProps) => JSX.Element-
renderPicClose自定义照片墙的关闭按钮,参数为 class 和 remove,只在照片墙模式下有效。Type(renderPicCloseProps: RenderPictureCloseProps) => JSX.Element-
renderPicInfo自定义照片墙信息,只在照片墙模式且 showPicInfo 为 true 时有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderPicPreviewIcon自定义照片墙悬停时的预览图标,只在照片墙模式下有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderThumbnail自定义照片墙缩略图,只在照片墙模式下有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderValidateMessage自定义文件卡片的校验信息,参数为 RenderFileItemProps;渲染为空时回落到文件的 validateMessage。照片墙只为带 validateMessage 的文件显示校验图标,它替换图标提示中的文字。Type(fileItem: RenderFileItemProps) => JSX.Element-
showClearlist 模式下,limit 不为 1 且至少有一个文件时,是否展示清空按钮。booleantrue
showPicInfo是否显示图片信息,只在照片墙模式下有效。booleanfalse
showReplace上传成功后,文件卡片内是否展示替换按钮。booleanfalse
showRetry上传失败时,文件卡片内是否展示重试按钮。booleantrue
showTooltip文件名过长时是否展示提示。传对象时可配置 type(tooltip 或 popover)、透传给浮层的 opts,以及自定义弹出层的 renderTooltip。boolean | ShowTooltiptrue
showUploadList是否显示文件列表。booleantrue
timeout不生效:组件不读取该属性。number-
transformFile选中文件后、上传前的转换函数,返回要上传的 File。Type(file: File) => File-
uploadTrigger触发上传的时机,auto 选中后自动上传,custom 需调用 ref 上的 upload 方法。'auto' | 'custom'"auto"
validateMessageUpload 整体的校验信息,JSX 元素。JSX.Element-
validateStatus校验状态,影响根元素的样式,可选 default、error、warning、success。ValidateStatus-
withCredentials上传请求是否携带 Cookie。booleanfalse

Methods

方法说明类型
insert上传文件,当 index 传入时插入到指定位置,不传则插入到最后。Type(files: Array<CustomFile>, index?: number) => void
openFileDialog打开文件选择窗口。Type() => void
upload手动开始上传,配合 uploadTrigger 为 custom 使用。Type() => void

class、style、aria-* 等未声明的属性和事件落在根元素上,ref 取得根元素;根元素带 aria-label 或 aria-labelledby 时自动加 role="group"。

Upload.FileCard

Upload.FileCard 是默认的文件卡片,用于在 renderFileItem 中保留默认外观:<Upload.FileCard {...props} />。data-*、aria-* 落在根元素上。

Props

属性说明类型默认值
_sizeInvalid内部标记,文件大小超出限制时由组件设置,Upload 据此显示大小不合法的提示,无需手动传入。boolean-
disabled是否禁用,禁用后不显示移除、重试和替换按钮。boolean-
eventxhr 事件。Event-
fileInstance浏览器实际获取到的文件对象,继承自 Blob。File-
index文件在列表中的位置。number-
listType卡片的展示类型,可选 list、picture、none。UploadListType-
name文件名。string-
onPreviewClick点击卡片时的回调。传入后卡片可点击,并显示预览按钮;是否传入会改变渲染。Type() => void-
onRemove点击移除时的回调。Type() => void-
onReplace点击替换时的回调。Type() => void-
onRetry点击重试时的回调。Type() => void-
percent上传进度百分比。number-
picHeight照片墙模式下图片的高度。string | number-
picWidth照片墙模式下图片的宽度。string | number-
preview是否根据 url 进行预览。boolean-
previewFile自定义预览逻辑,返回的内容替换原缩略图,只在 list 模式下有效。Type(fileItem: RenderFileItemProps) => JSX.Element-
renderFileOperation自定义列表项的操作区。Type(fileItem: RenderFileItemProps) => JSX.Element-
renderPicClose自定义照片墙的关闭按钮,参数为 class 和 remove,只在照片墙模式下有效。Type(renderPicCloseProps: RenderPictureCloseProps) => JSX.Element-
renderPicInfo自定义照片墙信息,只在照片墙模式下有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderPicPreviewIcon自定义照片墙悬停时的预览图标,只在照片墙模式下有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderThumbnail自定义照片墙缩略图,只在照片墙模式下有效。Type(renderFileItemProps: RenderFileItemProps) => JSX.Element-
renderValidateMessage自定义校验信息,渲染为空时回落到 validateMessage。照片墙只在有 validateMessage 时显示校验图标,它替换图标提示中的文字。Type(fileItem: RenderFileItemProps) => JSX.Element-
responsexhr 的响应。请求成功时为响应体,失败时为对应的错误。any-
shouldUpload是否应该继续上传。boolean-
showPicInfo是否显示图片信息,只在照片墙模式下有效。boolean-
showReplace上传成功后是否展示替换按钮,文件自身的 showReplace 优先。boolean-
showRetry上传失败时是否展示重试按钮,文件自身的 showRetry 优先。boolean-
showTooltip文件名过长时是否展示提示,或配置提示的类型和透传属性。boolean | ShowTooltip-
size文件大小,单位 KB。string-
status文件状态,可选 success、uploadFail、validateFail、validating、uploading、wait。FileItemStatus-
style文件卡片的内联样式,对象形式,键使用 CSS 属性名。JSX.CSSProperties-
uid文件唯一标识符,列表内必须唯一。string-
url文件地址,图片墙用它展示图片,预览时使用。string-
validateMessage校验信息的文字;需要节点时使用 renderValidateMessage。string-

Interfaces

FileItem Interface

interface FileItem {
    event?: Event; // xhr 事件
    fileInstance?: File; // 浏览器实际获取到的文件对象,继承自 Blob(https://developer.mozilla.org/zh-CN/docs/Web/API/File)
    name: string;
    percent?: number; // 上传进度百分比
    preview?: boolean; // 是否根据url进行预览
    response?: any; // xhr的response, 请求成功时为response body,请求失败时为对应 error
    shouldUpload?: boolean; // 是否应该继续上传
    showReplace?: boolean; // 单独控制该file是否展示替换按钮
    showRetry?: boolean; // 单独控制该file是否展示重试按钮
    size: string; // 文件大小,单位kb
    status: 'success' | 'uploadFail' | 'validateFail' | 'validating' | 'uploading' | 'wait';
    uid: string; // 文件唯一标识符,如果当前文件是通过upload选中添加的,会自动生成uid。如果是defaultFileList, 需要自行保证不会重复
    url?: string;
    validateMessage?: string; // 校验信息,需要节点时使用 renderValidateMessage
}

FileItem 只包含上述字段,写入未声明的字段会报类型错误。

RenderFileItemProps Interface

renderFileItem、renderThumbnail、renderPicInfo、renderPicPreviewIcon、renderFileOperation、renderValidateMessage、previewFile 收到的参数,在 FileItem 之上增加文件卡片的属性。

interface RenderFileItemProps extends FileItem {
    index?: number; // 文件在列表中的位置
    listType?: 'picture' | 'list' | 'none';
    disabled?: boolean;
    style?: JSX.CSSProperties; // 来自 itemStyle
    showRetry?: boolean;
    showReplace?: boolean;
    showPicInfo?: boolean;
    showTooltip?: boolean | ShowTooltip;
    picWidth?: string | number;
    picHeight?: string | number;
    onRemove?: () => void; // 移除该文件
    onRetry?: () => void; // 重新上传该文件
    onReplace?: () => void; // 替换该文件
    onPreviewClick?: () => void; // 点击文件卡片时的回调
    previewFile?: (renderProps: RenderFileItemProps) => JSX.Element;
    renderPicInfo?: (renderProps: RenderFileItemProps) => JSX.Element;
    renderPicPreviewIcon?: (renderProps: RenderFileItemProps) => JSX.Element;
    renderPicClose?: (renderPicCloseProps: { class: string; remove: (e: MouseEvent) => void }) => JSX.Element;
    renderThumbnail?: (renderProps: RenderFileItemProps) => JSX.Element;
    renderFileOperation?: (renderProps: RenderFileItemProps) => JSX.Element;
    renderValidateMessage?: (renderProps: RenderFileItemProps) => JSX.Element; // 渲染为空时回落到 validateMessage
}

RenderFileListTitleProps Interface

fileListTitle 传入函数时的参数类型:

interface RenderFileListTitleProps {
    fileList: Array<FileItem>; // 当前文件列表
    onClear: () => void; // 清空文件的回调函数
    clearText: string; // 清空按钮的默认文案(根据当前语言环境)
}

CropProps Interface

crop 属性传入对象时的配置项:

interface CropProps {
    aspectRatio?: number; // 裁切框宽高比,例如 16/9, 1, 4/3
    shape?: 'rect' | 'round' | 'roundRect'; // 裁切框形状,默认 'rect'
    minZoom?: number; // 最小缩放比例
    maxZoom?: number; // 最大缩放比例
    zoomStep?: number; // 缩放步长
    quality?: number; // 输出图片质量 0-1,默认 0.92
    fill?: string; // 填充颜色,默认透明 'rgba(0, 0, 0, 0)'
    modalTitle?: string; // 裁切弹窗标题
    modalOkText?: string; // 确认按钮文字
    modalCancelText?: string; // 取消按钮文字
}

Methods

绑定在组件根元素上的方法,可以通过 ref 调用实现某些特殊交互

名称描述类型
insert上传文件,当index传入时,会插入到指定位置,不传则插入到最后(files: Array<File>, index?: number) => void
upload手动开始上传,配合uploadTrigger="custom"使用() => void
openFileDialog打开文件选择窗口() => void

Accessibility

Upload组件是一个可交互的控件,在点击或拖拽时触发文件选择,文件选中后会在文件列表内展示状态。

ARIA

  • 触发区是 role="button",Enter 与 Space 可激活。触发区内容自带控件(如 Button)时,触发区不加角色,由该控件充当按钮,点击它打开文件选择。该控件被禁用(原生 disabled 或 aria-disabled="true")时仍算使用方自己的控件,触发区不会变成按钮,Enter 与 Space 也不会打开文件选择
  • 禁用 Upload 时请同时给该控件传 disabled。达到 limit 时只禁用该控件、不禁用 Upload,用户仍可移除已上传的文件
  • 根元素带 aria-label 或 aria-labelledby 时自动加 role="group",使用方显式传了 role 时以使用方为准
  • 文件列表添加 role="list",并用 aria-label 描述;照片墙的添加按钮在列表之外
  • 列表模式下的替换按钮直接作为 Tooltip 的触发器,Tab 键只停靠一次

文案规范

  • 上传按钮
  • 帮助文本
    • 帮助文本使用语句书写规范,首字母大写,可以不需要句号
  • 用户出错提示
    • 清晰地告诉用户为什么文件无法被上传,并且告知用户如何操作能够成功上传
    • 帮助文本使用语句书写规范,首字母大写
    • 简洁的用语让用户能够一眼读懂,比如 File size must be less than 20MB, File type must be .gif, .jpg, .png or .svg

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$color-upload-textvar(--semi-color-text-0)上传文件卡片等默认文本颜色
$color-upload_assist-textvar(--semi-color-text-2)上传文件列表标题及文件大小文本颜色
$color-upload-bordervar(--semi-color-border)图片墙添加按钮及拖拽区域描边颜色
$color-upload_card-bg-hovervar(--semi-color-fill-1)上传文件卡片背景色 - 悬浮
$color-upload_card-bgvar(--semi-color-fill-0)上传文件卡片背景色 - 默认
$color-upload_card_fail-bg-hovervar(--semi-color-danger-light-hover)上传失败文件卡片背景色 - 悬浮
$color-upload_card_fail-bgvar(--semi-color-danger-light-default)上传失败文件卡片背景色 - 默认
$color-upload_clear-textvar(--semi-color-primary)上传清空按钮文本颜色
$color-upload_drag_area-bg-hovervar(--semi-color-primary-light-default)上传可拖拽区域背景颜色 - 悬浮
$color-upload_drag_area-border-hovervar(--semi-color-primary)上传可拖拽区域描边颜色 - 悬浮
$color-upload_drag_area_disabled-textvar(--semi-color-disabled-text)上传可拖拽区域禁用文本颜色
$color-upload_drag_area-bgvar(--semi-color-tertiary-light-default)上传可拖拽区域背景颜色 - 默认
$color-upload_drag_area_iconvar(--semi-color-primary)上传可拖拽区域图标颜色
$color-upload_drag_area_tips-textvar(--semi-color-primary)上传可拖拽区域提示文本颜色
$color-upload_drag_area_main-text-defaultvar(--semi-color-text-0)上传可拖拽区主要提示文本颜色 - 默认
$color-upload_drag_area_main-text-hovervar(--semi-color-text-0)上传可拖拽区主要提示文本颜色 - 悬浮
$color-upload_drag_area_main-text-activevar(--semi-color-text-0)上传可拖拽区主要提示文本颜色 - 按下
$color-upload_drag_area_sub-text-defaultvar(--semi-color-text-0)上传可拖拽区次要提示文本颜色 - 默认
$color-upload_drag_area_sub-text-hovervar(--semi-color-text-0)上传可拖拽区次要提示文本颜色 - 悬浮
$color-upload_drag_area_sub-text-activevar(--semi-color-text-0)上传可拖拽区次要提示文本颜色 - 按下
$color-upload_file_card_fail_info-textvar(--semi-color-danger)上传文件卡片失败提示信息文本颜色
$color-upload_file_card_preview_placeholder-bgvar(--semi-grey-3)文件卡片默认预览背景颜色
$color-upload_file_card_preview_placeholder-textvar(--semi-white)文件卡片默认预览图颜色
$color-upload_file_card_retry-bgvar(--semi-color-white)图片墙上传卡片重新上传按钮背景颜色
$color-upload_file_card_retry-textvar(--semi-color-primary)图片墙上传卡片重新上传按钮文本颜色
$color-upload-iconvar(--semi-color-tertiary)图片墙上传图标加号颜色
$color-upload_pic_add-bg-activevar(--semi-color-fill-2)图片墙上传背景色 - 按下
$color-upload_pic_add-bg-hovervar(--semi-color-fill-1)图片墙上传背景色 - 悬浮
$color-upload_pic_add-bgvar(--semi-color-fill-0)图片墙上传背景色 - 默认
$color-upload_picture_file_card_loading_error-iconvar(--semi-color-danger)图片墙上传卡片加载及失败状态图标颜色
$color-upload_picture_file_card_error-bordervar(--semi-color-danger)图片墙上传卡片失败状态描边颜色
$color-upload_picture_file_card_pic_info-textvar(--semi-color-white)图片墙图片信息(序号)文字颜色
$color-upload_picture_file_card_close-iconvar(--semi-color-white)图片墙关闭图标颜色
$color-upload_picture_file_card_hover-bgvar(--semi-color-overlay-bg)图片墙预览悬浮背景色
$color-upload_preview-iconvar(--semi-color-text-2)上传文件卡片文本颜色
$color-upload_retry-textvar(--semi-color-primary)上传文件卡片重新上传按钮文本颜色
$color-upload_replace-textvar(--semi-color-white)图片墙上传卡片替换按钮文本颜色
$height-upload_file_card52px上传文件卡片高度
$width-upload_file_card250px上传文件卡片宽度
$height-upload_file_pic_card96px图片墙上传卡片高度
$width-upload_file_pic_card96px图片墙上传卡片宽度
$width-upload_file_card_preview36px文件卡片预览图标宽度
$height-upload_file_card_preview36px文件卡片预览图标高度
$width-upload_file_card_preview_img36px文件卡片预览图片宽度
$width-upload_file_card-icon11px文件卡片图标宽度
$width-upload_picture_add-border2px图片墙添加按钮描边宽度
$width-upload_picture_file_card_img96px图片墙上传卡片宽度
$width-upload_picture_file_card_close16px图片墙上传卡片删除按钮图标宽度
$width-upload_file_card_retry24px重新上传按钮宽度
$width-upload_file_card_retry-icon13px重新上传按钮图标宽度
$width-upload_picture_file_card_loading-icon14px上传加载 spin 宽度
$width-upload_drag_area-border2px可拖拽上传描边宽度
$spacing-upload_title_choosen-marginRight$spacing-tight已选择文件标题右侧外边距
$spacing-upload_title-marginBottom$spacing-extra-tight文件列表标题底部外边距
$spacing-upload_file_card_preview-margin$spacing-tight上传文件卡片预览外边距
$spacing-upload_file_card_info_size-marginLeft$spacing-tight上传文件卡片文件大小文本左侧外边距
$spacing-upload_file_card_info_retry-marginLeft$spacing-tight上传文件卡片重新上传按钮左侧外边距
$spacing-upload_file_card_info_progress-marginTop4px上传文件卡片进度顶部外边距
$spacing-upload_file_card_close-marginLeft$spacing-tight上传文件卡片删除按钮左侧外边距
$spacing-upload_file_card_close-marginRight$spacing-tight上传文件卡片删除按钮右侧外边距
$spacing-upload_file_card_icon-marginRight$spacing-super-tight上传文件卡片图标右侧外边距
$spacing-upload_picture_file_card-gap$spacing-tight图片墙卡片之间边距
$spacing-upload_picture_file_card-marginBottom$spacing-tight图片墙上传卡片底部外边距
$spacing-upload_picture_file_card_close-top8px图片墙上传卡片删除按钮顶部位置
$spacing-upload_picture_file_card_close-right8px图片墙上传卡片删除右侧位置
$spacing-upload_picture_file_card_loading_error-bottom6px图片墙上传卡片加载中图标底部位置
$spacing-upload_picture_file_card_loading_error-right6px图片墙上传卡片报错图标底部位置
$spacing-upload_picture_file_card_icon_error-top-1px图片墙上传卡片加载中图标顶部位置
$spacing-upload_drag_area-padding$spacing-base-tight可拖拽上传拖拽区域内边距
$spacing-upload_drag_area_main_text-marginBottom$spacing-extra-tight可拖拽上传拖拽标题底部外边距
$radius-upload_file_cardvar(--semi-border-radius-medium)上传文件卡片圆角
$radius-upload_file_card_previewvar(--semi-border-radius-small)上传文件卡片预览图圆角
$radius-upload_picture_addvar(--semi-border-radius-small)图片墙上传文件添加按钮圆角
$radius-upload_picture_file_card_imgvar(--semi-border-radius-small)图片墙上传卡片圆角
$radius-upload_picture_file_card_closevar(--semi-border-radius-circle)图片墙上传卡片删除按钮圆角
$radius-upload_file_card_retryvar(--semi-border-radius-circle)上传文件卡片重新上传圆角
$radius-upload_drag_areavar(--semi-border-radius-small)可拖拽上传拖拽区域圆角
$font-upload_file_card_info_name-fontWeight$font-weight-bold上传文件卡片文件名字重
$font-upload_file_card_info_size-fontWeight$font-weight-regular上传文件卡片文件尺寸字重
$font-upload_drag_area_tips-fontWeight600可拖拽上传提示文本字重
$font-upload_picture_file_card_pic_info-fontSize12px图片墙图片信息字体大小
$font-upload_picture_file_card_pic_info-fontWeight600图片墙图片信息文本字重

FAQ

  • 什么时候会展示重试按钮?
    • 当 showRetry 为 true,且当前文件是由于网络原因错误导致的上传失败时,会展示重试按钮。其他如校验失败,上传成功等状态是不会展示重试按钮的。
  • 什么时候会展示替换按钮?
    • 当 showReplace 为true,且当前文件状态为已上传时,会展示替换按钮。
  • Semi Upload把图片存到哪里了?
    • Semi Upload不负责图片的保存,当你使用 Upload 组件时需要自定义 action。你可以选择把 action 设置为自己的服务器地址或者图片服务地址。
  • Form.Upload props.name无效?
    • Form.Field 中有 props.name,Upload也有 props.name,同名 props 会冲突。使用 Form.Upload 时,可以转为使用 props.fileName,避免冲突
  • 上传图片后没有调用 XXX 方法?
    • 如果你设置了 accept,可以尝试把 accept 属性去掉,然后再看是否调用了改方法。去掉后调用了该方法说明,accept 在当前环境下获取的 file type 与设置的 accept 不符,上传行为提前终止。可以打个断点到 upload/foundation.js checkFileFormat 函数,看下获取的 file.type 真实值是否符合预期。