输入类

上传

文件选择上传

代码演示

如何引入

<script lang="ts">
    import { Upload } from '@fibrae/semi-ui-svelte';
</script>

基本

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';
</script>

<Upload action="https://api.semi.design/upload">
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

文件名超长省略

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

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';
</script>

<Upload action="https://api.semi.design/upload" showTooltip={false}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

通过 ellipsisTooltip snippet 可以自定义弹出层,参数为 content(完整文件名)与 trigger(文件名元素,用 {@render trigger()} 放进弹出层);showTooltip 传对象时可配置提示的类型与透传属性

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Tooltip, Upload } from '@fibrae/semi-ui-svelte';
</script>

<Upload action="https://api.semi.design/upload" showTooltip>
    {#snippet ellipsisTooltip({ content: text, trigger })}
        <Tooltip position="bottom">
            {#snippet content()}{text}{/snippet}
            {@render trigger()}
        </Tooltip>
    {/snippet}
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

添加提示文本

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

请上传资格认证材料
请上传资格认证材料
请上传资格认证材料
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type PromptPositionType } from '@fibrae/semi-ui-svelte';

    const action = 'https://api.semi.design/upload';
    const marginStyle: Partial<Record<PromptPositionType, string>> = {
        left: 'margin-right: 10px',
        right: 'margin-left: 10px',
    };
    const promptStyle = (pos: PromptPositionType) => `display: flex; align-items: center; color: var(--semi-color-text-2); height: 32px; ${marginStyle[pos] ?? ''}`;
    const positions: PromptPositionType[] = ['right', 'left', 'bottom'];
</script>

{#each positions as pos, index (pos)}
    {#if index}
        <div style="margin-bottom: 12px; margin-top: 12px; border-bottom: 1px solid var(--semi-color-border)"></div>
    {/if}
    <Upload {action} promptPosition={pos}>
        {#snippet prompt()}
            <div style={promptStyle(pos)}>请上传资格认证材料</div>
        {/snippet}
        <Button theme="light">
            {#snippet icon()}<IconUpload />{/snippet}
            点击上传
        </Button>
    </Upload>
{/each}

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

请上传认证材料
dy.jpeg
resso.jpeg
请上传认证材料
dy.jpeg
resso.jpeg
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Upload, type FileItem, type PromptPositionType } from '@fibrae/semi-ui-svelte';

    const action = 'https://api.semi.design/upload';
    const marginStyle: Partial<Record<PromptPositionType, string>> = {
        right: 'margin-left: 10px',
    };
    const promptStyle = (pos: PromptPositionType) => `display: flex; align-items: center; color: var(--semi-color-text-2); height: 100%; ${marginStyle[pos] ?? ''}`;
    const initialList = (): 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'];
</script>

{#each positions as pos, index (pos)}
    {#if index}
        <div style="margin-bottom: 12px; margin-top: 12px; border-bottom: 1px solid var(--semi-color-border)"></div>
    {/if}
    <Upload {action} fileList={initialList()} listType="picture" promptPosition={pos}>
        {#snippet prompt()}
            <div style={promptStyle(pos)}>请上传认证材料</div>
        {/snippet}
        <IconPlus aria-label="上传图片" size="extra-large" />
    </Upload>
{/each}

点击头像触发上传

上传头像
代码
<script lang="ts">
    import { IconCamera } from '@fibrae/semi-icons-svelte';
    import { Avatar, Toast, Upload } from '@fibrae/semi-ui-svelte';

    let url = $state('/assets/placeholder/avatar-a.svg');

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

    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)';
</script>

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

自定义上传属性

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';

    // 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',
    };
</script>

<Upload action="https://api.semi.design/upload" {data} {headers}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </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 通过,连接起来即可)。

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';

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

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

上传文件夹

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';
</script>

<Upload action="https://api.semi.design/upload" directory>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        上传文件夹
    </Button>
</Upload>

一次选中多个文件

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';
</script>

<Upload action="https://api.semi.design/upload" multiple>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

限制文件总数量

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type UploadOnChangeProps } from '@fibrae/semi-ui-svelte';

    const limit = 1;
    const onChange = (props: UploadOnChangeProps) => {
        console.log(props.fileList);
    };
</script>

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

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Toast, Upload, type UploadOnChangeProps } from '@fibrae/semi-ui-svelte';

    const limit = 2;
    // Only the trigger button is disabled; Upload itself stays enabled so files can still be removed.
    let disabled = $state(false);
    const onChange = (props: UploadOnChangeProps) => {
        disabled = props.fileList.length === limit;
    };
</script>

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

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

dyBag.png
dyBag2.jpeg
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Toast, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    const fileList: 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',
        },
    ];
</script>

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

限制上传文件大小

通过 maxSize 和 minSize 属性可以自定义上传文件大小的限制,通过 onSizeError 可以在超出限制时得到通知。

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Toast, Upload, type CustomFile } from '@fibrae/semi-ui-svelte';

    const onSizeError = (file: CustomFile) => Toast.error(`${file.name} size invalid`);
</script>

<Upload action="https://api.semi.design/upload" maxSize={1024} minSize={200} {onSizeError}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传(最小 200KB,最大 1MB)
    </Button>
</Upload>

自定义列表操作区

listType 为 list 时,可以通过 fileOperation snippet 自定义列表操作区,参数为 UploadFileItemSlotProps

已选择文件
dyBag.png 130KB
代码
<script lang="ts">
    import { IconDelete, IconDownload, IconEyeOpened, IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    const action = 'https://api.semi.design/upload';
    const fileList: FileItem[] = [
        {
            uid: '1',
            name: 'dyBag.png',
            status: 'success',
            size: '130KB',
            preview: true,
            url: '/assets/root-web-sites/edit-bag.jpeg',
        },
    ];
</script>

<Upload {action} {fileList} itemStyle="width: 300px">
    {#snippet fileOperation({ onRemove })}
        <div style="display: flex; column-gap: 8px; padding: 0 8px">
            <Button aria-label="预览" size="small" theme="borderless" type="tertiary">
                {#snippet icon()}<IconEyeOpened />{/snippet}
            </Button>
            <Button aria-label="下载" size="small" theme="borderless" type="tertiary">
                {#snippet icon()}<IconDownload />{/snippet}
            </Button>
            <Button aria-label="删除" size="small" theme="borderless" type="tertiary" onclick={() => onRemove()}>
                {#snippet icon()}<IconDelete />{/snippet}
            </Button>
        </div>
    {/snippet}
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

自定义文件列表标题

listType 为 list 时,可以自定义文件列表顶部的标题区域,分为两个 snippet:

只替换标题文字

fileListTitle 仅替换标题文字,清空按钮保持默认样式:

已上传文件
document.pdf 130KB
report.xlsx 222KB
📁 重要文件
document.pdf 130KB
report.xlsx 222KB
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

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

    const initialList = (): FileItem[] => [
        {
            uid: '1',
            name: 'document.pdf',
            status: 'success',
            size: '130KB',
        },
        {
            uid: '2',
            name: 'report.xlsx',
            status: 'success',
            size: '222KB',
        },
    ];
</script>

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

替换整个标题区

fileListHeader 替换整个标题区域,包括清空按钮。提供该 snippet 后,即使它渲染为空也不回落到默认标题。snippet 参数如下:

  • fileList: 当前文件列表
  • onClear: 清空文件的函数
  • clearText: 清空按钮的默认文案(根据当前语言环境)
📂 共 2 个文件
document.pdf 130KB
report.xlsx 222KB
已选择 2 个文件
document.pdf 130KB
report.xlsx 222KB
代码
<script lang="ts">
    import { IconClose, IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

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

    const initialList = (): FileItem[] => [
        {
            uid: '1',
            name: 'document.pdf',
            status: 'success',
            size: '130KB',
        },
        {
            uid: '2',
            name: 'report.xlsx',
            status: 'success',
            size: '222KB',
        },
    ];
</script>

<Upload {action} fileList={initialList()} style="margin-bottom: 20px">
    {#snippet fileListHeader({ 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" onclick={onClear}>
                {#snippet icon()}<IconClose />{/snippet}
                {clearText}
            </Button>
        </div>
    {/snippet}
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        自定义清空按钮样式
    </Button>
</Upload>
<Upload {action} fileList={initialList()}>
    {#snippet fileListHeader({ 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 style="padding: 0; border: none; background: none; font: inherit; cursor: pointer; color: var(--semi-color-link); font-size: 12px" type="button" onclick={onClear}>全部移除</button>
        </div>
    {/snippet}
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        完全自定义标题区域
    </Button>
</Upload>

自定义预览逻辑

listType 为 list 时,可以通过 previewFile snippet 自定义预览逻辑,snippet 内容会替换原缩略图,渲染为空时也不回落到默认缩略图
例如你不需要对图片类型进行缩略图预览时,可以在 previewFile 中恒定渲染一个 <IconFile />
假如你希望点击图片时放大预览,则可以在 previewFile 中使用 Image 组件
或者你希望使用额外的操作区来实现点击放大预览,你也可以结合 fileOperation 放置一些自定义元素例如 Icon 图标实现点击放大

已选择文件
dyBag.png 130KB
dyBag2.png 130KB
代码
<script lang="ts">
    import { IconFile, IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Image, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

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

<Upload action="https://api.semi.design/upload" {fileList}>
    {#snippet previewFile({ file })}
        {#if file.uid === '1'}
            <IconFile size="large" />
        {:else}
            <Image alt="" src={file.url} />
        {/if}
    {/snippet}
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

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

已选择文件
dyBag.png 130KB
代码
<script lang="ts">
    import { IconDelete, IconDownload, IconExpand, IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, ImagePreview, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    let visible = $state(false);
    const fileList: FileItem[] = [
        {
            uid: '1',
            name: 'dyBag.png',
            status: 'success',
            size: '130KB',
            preview: true,
            url: '/assets/root-web-sites/edit-bag.jpeg',
        },
    ];
</script>

<Upload action="https://api.semi.design/upload" {fileList} itemStyle="width: 300px">
    {#snippet fileOperation({ file, onRemove })}
        <div style="display: flex; column-gap: 8px; padding: 0 8px">
            <Button aria-label="预览" size="small" theme="borderless" type="tertiary" onclick={() => (visible = true)}>
                {#snippet icon()}<IconExpand />{/snippet}
            </Button>
            <Button aria-label="下载" size="small" theme="borderless" type="tertiary">
                {#snippet icon()}<IconDownload />{/snippet}
            </Button>
            <Button aria-label="删除" size="small" theme="borderless" type="tertiary" onclick={() => onRemove()}>
                {#snippet icon()}<IconDelete />{/snippet}
            </Button>
            <ImagePreview bind:visible src={file.url} />
        </div>
    {/snippet}
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

默认文件列表

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

已选择文件
dyBag.jpeg 130KB
dyBag2.jpeg 222KB
上传失败
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    const fileList: 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',
        },
    ];
</script>

<Upload action="https://api.semi.design/upload" {fileList}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

受控组件

使用 bind:fileList 绑定文件列表,Upload 在列表变化时回写。只传 fileList 的值而不绑定时,该值作为初始列表,之后由 Upload 自己维护。onChange 仍会收到 { fileList, currentFile },其中 currentFile 在通过 insert 方法插入文件后为 null。

已选择文件
dyBag.jpeg 130KB
dy.jpeg 222KB
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type FileItem, type UploadOnChangeProps } from '@fibrae/semi-ui-svelte';

    let list = $state<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',
        },
    ]);

    const onChange = ({ fileList, currentFile }: UploadOnChangeProps) => {
        console.log('change', fileList, currentFile);
    };
</script>

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

图片墙

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

dy.png
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Upload, type FileItem } from '@fibrae/semi-ui-svelte';

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

<Upload accept="image/*" action="https://api.semi.design/upload" {fileList} listType="picture" multiple>
    <IconPlus aria-label="上传图片" size="extra-large" />
</Upload>

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

music.png
1
brand.png
2
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Upload, type FileItem } from '@fibrae/semi-ui-svelte';

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

<Upload accept="image/*" action="https://api.semi.design/upload" {fileList} listType="picture" multiple showPicInfo>
    <IconPlus aria-label="上传图片" size="extra-large" />
</Upload>

图片墙放大预览

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

music.png
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Image, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    const action = 'https://api.semi.design/upload';
    const fileList: FileItem[] = [
        {
            uid: '1',
            name: 'music.png',
            status: 'success',
            size: '130KB',
            preview: true,
            url: '/assets/placeholder/avatar-b.svg',
        },
    ];
</script>

<Upload accept="image/*" {action} {fileList} listType="picture" multiple>
    {#snippet thumbnail({ file })}
        <Image alt={file.name} src={file.url} />
    {/snippet}
    <IconPlus aria-label="Upload picture" size="extra-large" />
</Upload>

可以通过 picPreviewIcon snippet、onPreviewClick 来自定义预览图标,当显示替换图标 showReplace 时,不会再显示预览图标
当需要自定义预览/替换功能时,需要关闭替换功能,使用 onPreviewClick 监听点击即可。
onPreviewClick 监听的是单张图片容器的点击事件。传入它会同时让文件卡片可点击,并显示预览按钮

resso.png
1
代码
<script lang="ts">
    import { IconEyeOpened, IconPlus } from '@fibrae/semi-icons-svelte';
    import { Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    const fileList: 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');
    };
</script>

<Upload accept="image/*" action="https://api.semi.design/upload" {fileList} listType="picture" multiple onPreviewClick={handlePreview} showPicInfo>
    {#snippet picPreviewIcon()}
        <IconEyeOpened style="color: var(--semi-color-white); font-size: 24px" />
    {/snippet}
    <IconPlus aria-label="上传图片" size="extra-large" />
</Upload>

图片墙设置宽高

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

image-1.jpg
点击添加图片
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Image, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

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

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

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


resso.png
1
代码
<script lang="ts">
    import { IconPlus } from '@fibrae/semi-icons-svelte';
    import { Radio, RadioGroup, Upload, type FileItem, type UploadProps } from '@fibrae/semi-ui-svelte';

    const action = 'https://api.semi.design/upload';
    const fileList: 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');
    };
    let hotSpotLocation = $state<UploadProps['hotSpotLocation']>('end');
</script>

<RadioGroup aria-label="添加按钮位置" bind:value={hotSpotLocation} type="button">
    <Radio value="start">start</Radio>
    <Radio value="end">end</Radio>
</RadioGroup>
<hr />
<Upload accept="image/*" {action} {fileList} {hotSpotLocation} listType="picture" multiple onPreviewClick={handlePreview} showPicInfo>
    <IconPlus aria-label="上传图片" size="extra-large" />
</Upload>

禁用

已选择文件
first.png 130KB
second.png 222KB
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type FileItem } from '@fibrae/semi-ui-svelte';

    const fileList: 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',
        },
    ];
</script>

<Upload action="https://api.semi.design/upload" disabled {fileList}>
    <Button disabled theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

手动触发上传

uploadTrigger="custom",选中文件后将不会自动触发上传。需要通过 bind:this 取得实例,调用其 upload 方法触发

代码
<script lang="ts">
    import { IconPlus, IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type UploadHandle } from '@fibrae/semi-ui-svelte';

    let upload = $state<UploadHandle>();
    const action = 'https://api.semi.design/upload';
</script>

<div>
    <Upload accept="image/gif, image/png, image/jpeg, image/bmp, image/webp" {action} bind:this={upload} onError={(...v) => console.log(...v)} onSuccess={(...v) => console.log(...v)} uploadTrigger="custom">
        <Button style="margin-inline-end: 8px" theme="light">
            {#snippet icon()}<IconPlus />{/snippet}
            选择文件
        </Button>
    </Upload>
    <Button theme="light" onclick={() => upload?.upload()}>
        {#snippet icon()}<IconUpload />{/snippet}
        开始上传
    </Button>
</div>

拖拽上传

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

点击上传文件或拖拽文件到这里
支持任意类型文件
代码
<script lang="ts">
    import { Upload } from '@fibrae/semi-ui-svelte';
</script>

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

可以通过 dragIcon、dragMainText、dragSubText snippet 快捷设置拖拽区内容。dragIcon、dragMainText 渲染为空时显示默认图标和文案

点击上传文件或拖拽文件到这里
仅支持jpeg、pdf
代码
<script lang="ts">
    import { IconBolt } from '@fibrae/semi-icons-svelte';
    import { Upload } from '@fibrae/semi-ui-svelte';
</script>

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

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

demo img
Wow, you can really dance.
代码
<script lang="ts">
    import { IconBolt } from '@fibrae/semi-icons-svelte';
    import { Upload } from '@fibrae/semi-ui-svelte';
</script>

<Upload accept="application/pdf,.jpeg" action="https://api.semi.design/upload" draggable style="margin-top: 10px">
    {#snippet dragIcon()}
        <IconBolt />
    {/snippet}
    <div class="components-upload-demo-drag-area">
        <img alt="demo img" height="96" src="/assets/placeholder/avatar-a.svg" 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
也可以点击或拖拽上传
代码
<script lang="ts">
    import { Toast, Upload } from '@fibrae/semi-ui-svelte';
</script>

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

上传前自定义校验

可通过 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, // 文件的校验信息,需要节点时使用 fileValidateMessage snippet
    shouldUpload: boolean, // 是否需要上传。默认为true,如果为false,该fileItem只会被展示在列表中,不会触发上传操作
    autoRemove: boolean, // 是否从fileList中移除该文件,默认为false
}
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type BeforeUploadObjectResult, type BeforeUploadProps } from '@fibrae/semi-ui-svelte';

    let count = 0;

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

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

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

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type BeforeUploadObjectResult, type BeforeUploadProps } from '@fibrae/semi-ui-svelte';

    let count = 0;

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

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

上传后更新文件信息

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

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

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

<Upload action="https://api.semi.design/upload" {afterUpload}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </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,
}
代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type UploadCustomRequestArgs } from '@fibrae/semi-ui-svelte';

    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);
    };
</script>

<Upload action="https://api.semi.design/upload" customRequest={mockRequest}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传
    </Button>
</Upload>

图片裁切

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

基本用法

设置 crop 启用默认裁切配置(crop={true} 或布尔简写 crop)。

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';
</script>

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

自定义裁切配置

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type UploadCropProps } from '@fibrae/semi-ui-svelte';

    const crop: UploadCropProps = {
        aspectRatio: 16 / 9,
        shape: 'rect',
        quality: 0.8,
        modalTitle: '裁切图片',
        modalOkText: '确认裁切',
        modalCancelText: '取消',
    };
</script>

<Upload action="https://api.semi.design/upload" {crop} onSuccess={(response, file) => console.log('上传成功:', response, file)}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传(16:9 裁切)
    </Button>
</Upload>

圆形裁切

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';

    const crop = {
        aspectRatio: 1,
        shape: 'round',
        quality: 0.9,
        modalTitle: '裁切头像',
    } as const;
</script>

<Upload action="https://api.semi.design/upload" {crop} onSuccess={(response, file) => console.log('上传成功:', response, file)}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传(圆形裁切)
    </Button>
</Upload>

裁切前确认

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload, type UploadProps } from '@fibrae/semi-ui-svelte';

    const crop = { aspectRatio: 1, shape: 'round' } as const;

    const beforeCrop: UploadProps['beforeCrop'] = (file, fileList) => {
        console.log('beforeCrop:', file, fileList);
        // true continues cropping; false skips it and uploads directly
        return window.confirm('是否裁切图片?');
    };
</script>

<Upload action="https://api.semi.design/upload" {beforeCrop} {crop} onCropError={(error) => console.error('裁切失败:', error)} onSuccess={(response, file) => console.log('上传成功:', response, file)}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        点击上传(裁切前确认)
    </Button>
</Upload>

拖拽上传裁切

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';

    const crop = {
        aspectRatio: 1,
        shape: 'rect',
    } as const;
</script>

<Upload action="https://api.semi.design/upload" {crop} draggable onDrop={(_e, files) => console.log('拖拽文件:', files)} onSuccess={(response, file) => console.log('上传成功:', response, file)}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        拖拽上传(启用裁切)
    </Button>
</Upload>

自定义裁切弹窗样式

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

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

代码
<script lang="ts">
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Upload } from '@fibrae/semi-ui-svelte';

    const crop = {
        aspectRatio: 1,
        shape: 'rect',
    } as const;

    const cropModalProps = {
        width: 800,
        style: 'height: 600px',
        bodyStyle: 'height: 500px; background-color: #f5f5f5',
    };
</script>

<Upload action="https://api.semi.design/upload" {crop} {cropModalProps} onSuccess={(response, file) => console.log('上传成功:', response, file)}>
    <Button theme="light">
        {#snippet icon()}<IconUpload />{/snippet}
        自定义弹窗样式
    </Button>
</Upload>

API 参考


Upload

Props

属性说明类型默认值
accept接受上传的文件类型,写法同 input 的 accept:文件后缀(如 .jpg)或 MIME type,用逗号分隔。string-
action文件上传地址,必填。string-
addOnPasting粘贴(paste 事件)时是否把剪贴板中的文件加入文件列表,无需额外授权。booleanfalse
afterUpload上传完成且没有出错后的钩子,根据返回的对象更新文件状态、校验信息、名称和预览地址;不支持异步返回。Type(object: AfterUploadProps) => AfterUploadResult-
beforeClear清空文件前的回调,返回 false、resolve(false) 或 reject 会阻止清空。Type(fileList: FileItem[]) => boolean | Promise<boolean>-
beforeCrop图片裁切前的回调,只用第一张选中的图片调用一次,结果决定整批是否裁切。返回 false 跳过裁切直接上传,支持异步返回。Type(file: File, fileList: File[]) => boolean | Promise<boolean>-
beforeRemove移除文件前的回调,返回 false、resolve(false) 或 reject 会阻止移除。Type(file: FileItem, fileList: 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触发区内容。拖拽模式下替换整个默认拖拽区内容。Snippet-
crop启用图片裁切。传 true 使用默认配置,传对象自定义裁切参数;点击选择、拖拽、粘贴、替换都会裁切。boolean | UploadCropPropsfalse
cropModalProps裁切弹窗 Modal 的属性;visible、title、okText、cancelText、onOk、onCancel、children 由组件内置,style、bodyStyle 为样式字符串。UploadCropModalProps-
customRequest替换内置 XMLHttpRequest 的自定义请求方法,需自行调用入参的 onProgress、onError、onSuccess 更新状态。Type(object: UploadCustomRequestArgs) => void-
data上传时附带的额外参数,或根据文件返回额外参数的函数。TypeRecord<string, any> | ((file: File) => Record<string, unknown>)-
directory是否上传文件夹。booleanfalse
disabled是否禁用。禁用后不再显示移除、重试、替换和清空操作。booleanfalse
draggable是否支持拖拽上传。booleanfalse
dragIcon拖拽区的图标,渲染为空时显示默认图标。Snippet-
dragMainText拖拽区的主文本,渲染为空时显示语言包文案。Snippet-
dragSubText拖拽区的帮助文本。Snippet-
ellipsisTooltip自定义文件名过长时的弹出内容,参数为 content 和 trigger,trigger 是需要放进弹层的文件名元素。TypeSnippet<[TypographyEllipsisTooltipProps]>-
fileItem替换每张文件卡片,参数为 UploadFileItemSlotProps;渲染 <UploadFileCard {...props} /> 即默认卡片;对某个文件渲染为空时不显示该文件。TypeSnippet<[UploadFileItemSlotProps]>-
fileList文件列表,可用 bind:fileList 双向绑定;只传值时作为初始列表,之后由组件自己维护。每项的 uid 必填且不能重复。FileItem[]-
fileListHeader替换整个标题区(含清空按钮),参数为 fileList、onClear、clearText。TypeSnippet<[UploadFileListHeaderSlotProps]>-
fileListTitle只替换文件列表的标题文字,保留清空按钮。Snippet-
fileName作用与 name 相同,主要在 Form.Upload 中使用,避免与 Field 的 name 冲突。string-
fileOperation自定义列表项的操作区。TypeSnippet<[UploadFileItemSlotProps]>-
fileValidateMessage自定义文件卡片的校验信息,参数中带 validateMessage。TypeSnippet<[UploadFileItemSlotProps]>-
headers上传时附带的 headers,或根据文件返回 headers 的函数。TypeRecord<string, any> | ((file: File) => Record<string, string>)-
hotSpotLocation照片墙添加按钮的位置,可选 start 或 end。'start' | 'end'"end"
itemStyle文件卡片的样式字符串。string-
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文件状态变化时调用,包括上传中、成功、失败。currentFile 在 insert 后为 null,清空时不带。这是 Upload 自己的回调,不是隐藏 input 的原生 change。Type(object: UploadOnChangeProps) => void-
onClear点击清空后调用。Type() => void-
onCropError图片裁切失败时调用。Type(error: Error) => void-
onDrop拖拽的元素在拖拽区上释放时调用,参数为原生 drop 事件、拖入的文件和当前文件列表。Type(e: DragEvent, files: File[], fileList: FileItem[]) => void-
onError上传出错时调用。使用 customRequest 时,xhr 是使用方传给 onError 的对象。Type(e: UploadCustomError, file: CustomFile, fileList: FileItem[], xhr: XMLHttpRequest | { status?: number }) => void-
onExceed文件总数超出 limit 时调用,参数为截断前本次选中的原始文件。Type(fileList: File[]) => void-
onFileChange选中文件后调用。Type(files: File[]) => void-
onOpenFileDialog打开系统文件选择窗口时调用。Type() => void-
onPastingErroraddOnPasting 为 true 且读取剪贴板中的文件失败时调用。Type(error: Error) => void-
onPreviewClick传入后文件卡片可点击,并显示预览按钮;是否传入会改变渲染。Type(fileItem: FileItem) => void-
onProgress上传过程中调用,参数为百分比、文件和当前文件列表。Type(percent: number, file: CustomFile, fileList: FileItem[]) => void-
onRemove移除文件后调用。来自初始列表且没有 File 对象的项,currentFile 为 undefined。Type(currentFile: CustomFile | undefined, fileList: FileItem[], currentFileItem: FileItem) => void-
onRetry点击重试时调用。Type(fileItem: FileItem) => void-
onSizeError文件体积不符合 maxSize 或 minSize 时调用。Type(file: CustomFile, fileList: FileItem[]) => void-
onSuccess上传成功后调用,参数为服务端响应、文件和当前文件列表。Type(responseBody: any, file: CustomFile, fileList: FileItem[]) => void-
picClose自定义照片墙的关闭按钮,参数为 class 和 onRemove,只在照片墙模式下有效。TypeSnippet<[UploadPicCloseSlotProps]>-
picHeight照片墙模式下图片的展示高度。string | number-
picInfo自定义照片墙信息,只在照片墙模式且 showPicInfo 为 true 时有效。TypeSnippet<[UploadFileItemSlotProps]>-
picPreviewIcon自定义照片墙悬停时的预览图标,只在照片墙模式下有效。TypeSnippet<[UploadFileItemSlotProps]>-
picWidth照片墙模式下图片的展示宽度。string | number-
previewFile自定义预览逻辑,替换原缩略图,只在 list 模式下有效;渲染为空时不回落默认缩略图。TypeSnippet<[UploadFileItemSlotProps]>-
prompt提示文本。与写在 children 中不同,点击它不会触发上传。Snippet-
promptPosition提示文本的位置,可选 left、right、bottom。list 模式相对触发区,picture 模式相对图片列表。PromptPositionType"right"
showClearlist 模式下,limit 不为 1 且至少有一个文件时,是否展示清空按钮。booleantrue
showPicInfo是否显示图片信息,只在照片墙模式下有效。booleanfalse
showReplace上传成功后,文件卡片内是否展示替换按钮。booleanfalse
showRetry上传失败时,文件卡片内是否展示重试按钮。booleantrue
showTooltip文件名过长时是否展示提示,或配置提示的类型(tooltip 或 popover)和透传属性;自定义弹出内容使用 ellipsisTooltip snippet。boolean | ShowTooltiptrue
showUploadList是否显示文件列表。booleantrue
thumbnail自定义照片墙缩略图,只在照片墙模式下有效。TypeSnippet<[UploadFileItemSlotProps]>-
transformFile选中文件后、上传前的转换函数,返回要上传的 File。Type(file: File) => File-
uploadTrigger触发上传的时机,auto 选中后自动上传,custom 需调用 upload 方法。'auto' | 'custom'"auto"
validateMessageUpload 整体的校验信息。Snippet-
validateStatus校验状态,影响根元素的样式,可选 default、error、warning、success。ValidateStatus-
withCredentials上传请求是否携带 Cookie。booleanfalse

Methods

方法说明类型
insert在 index 位置插入文件,不传 index 则插入到末尾。Type(files: CustomFile[], index?: number) => void
openFileDialog打开文件选择窗口。Type() => void
upload开始上传等待中的文件,配合 uploadTrigger 为 custom 使用。Type() => void

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

Upload.FileCard

Upload.FileCard(也可单独导入为 UploadFileCard)是默认的文件卡片,用于在 fileItem 等 snippet 中保留默认外观:<Upload.FileCard {...props} />。class、style、data-*、aria-* 落在根元素上。

Props

属性说明类型默认值
disabled是否禁用,禁用后不显示移除、重试和替换按钮。boolean-
ellipsisTooltip自定义文件名过长时的弹出内容,参数为 content 和 trigger。TypeSnippet<[TypographyEllipsisTooltipProps]>-
file要展示的文件,必填。FileItem-
fileOperation自定义列表项的操作区。TypeSnippet<[UploadFileItemSlotProps]>-
fileValidateMessage自定义校验信息,参数中带 validateMessage。TypeSnippet<[UploadFileItemSlotProps]>-
index文件在列表中的位置。number-
listType卡片的展示类型,可选 list、picture、none;none 不渲染卡片。UploadListType"list"
onPreviewClick传入后卡片可点击,并显示预览按钮;是否传入会改变渲染。Type() => void-
onRemove点击移除时调用。Type() => void-
onReplace点击替换时调用。Type() => void-
onRetry点击重试时调用。Type() => void-
picClose自定义照片墙的关闭按钮,参数为 class 和 onRemove,只在照片墙模式下有效。TypeSnippet<[UploadPicCloseSlotProps]>-
picHeight照片墙模式下图片的高度。string | number-
picInfo自定义照片墙信息,只在照片墙模式且 showPicInfo 为 true 时有效。TypeSnippet<[UploadFileItemSlotProps]>-
picPreviewIcon自定义照片墙悬停时的预览图标,只在照片墙模式下有效。TypeSnippet<[UploadFileItemSlotProps]>-
picWidth照片墙模式下图片的宽度。string | number-
previewFile自定义预览逻辑,替换原缩略图,只在 list 模式下有效。TypeSnippet<[UploadFileItemSlotProps]>-
showPicInfo是否显示图片信息,只在照片墙模式下有效。boolean-
showReplace上传成功后是否展示替换按钮,文件自身的 showReplace 优先。boolean-
showRetry上传失败时是否展示重试按钮,文件自身的 showRetry 优先。boolean-
showTooltip文件名过长时是否展示提示,或配置提示的类型和透传属性。boolean | ShowTooltiptrue
thumbnail自定义照片墙缩略图,只在照片墙模式下有效。TypeSnippet<[UploadFileItemSlotProps]>-
validateMessage校验信息,优先于文件自身的 validateMessage。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, 请求成功时为respoonse body,请求失败时为对应 error
    shouldUpload?: boolean; // 是否应该继续上传
    showReplace?: boolean; // 单独控制该file是否展示替换按钮
    showRetry?: boolean; // 单独控制该file是否展示重试按钮
    size: string; // 文件大小,单位kb
    status: 'success' | 'uploadFail' | 'validateFail' | 'validating' | 'uploading' | 'wait';
    uid: string; // 文件唯一标识符,如果当前文件是通过upload选中添加的,会自动生成uid。如果是初始列表,需要自行保证不会重复
    url?: string;
    validateMessage?: string; // 只能是字符串,需要节点时使用 fileValidateMessage snippet
}

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

UploadFileItemSlotProps Interface

fileItem、thumbnail、picInfo、picPreviewIcon、fileOperation、previewFile、fileValidateMessage 收到的参数。文件数据放在 file 字段中,另有文件卡片的属性。可以直接写 <Upload.FileCard {...props} /> 渲染默认卡片。

interface UploadFileItemSlotProps {
    file: FileItem;
    index?: number; // 文件在列表中的位置
    listType: 'picture' | 'list' | 'none';
    disabled?: boolean;
    showRetry?: boolean;
    showReplace?: boolean;
    showPicInfo?: boolean;
    showTooltip?: boolean | ShowTooltip;
    picWidth?: string | number;
    picHeight?: string | number;
    validateMessage?: string;
    style?: string; // 来自 itemStyle
    onRemove: () => void; // 移除该文件
    onRetry: () => void; // 重新上传该文件
    onReplace: () => void; // 替换该文件
    onPreviewClick?: () => void; // 仅在传入了 Upload 的 onPreviewClick 时存在
}

UploadPicCloseSlotProps Interface

picClose 收到的参数:

interface UploadPicCloseSlotProps {
    class: string;
    onRemove: (e: MouseEvent) => void;
}

UploadFileListHeaderSlotProps Interface

fileListHeader 收到的参数:

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

CropProps Interface

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

Svelte 中该类型的导出名为 UploadCropProps。

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

Upload 通过 export function 暴露 insert、upload、openFileDialog,用 bind:this 取得实例后调用,类型为 UploadHandle,说明见上方 Upload 的 Methods 表。

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 真实值是否符合预期。