代码演示
如何引入
<script setup lang="ts">
import { Upload } from '@fibrous/semi-ui-vue';
</script>
基本
最基本的用法,在触发区内放置一个 Button,点击它激活文件选择框,选择完成后自动开始上传
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>文件名超长省略
通过 showTooltip 属性,自定义设置文件名弹出提示
当类型为 boolean 时,控制是否弹出提示
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" :showTooltip="false">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>通过 #ellipsisTooltip 插槽可以自定义弹出层,插槽参数为 content(完整文件名)与 trigger(文件名元素,用 <component :is="trigger" /> 放进弹出层);showTooltip 传对象时可配置提示的类型与透传属性
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Tooltip, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" showTooltip>
<template #ellipsisTooltip="{ content, trigger }">
<Tooltip :content="content" position="bottom">
<component :is="trigger" />
</Tooltip>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>添加提示文本
通过 prompt 插槽,设置自定义提示文本
通过 promptPosition 设置插槽位置,可选 left、right、bottom,默认为 right
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type PromptPositionType } from '@fibrous/semi-ui-vue';
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>
<template>
<template v-for="(pos, index) in positions" :key="pos">
<div v-if="index" style="margin-bottom: 12px; margin-top: 12px; border-bottom: 1px solid var(--semi-color-border)" />
<Upload :action="action" :promptPosition="pos">
<template #prompt>
<div :style="promptStyle(pos)">请上传资格认证材料</div>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>
</template>当 listType 为 picture 时,promptPosition 位置的参照对象为图片墙列表整体
代码
<script setup lang="ts">
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Upload, type FileItem, type PromptPositionType } from '@fibrous/semi-ui-vue';
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 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'];
</script>
<template>
<template v-for="(pos, index) in positions" :key="pos">
<div v-if="index" style="margin-bottom: 12px; margin-top: 12px; border-bottom: 1px solid var(--semi-color-border)" />
<Upload :action="action" :defaultFileList="defaultFileList" listType="picture" :promptPosition="pos">
<template #prompt>
<div :style="promptStyle(pos)">请上传认证材料</div>
</template>
<IconPlus aria-label="上传图片" size="extra-large" />
</Upload>
</template>
</template>点击头像触发上传
代码
<script setup lang="ts">
import { ref } from 'vue';
import { IconCamera } from '@fibrous/semi-icons-vue';
import { Avatar, Toast, Upload } from '@fibrous/semi-ui-vue';
const url = ref('/assets/placeholder/avatar-a.svg');
const onSuccess = () => {
Toast.success('头像更新成功');
url.value = '/assets/ttmoment.jpeg';
};
const maskStyle = {
backgroundColor: 'var(--semi-color-overlay-bg)',
height: '100%',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--semi-color-white)',
};
</script>
<template>
<Upload accept="image/*" action="https://api.semi.design/upload" class="avatar-upload" :showUploadList="false" @error="Toast.error('上传失败')" @success="onSuccess">
<!-- the avatar is the whole content of the upload button, so its alt names the button -->
<Avatar alt="上传头像" :src="url" :style="{ margin: '4px' }">
<template #hoverMask>
<div :style="maskStyle">
<IconCamera />
</div>
</template>
</Avatar>
</Upload>
</template>.avatar-upload .semi-upload-add {
border-radius: 50%; // 确保只有圆是点击热区
}
自定义上传属性
通过设置 data、headers 可添加自定义上传属性
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
// 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>
<template>
<Upload action="https://api.semi.design/upload" :data="data" :headers="headers">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>上传文件类型
通过 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 setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
const action = 'https://api.semi.design/upload';
const imageOnly = 'image/*';
const videoOnly = 'video/*';
const fileLimit = '.pdf,.png,.jpeg';
</script>
<template>
<Upload :accept="imageOnly" :action="action" :style="{ marginBottom: '12px' }">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
上传图片
</Button>
</Upload>
<Upload :accept="videoOnly" :action="action" :style="{ marginBottom: '12px' }">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
上传视频
</Button>
</Upload>
<Upload :accept="fileLimit" :action="action">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
上传 PDF, PNG, JPEG
</Button>
</Upload>
</template>上传文件夹
通过传入 directory 为 true,可以支持上传文件夹下的所有文件
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" directory>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
上传文件夹
</Button>
</Upload>
</template>一次选中多个文件
通过设置 multiple 属性可以支持同时选中多个文件上传。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" multiple>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>限制文件总数量
通过设置 limit 属性可以限制最大可上传的文件数
当 limit 为1时,始终用最新上传的代替当前,并不会触发 exceed 事件
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type UploadOnChangeProps } from '@fibrous/semi-ui-vue';
const limit = 1;
const onChange = (props: UploadOnChangeProps) => {
console.log(props.fileList);
};
</script>
<template>
<Upload action="https://api.semi.design/upload" :limit="limit" @change="onChange">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(最多{{ limit }}项)
</Button>
</Upload>
</template>达到上限时禁用触发控件而不是 Upload,这样仍能移除已上传的文件。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Toast, Upload, type UploadOnChangeProps } from '@fibrous/semi-ui-vue';
const limit = 2;
// Only the trigger button is disabled; Upload itself stays enabled so files can still be removed.
const disabled = ref(false);
const onChange = (props: UploadOnChangeProps) => {
disabled.value = props.fileList.length === limit;
};
</script>
<template>
<Upload action="https://api.semi.design/upload" :limit="limit" @change="onChange" @exceed="Toast.warning(`最多只允许上传${limit}个文件`)">
<Button :disabled="disabled" theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(最多{{ limit }}项)
</Button>
</Upload>
</template>照片墙模式下,当已上传文件数量等于 limit 时,会自动隐藏上传入口


代码
<script setup lang="ts">
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Toast, Upload, type FileItem } from '@fibrous/semi-ui-vue';
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',
},
];
</script>
<template>
<Upload accept="image/*" action="https://api.semi.design/upload" :defaultFileList="defaultFileList" :limit="2" listType="picture" @exceed="Toast.warning('最多只允许上传2个文件')">
<IconPlus aria-label="上传图片" size="extra-large" />
</Upload>
</template>限制上传文件大小
通过 maxSize 和 minSize 属性可以自定义上传文件大小的限制,通过监听 @sizeError 可以在超出限制时得到通知。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Toast, Upload, type CustomFile } from '@fibrous/semi-ui-vue';
const onSizeError = (file: CustomFile) => Toast.error(`${file.name} size invalid`);
</script>
<template>
<Upload action="https://api.semi.design/upload" :maxSize="1024" :minSize="200" @sizeError="onSizeError">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(最小 200KB,最大 1MB)
</Button>
</Upload>
</template>自定义列表操作区
listType 为 list 时,可以通过 #fileOperation 插槽自定义列表操作区,参数为 UploadFileItemSlotProps

代码
<script setup lang="ts">
import { IconDelete, IconDownload, IconEyeOpened, IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type FileItem } from '@fibrous/semi-ui-vue';
const action = 'https://api.semi.design/upload';
const defaultFileList: FileItem[] = [
{
uid: '1',
name: 'dyBag.png',
status: 'success',
size: '130KB',
preview: true,
url: '/assets/root-web-sites/edit-bag.jpeg',
},
];
</script>
<template>
<Upload :action="action" :defaultFileList="defaultFileList" :itemStyle="{ width: '300px' }">
<template #fileOperation="{ onRemove }">
<div style="display: flex; column-gap: 8px; padding: 0 8px">
<Button aria-label="预览" size="small" theme="borderless" type="tertiary">
<template #icon>
<IconEyeOpened />
</template>
</Button>
<Button aria-label="下载" size="small" theme="borderless" type="tertiary">
<template #icon>
<IconDownload />
</template>
</Button>
<Button aria-label="删除" size="small" theme="borderless" type="tertiary" @click="onRemove">
<template #icon>
<IconDelete />
</template>
</Button>
</div>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>自定义文件列表标题
listType 为 list 时,可以自定义文件列表顶部的标题区域,分为两个插槽:
只替换标题文字
#fileListTitle 仅替换标题文字,清空按钮保持默认样式:
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type FileItem } from '@fibrous/semi-ui-vue';
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',
},
];
</script>
<template>
<Upload :action="action" :defaultFileList="defaultFileList" style="margin-bottom: 20px">
<template #fileListTitle>已上传文件</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
自定义标题文字
</Button>
</Upload>
<Upload :action="action" :defaultFileList="defaultFileList">
<template #fileListTitle>
<span style="color: var(--semi-color-primary); font-weight: 600">📁 重要文件</span>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
带样式的标题
</Button>
</Upload>
</template>替换整个标题区
#fileListHeader 替换整个标题区域,包括清空按钮。提供该插槽后,即使它渲染为空也不回落到默认标题。插槽参数如下:
fileList: 当前文件列表onClear: 清空文件的函数clearText: 清空按钮的默认文案(根据当前语言环境)
代码
<script setup lang="ts">
import { IconClose, IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type FileItem } from '@fibrous/semi-ui-vue';
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',
},
];
</script>
<template>
<Upload :action="action" :defaultFileList="defaultFileList" style="margin-bottom: 20px">
<template #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" @click="onClear">
<template #icon>
<IconClose />
</template>
{{ clearText }}
</Button>
</div>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
自定义清空按钮样式
</Button>
</Upload>
<Upload :action="action" :defaultFileList="defaultFileList">
<template #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" @click="onClear">全部移除</button>
</div>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
完全自定义标题区域
</Button>
</Upload>
</template>自定义预览逻辑
listType 为 list 时,可以通过 #previewFile 插槽自定义预览逻辑,插槽内容会替换原缩略图,渲染为空时也不回落到默认缩略图
例如你不需要对图片类型进行缩略图预览时,可以在 #previewFile 中恒定渲染一个 <IconFile />
假如你希望点击图片时放大预览,则可以在 #previewFile 中使用 Image 组件
或者你希望使用额外的操作区来实现点击放大预览,你也可以结合 #fileOperation 放置一些自定义元素例如 Icon 图标实现点击放大

代码
<script setup lang="ts">
import { IconFile, IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Image, Upload, type FileItem } from '@fibrous/semi-ui-vue';
const url = '/assets/root-web-sites/edit-bag.jpeg';
const defaultFileList: FileItem[] = [
{ uid: '1', name: 'dyBag.png', status: 'success', size: '130KB', url },
{ uid: '2', name: 'dyBag2.png', status: 'success', size: '130KB', url },
];
</script>
<template>
<Upload action="https://api.semi.design/upload" :defaultFileList="defaultFileList">
<template #previewFile="{ file }">
<IconFile v-if="file.uid === '1'" size="large" />
<Image v-else alt="" :src="file.url" />
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>结合 #fileOperation 与 ImagePreview 的示例,以下示例点击右侧第一个 Icon 可放大图片预览

代码
<script setup lang="ts">
import { ref } from 'vue';
import { IconDelete, IconDownload, IconExpand, IconUpload } from '@fibrous/semi-icons-vue';
import { Button, ImagePreview, Upload, type FileItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const defaultFileList: FileItem[] = [
{
uid: '1',
name: 'dyBag.png',
status: 'success',
size: '130KB',
preview: true,
url: '/assets/root-web-sites/edit-bag.jpeg',
},
];
</script>
<template>
<Upload action="https://api.semi.design/upload" :defaultFileList="defaultFileList" :itemStyle="{ width: '300px' }">
<template #fileOperation="{ file, onRemove }">
<div style="display: flex; column-gap: 8px; padding: 0 8px">
<Button aria-label="预览" size="small" theme="borderless" type="tertiary" @click="visible = true">
<template #icon>
<IconExpand />
</template>
</Button>
<Button aria-label="下载" size="small" theme="borderless" type="tertiary">
<template #icon>
<IconDownload />
</template>
</Button>
<Button aria-label="删除" size="small" theme="borderless" type="tertiary" @click="onRemove()">
<template #icon>
<IconDelete />
</template>
</Button>
<ImagePreview v-model:visible="visible" :src="file.url" />
</div>
</template>
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>默认文件列表
通过 defaultFileList 可以展示已上传的文件。当需要预览默认文件的缩略图时,你可以将 defaultFileList 内对应 item 的 preview 属性设为 true


代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type FileItem } from '@fibrous/semi-ui-vue';
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',
},
];
</script>
<template>
<Upload action="https://api.semi.design/upload" :defaultFileList="defaultFileList">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>受控组件
使用 v-model:fileList 绑定文件列表,Upload 在列表变化时通过 update:fileList 事件回写。只用单向绑定 :fileList 而不回写时,列表保持传入的值。@change 事件仍会收到 { fileList, currentFile },其中 currentFile 在通过 insert 方法插入文件后为 null。

代码
<script setup lang="ts">
import { ref } from 'vue';
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type FileItem, type UploadOnChangeProps } from '@fibrous/semi-ui-vue';
const list = ref<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>
<template>
<Upload v-model:fileList="list" action="https://api.semi.design/upload" :showRetry="false" @change="onChange">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>图片墙
设置 listType = 'picture',用户可以上传图片并在列表中显示缩略图
如果通过 defaultFileList 或 fileList 设置已上传的文件列表时,会自动读取对象数组中的 url 属性用于展示图片
代码
<script setup lang="ts">
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Upload, type FileItem } from '@fibrous/semi-ui-vue';
const defaultFileList: FileItem[] = [
{
uid: '1',
name: 'dy.png',
status: 'success',
size: '130KB',
preview: true,
url: '/assets/placeholder/avatar-a.svg',
},
];
</script>
<template>
<Upload accept="image/*" action="https://api.semi.design/upload" :defaultFileList="defaultFileList" listType="picture" multiple>
<IconPlus aria-label="上传图片" size="extra-large" />
</Upload>
</template>设置 showPicInfo,可以查看图片基础信息
代码
<script setup lang="ts">
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Upload, type FileItem } from '@fibrous/semi-ui-vue';
const url = '/assets/placeholder/avatar-b.svg';
const defaultFileList: 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>
<template>
<Upload accept="image/*" action="https://api.semi.design/upload" :defaultFileList="defaultFileList" listType="picture" multiple showPicInfo>
<IconPlus aria-label="上传图片" size="extra-large" />
</Upload>
</template>图片墙放大预览
配合 Image 组件,通过 #thumbnail 插槽,可以实现点击图片放大预览
代码
<script setup lang="ts">
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Image, Upload, type FileItem } from '@fibrous/semi-ui-vue';
const action = 'https://api.semi.design/upload';
const defaultFileList: FileItem[] = [
{
uid: '1',
name: 'music.png',
status: 'success',
size: '130KB',
preview: true,
url: '/assets/placeholder/avatar-b.svg',
},
];
</script>
<template>
<Upload accept="image/*" :action="action" :defaultFileList="defaultFileList" listType="picture" multiple>
<template #thumbnail="{ file }">
<Image :alt="file.name" :src="file.url" />
</template>
<IconPlus aria-label="Upload picture" size="extra-large" />
</Upload>
</template>可以通过 #picPreviewIcon 插槽,@previewClick 事件来自定义预览图标,当显示替换图标 showReplace 时,不会再显示预览图标
当需要自定义预览/替换功能时,需要关闭替换功能,使用 #picPreviewIcon 监听图标点击事件即可。
@previewClick 监听的是单张图片容器的点击事件。监听它会同时让文件卡片可点击,并显示预览按钮
代码
<script setup lang="ts">
import { IconEyeOpened, IconPlus } from '@fibrous/semi-icons-vue';
import { Upload, type FileItem } from '@fibrous/semi-ui-vue';
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');
};
</script>
<template>
<Upload accept="image/*" action="https://api.semi.design/upload" :defaultFileList="defaultFileList" listType="picture" multiple showPicInfo @previewClick="handlePreview">
<template #picPreviewIcon>
<IconEyeOpened style="color: var(--semi-color-white); font-size: 24px" />
</template>
<IconPlus aria-label="上传图片" size="extra-large" />
</Upload>
</template>图片墙设置宽高
通过设置 picHeight, picWidth,可以统一设置图片墙元素的宽高。如果同时使用 #thumbnail 插槽渲染 Image 组件来实现点击放大预览,你需要同时指定 Image 组件的 width 和 height

代码
<script setup lang="ts">
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Image, Upload, type FileItem } from '@fibrous/semi-ui-vue';
const defaultFileList: FileItem[] = [
{
uid: '1',
name: 'image-1.jpg',
status: 'success',
size: '130KB',
preview: true,
url: '/assets/root-web-sites/abstract.jpg',
},
];
</script>
<template>
<Upload accept="image/*" action="https://api.semi.design/upload" :defaultFileList="defaultFileList" listType="picture" multiple :picHeight="110" :picWidth="200">
<template #thumbnail="{ file }">
<Image :alt="file.name" :height="110" :src="file.url" :width="200" />
</template>
<IconPlus aria-label="上传图片" size="extra-large" style="margin: 4px" />
点击添加图片
</Upload>
</template>设置 hotSpotLocation 自定义点击热区的顺序,默认在照片墙列表结尾
代码
<script setup lang="ts">
import { ref } from 'vue';
import { IconPlus } from '@fibrous/semi-icons-vue';
import { Radio, RadioGroup, Upload, type FileItem, type UploadProps } from '@fibrous/semi-ui-vue';
const action = 'https://api.semi.design/upload';
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');
};
const hotSpotLocation = ref<UploadProps['hotSpotLocation']>('end');
</script>
<template>
<RadioGroup :modelValue="hotSpotLocation" type="button" @update:modelValue="hotSpotLocation = $event as UploadProps['hotSpotLocation']">
<Radio value="start">start</Radio>
<Radio value="end">end</Radio>
</RadioGroup>
<hr />
<Upload accept="image/*" :action="action" :defaultFileList="defaultFileList" :hotSpotLocation="hotSpotLocation" listType="picture" multiple showPicInfo @previewClick="handlePreview">
<IconPlus aria-label="上传图片" size="extra-large" />
</Upload>
</template>禁用
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type FileItem } from '@fibrous/semi-ui-vue';
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',
},
];
</script>
<template>
<Upload action="https://api.semi.design/upload" :defaultFileList="defaultFileList" disabled>
<Button disabled theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>手动触发上传
uploadTrigger="custom",选中文件后将不会自动触发上传。需要通过模板引用调用暴露的 upload 方法触发
代码
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { IconPlus, IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
const uploadRef = useTemplateRef('uploadRef');
const action = 'https://api.semi.design/upload';
</script>
<template>
<div>
<Upload ref="uploadRef" accept="image/gif, image/png, image/jpeg, image/bmp, image/webp" :action="action" uploadTrigger="custom" @error="(...v) => console.log(...v)" @success="(...v) => console.log(...v)">
<Button style="margin-inline-end: 8px" theme="light">
<template #icon>
<IconPlus />
</template>
选择文件
</Button>
</Upload>
<Button theme="light" @click="uploadRef?.upload()">
<template #icon>
<IconUpload />
</template>
开始上传
</Button>
</div>
</template>拖拽上传
draggable='true',可以使用拖拽功能
代码
<script setup lang="ts">
import { Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" draggable>
<template #dragMainText>点击上传文件或拖拽文件到这里</template>
<template #dragSubText>支持任意类型文件</template>
</Upload>
</template>可以通过 #dragIcon、#dragMainText、#dragSubText 插槽快捷设置拖拽区内容。#dragIcon、#dragMainText 渲染为空时显示默认图标和文案
代码
<script setup lang="ts">
import { IconBolt } from '@fibrous/semi-icons-vue';
import { Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload accept="application/pdf,.jpeg" action="https://api.semi.design/upload" draggable style="margin-top: 10px">
<template #dragIcon>
<IconBolt />
</template>
<template #dragMainText>点击上传文件或拖拽文件到这里</template>
<template #dragSubText>仅支持jpeg、pdf</template>
</Upload>
</template>还可以通过默认插槽传入内容,完全自定义拖拽区的显示
代码
<script setup lang="ts">
import { IconBolt } from '@fibrous/semi-icons-vue';
import { Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload accept="application/pdf,.jpeg" action="https://api.semi.design/upload" draggable style="margin-top: 10px">
<template #dragIcon>
<IconBolt />
</template>
<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>
</template>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 事件)的文件会加入文件列表,不需要剪贴板授权。读取粘贴的文件失败时触发 pastingError 事件。被 KeepAlive 失活的 Upload 不响应粘贴。
代码
<script setup lang="ts">
import { Toast, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" addOnPasting draggable @pastingError="Toast.error('读取剪贴板中的文件失败')">
<template #dragMainText>复制截图或文件后,在页面上按 Ctrl/Cmd + V</template>
<template #dragSubText>也可以点击或拖拽上传</template>
</Upload>
</template>上传前自定义校验
可通过 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 插槽
shouldUpload: boolean, // 是否需要上传。默认为true,如果为false,该fileItem只会被展示在列表中,不会触发上传操作
autoRemove: boolean, // 是否从fileList中移除该文件,默认为false
}
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type BeforeUploadObjectResult, type BeforeUploadProps } from '@fibrous/semi-ui-vue';
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>
<template>
<Upload action="https://api.semi.design/upload" :beforeUpload="beforeUpload" :transformFile="transformFile">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(上传前同步校验)
</Button>
</Upload>
</template>异步校验时,需返回 Promise,Promise resolve 代表检验通过,reject 代表校验失败,不会触发上传。
resolve/reject 时可以传入 object(结构同上 beforeUploadResult)
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type BeforeUploadObjectResult, type BeforeUploadProps } from '@fibrous/semi-ui-vue';
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>
<template>
<Upload action="https://api.semi.design/upload" :beforeUpload="beforeUpload">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(上传前异步校验)
</Button>
</Upload>
</template>上传后更新文件信息
可以通过 afterUpload 钩子,对文件状态,校验信息,文件名进行更新。
({ response: any, file: FileItem, fileList: Array<FileItem> }) => afterUploadResult
afterUpload 在上传完成后(xhr.onload)且没有发生错误的情况下触发,需返回一个 Object 对象(不支持异步返回),具体结构如下
// afterUploadResult:
{
status?: 'success' | 'uploadFail' | 'validateFail' | 'validating' | 'uploading' | 'wait';
// 文件的校验信息,需要节点时使用 #fileValidateMessage 插槽
validateMessage?: string;
// 是否从fileList中移除该文件,默认为false
autoRemove?: boolean;
// 文件的名称
name?: string;
// 预览文件的url,一般为当次上传请求中 Server 接收到文件后返回的存储地址
url?: string
}
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type AfterUploadProps, type AfterUploadResult } from '@fibrous/semi-ui-vue';
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>
<template>
<Upload action="https://api.semi.design/upload" :afterUpload="afterUpload">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>自定义请求
当传入 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 setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type UploadCustomRequestArgs } from '@fibrous/semi-ui-vue';
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>
<template>
<Upload action="https://api.semi.design/upload" :customRequest="mockRequest">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</Upload>
</template>图片裁切
通过 crop 属性启用图片裁切功能。支持点击选择、拖拽、粘贴、替换文件时进行裁切。
基本用法
设置 crop 启用默认裁切配置(:crop="true" 或布尔简写 crop)。
裁切前会先按 accept 与 limit 过滤,被拒绝或超出数量的图片不会进入裁切。浏览器只能编码 PNG、JPEG、WebP,其他格式(GIF、BMP、SVG)的裁切结果为 PNG,文件的 type 与扩展名随之变为 image/png 与 .png。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
</script>
<template>
<Upload action="https://api.semi.design/upload" crop @success="(response, file) => console.log('上传成功:', response, file)">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(启用裁切)
</Button>
</Upload>
</template>自定义裁切配置
通过对象形式配置裁切参数,包括宽高比、形状、质量等。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type UploadCropProps } from '@fibrous/semi-ui-vue';
const crop: UploadCropProps = {
aspectRatio: 16 / 9,
shape: 'rect',
quality: 0.8,
modalTitle: '裁切图片',
modalOkText: '确认裁切',
modalCancelText: '取消',
};
</script>
<template>
<Upload action="https://api.semi.design/upload" :crop="crop" @success="(response, file) => console.log('上传成功:', response, file)">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(16:9 裁切)
</Button>
</Upload>
</template>圆形裁切
适用于头像上传场景,设置 shape: 'round' 启用圆形裁切。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
const crop = {
aspectRatio: 1,
shape: 'round',
quality: 0.9,
modalTitle: '裁切头像',
} as const;
</script>
<template>
<Upload action="https://api.semi.design/upload" :crop="crop" @success="(response, file) => console.log('上传成功:', response, file)">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(圆形裁切)
</Button>
</Upload>
</template>裁切前确认
通过 beforeCrop 回调,在裁切前进行确认或其他处理。返回 false 可跳过裁切直接上传。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload, type UploadProps } from '@fibrous/semi-ui-vue';
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>
<template>
<Upload action="https://api.semi.design/upload" :beforeCrop="beforeCrop" :crop="crop" @cropError="(error) => console.error('裁切失败:', error)" @success="(response, file) => console.log('上传成功:', response, file)">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传(裁切前确认)
</Button>
</Upload>
</template>拖拽上传裁切
拖拽上传时也会触发裁切功能。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
const crop = {
aspectRatio: 1,
shape: 'rect',
} as const;
</script>
<template>
<Upload action="https://api.semi.design/upload" :crop="crop" draggable @drop="(_e, files) => console.log('拖拽文件:', files)" @success="(response, file) => console.log('上传成功:', response, file)">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
拖拽上传(启用裁切)
</Button>
</Upload>
</template>自定义裁切弹窗样式
通过 cropModalProps 自定义裁切弹窗的样式和属性。弹窗的 width 缺省为 600,传入的值优先。
cropModalProps 的类型为 UploadCropModalProps,不含 visible、title、okText、cancelText、onOk、onCancel:标题和按钮文字用 crop 的 modalTitle、modalOkText、modalCancelText 配置,其余由组件内置。
代码
<script setup lang="ts">
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Upload } from '@fibrous/semi-ui-vue';
const crop = {
aspectRatio: 1,
shape: 'rect',
} as const;
const cropModalProps = {
width: 800,
style: { height: '600px' },
bodyStyle: { height: '500px', backgroundColor: '#f5f5f5' },
};
</script>
<template>
<Upload action="https://api.semi.design/upload" :crop="crop" :cropModalProps="cropModalProps" @success="(response, file) => console.log('上传成功:', response, file)">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
自定义弹窗样式
</Button>
</Upload>
</template>API 参考
Upload
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| accept | 接受上传的文件类型,写法同 input 的 accept:文件后缀(如 .jpg)或 MIME type,用逗号分隔。 | string | semiGlobalDefault("Upload", "accept") |
| action | 文件上传地址,必填。 | string | undefined |
| addOnPasting | 粘贴(paste 事件)时是否把剪贴板中的文件加入文件列表,无需额外授权。 | boolean | semiDefault("Upload", "addOnPasting", false) |
| afterUpload | 上传完成且没有出错后的钩子,根据返回的对象更新文件状态、校验信息、名称和预览地址;不支持异步返回。 | Type((object: AfterUploadProps) => AfterUploadResult) | undefined |
| beforeClear | 清空文件前的回调,返回 false、resolve(false) 或 reject 会阻止清空。 | Type((fileList: FileItem[]) => boolean | Promise<boolean>) | undefined |
| beforeCrop | 图片裁切前的回调,只用第一张选中的图片调用一次,结果决定整批是否裁切。返回 false 跳过裁切直接上传,支持异步返回。 | Type((file: File, fileList: File[]) => boolean | Promise<boolean>) | undefined |
| beforeRemove | 移除文件前的回调,返回 false、resolve(false) 或 reject 会阻止移除。 | Type((file: FileItem, fileList: FileItem[]) => boolean | Promise<boolean>) | undefined |
| beforeUpload | 上传文件前的钩子,根据返回的对象更新文件状态并控制是否上传;返回 boolean 表示通过或失败,返回 Promise 时 resolve 为通过、reject 为失败。 | Type((object: BeforeUploadProps) => boolean | BeforeUploadObjectResult | Promise<BeforeUploadObjectResult>) | undefined |
| capture | 媒体拍摄的方式,可选 user、environment;传 true 输出空值属性,传 false 不输出该属性。 | boolean | "user" | "environment" | semiGlobalDefault("Upload", "capture") |
| crop | 启用图片裁切。传 true 使用默认配置,传对象自定义裁切参数;点击选择、拖拽、粘贴、替换都会裁切。 | boolean | UploadCropProps | semiDefault("Upload", "crop", false) |
| cropModalProps | 裁切弹窗 Modal 的属性;visible、title、okText、cancelText、onOk、onCancel 由组件内置,width 缺省为 600。 | UploadCropModalProps | semiGlobalDefault("Upload", "cropModalProps") |
| customRequest | 替换内置 XMLHttpRequest 的自定义请求方法,需自行调用入参的 onProgress、onError、onSuccess 更新状态。 | Type((object: UploadCustomRequestArgs) => void) | undefined |
| data | 上传时附带的额外参数,或根据文件返回额外参数的函数。 | TypeRecord<string, any> | ((file: File) => Record<string, unknown>) | undefined |
| defaultFileList | 初始的已上传文件列表,非受控。每项的 uid 必填且不能重复。 | FileItem[] | semiDefault("Upload", "defaultFileList", []) |
| directory | 是否上传文件夹。 | boolean | semiDefault("Upload", "directory", false) |
| disabled | 是否禁用。禁用后不再显示移除、重试、替换和清空操作。 | boolean | semiDefault("Upload", "disabled", false) |
| draggable | 是否支持拖拽上传。 | boolean | semiDefault("Upload", "draggable", false) |
| fileList | 受控的文件列表,配合 v-model:fileList 使用。每项的 uid 必填且不能重复。 | FileItem[] | semiGlobalDefault("Upload", "fileList") |
| fileName | 作用与 name 相同,主要在 Form.Upload 中使用,避免与 Field 的 name 冲突。 | string | semiGlobalDefault("Upload", "fileName") |
| headers | 上传时附带的 headers,或根据文件返回 headers 的函数。 | TypeRecord<string, any> | ((file: File) => Record<string, string>) | undefined |
| hotSpotLocation | 照片墙添加按钮的位置,可选 start 或 end。 | "start" | "end" | semiDefault("Upload", "hotSpotLocation", "end") |
| itemStyle | 文件卡片的内联样式。 | StyleValue | semiGlobalDefault("Upload", "itemStyle") |
| limit | 最多允许上传的文件个数。为 1 时始终用最新选择的文件替换当前文件,不触发 exceed。 | number | semiGlobalDefault("Upload", "limit") |
| listType | 文件列表的展示类型,可选 list、picture、none;none 不渲染文件列表,只保留触发区。 | "list" | "none" | "picture" | semiDefault("Upload", "listType", "list") |
| maxSize | 文件体积上限,单位 KB。 | number | semiGlobalDefault("Upload", "maxSize") |
| minSize | 文件体积下限,单位 KB。 | number | semiGlobalDefault("Upload", "minSize") |
| multiple | 是否允许一次选择多个文件。 | boolean | semiDefault("Upload", "multiple", false) |
| name | 上传时使用的文件字段名。 | string | semiGlobalDefault("Upload", "name") |
| onPreviewClick | 监听方式为 @previewClick。传入后文件卡片可点击,并显示预览按钮;是否传入会改变渲染,因此保留为函数 prop。 | Type((fileItem: FileItem) => void) | undefined |
| picHeight | 照片墙模式下图片的展示高度。 | string | number | semiGlobalDefault("Upload", "picHeight") |
| picWidth | 照片墙模式下图片的展示宽度。 | string | number | semiGlobalDefault("Upload", "picWidth") |
| promptPosition | 提示文本的位置,可选 left、right、bottom。list 模式相对触发区,picture 模式相对图片列表。 | "bottom" | "left" | "right" | semiDefault("Upload", "promptPosition", "right") |
| showClear | list 模式下,limit 不为 1 且至少有一个文件时,是否展示清空按钮。 | boolean | semiDefault("Upload", "showClear", true) |
| showPicInfo | 是否显示图片信息,只在照片墙模式下有效。 | boolean | semiDefault("Upload", "showPicInfo", false) |
| showReplace | 上传成功后,文件卡片内是否展示替换按钮。 | boolean | semiDefault("Upload", "showReplace", false) |
| showRetry | 上传失败时,文件卡片内是否展示重试按钮。 | boolean | semiDefault("Upload", "showRetry", true) |
| showTooltip | 文件名过长时是否展示提示,或配置提示的类型(tooltip 或 popover)和透传属性;自定义弹出内容使用 ellipsisTooltip 插槽。 | boolean | ShowTooltip | semiDefault("Upload", "showTooltip", true) |
| showUploadList | 是否显示文件列表。 | boolean | semiDefault("Upload", "showUploadList", true) |
| transformFile | 选中文件后、上传前的转换函数,返回要上传的 File。 | Type((file: File) => File) | undefined |
| uploadTrigger | 触发上传的时机,auto 选中后自动上传,custom 需调用 upload 方法。 | "auto" | "custom" | semiDefault("Upload", "uploadTrigger", "auto") |
| validateStatus | 校验状态,影响根元素的样式,可选 default、error、warning、success。 | "error" | "success" | "default" | "warning" | semiGlobalDefault("Upload", "validateStatus") |
| withCredentials | 上传请求是否携带 Cookie。 | boolean | semiDefault("Upload", "withCredentials", false) |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| update:fileList | 文件列表变化时触发,用于 v-model:fileList。 | [fileList: FileItem[]] |
| acceptInvalid | 接收到的文件不符合 accept 时触发,参数为不符合的文件。 | [files: File[]] |
| change | 文件状态变化时触发,包括上传中、成功、失败。currentFile 在 insert 后为 null,清空时不带。会遮住隐藏 input 冒泡出来的原生 change。 | [object: UploadOnChangeProps] |
| clear | 点击清空后触发。 | [] |
| cropError | 图片裁切失败时触发。 | [error: Error] |
| drop | 拖拽的元素在拖拽区上释放时触发,参数为原生 drop 事件、拖入的文件和当前文件列表。会遮住根元素上的原生 drop。 | [e: DragEvent, files: File[], fileList: FileItem[]] |
| error | 上传出错时触发。使用 customRequest 时,xhr 是使用方传给 onError 的对象。 | Type[e: UploadCustomError, file: CustomFile, fileList: FileItem[], xhr: XMLHttpRequest | { status?: number | undefined; }] |
| exceed | 文件总数超出 limit 时触发,参数为截断前本次选中的原始文件。 | [fileList: File[]] |
| fileChange | 选中文件后触发。 | [files: File[]] |
| openFileDialog | 打开系统文件选择窗口时触发。 | [] |
| pastingError | addOnPasting 为 true 且读取剪贴板中的文件失败时触发。 | [error: Error] |
| progress | 上传过程中触发,参数为百分比、文件和当前文件列表。 | [percent: number, file: CustomFile, fileList: FileItem[]] |
| remove | 移除文件后触发。来自 defaultFileList 且没有 File 对象的项,currentFile 为 undefined。 | Type[currentFile: CustomFile | undefined, fileList: FileItem[], currentFileItem: FileItem] |
| retry | 点击重试时触发。 | [fileItem: FileItem] |
| sizeError | 文件体积不符合 maxSize 或 minSize 时触发。 | [file: CustomFile, fileList: FileItem[]] |
| success | 上传成功后触发,参数为服务端响应、文件和当前文件列表。 | [responseBody: any, file: CustomFile, fileList: FileItem[]] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| default | 触发区内容。拖拽模式下替换整个默认拖拽区内容。 | any |
| dragIcon | 拖拽区的图标,渲染为空时显示默认图标。 | any |
| dragMainText | 拖拽区的主文本,渲染为空时显示语言包文案。 | any |
| dragSubText | 拖拽区的帮助文本。 | any |
| prompt | 提示文本。与写在默认插槽中不同,点击它不会触发上传。 | any |
| validateMessage | Upload 整体的校验信息。 | any |
| fileListTitle | 只替换文件列表的标题文字,保留清空按钮。 | any |
| fileListHeader | 替换整个标题区(含清空按钮),参数为 fileList、onClear、clearText。 | UploadFileListHeaderSlotProps |
| fileItem | 替换每张文件卡片,参数为 UploadFileItemSlotProps;对某个文件渲染为空时不显示该文件。 | UploadFileItemSlotProps |
| previewFile | 自定义预览逻辑,替换原缩略图,只在 list 模式下有效;渲染为空时不回落默认缩略图。 | UploadFileItemSlotProps |
| thumbnail | 自定义照片墙缩略图,只在照片墙模式下有效。 | UploadFileItemSlotProps |
| picInfo | 自定义照片墙信息,只在照片墙模式且 showPicInfo 为 true 时有效。 | UploadFileItemSlotProps |
| picPreviewIcon | 自定义照片墙悬停时的预览图标,只在照片墙模式下有效。 | UploadFileItemSlotProps |
| picClose | 自定义照片墙的关闭按钮,参数为 class 和 onRemove,只在照片墙模式下有效。 | UploadPicCloseSlotProps |
| fileOperation | 自定义列表项的操作区。 | UploadFileItemSlotProps |
| fileValidateMessage | 自定义文件卡片的校验信息,渲染为空时回落到文件自身的 validateMessage 字符串。 | UploadFileItemSlotProps |
| ellipsisTooltip | 自定义文件名过长时的弹出内容,参数为 content 和 trigger。 | TypographyEllipsisTooltipProps |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| insert | 在 index 位置插入文件,不传 index 则插入到末尾。 | Type(files: CustomFile[], index?: number | undefined) => void |
| upload | 开始上传等待中的文件,配合 uploadTrigger 为 custom 使用。 | Type() => void |
| openFileDialog | 打开文件选择窗口。 | Type() => void |
class、style、aria-* 等未声明的 attrs 落在根元素上,根元素带 aria-label 或 aria-labelledby 时自动加 role="group"。
Upload.FileCard
Upload.FileCard 是默认的文件卡片,用于在 #fileItem 等插槽中保留默认外观:<Upload.FileCard v-bind="props" />。class、style、data-*、aria-* 落在根元素上。
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| file | 要展示的文件,必填。 | FileItem | undefined |
| index | 文件在列表中的位置。 | number | undefined |
| listType | 卡片的展示类型,可选 list、picture、none;none 不渲染卡片。 | "list" | "none" | "picture" | "list" |
| disabled | 是否禁用,禁用后不显示移除、重试和替换按钮。 | boolean | undefined |
| showRetry | 上传失败时是否展示重试按钮,文件自身的 showRetry 优先。 | boolean | undefined |
| showReplace | 上传成功后是否展示替换按钮,文件自身的 showReplace 优先。 | boolean | undefined |
| showPicInfo | 是否显示图片信息,只在照片墙模式下有效。 | boolean | undefined |
| showTooltip | 文件名过长时是否展示提示,或配置提示的类型和透传属性。 | boolean | ShowTooltip | true |
| picWidth | 照片墙模式下图片的宽度。 | string | number | undefined |
| picHeight | 照片墙模式下图片的高度。 | string | number | undefined |
| validateMessage | 校验信息,优先于文件自身的 validateMessage。 | string | undefined |
| onPreviewClick | 监听方式为 @previewClick。传入后卡片可点击,并显示预览按钮;是否传入会改变渲染,因此保留为函数 prop。 | Type(() => void) | undefined |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| remove | 点击移除时触发。 | [] |
| retry | 点击重试时触发。 | [] |
| replace | 点击替换时触发。 | [] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| previewFile | 自定义预览逻辑,替换原缩略图,只在 list 模式下有效。 | UploadFileItemSlotProps |
| thumbnail | 自定义照片墙缩略图,只在照片墙模式下有效。 | UploadFileItemSlotProps |
| picInfo | 自定义照片墙信息,只在照片墙模式且 showPicInfo 为 true 时有效。 | UploadFileItemSlotProps |
| picPreviewIcon | 自定义照片墙悬停时的预览图标,只在照片墙模式下有效。 | UploadFileItemSlotProps |
| picClose | 自定义照片墙的关闭按钮,参数为 class 和 onRemove,只在照片墙模式下有效。 | UploadPicCloseSlotProps |
| fileOperation | 自定义列表项的操作区。 | UploadFileItemSlotProps |
| validateMessage | 自定义校验信息,渲染为空时回落到字符串。插槽为该文件渲染出内容时,图片墙同样显示校验图标。 | UploadFileItemSlotProps |
| ellipsisTooltip | 自定义文件名过长时的弹出内容,参数为 content 和 trigger。 | TypographyEllipsisTooltipProps |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
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。如果是defaultFileList, 需要自行保证不会重复
url?: string;
validateMessage?: string; // 需要节点时使用 #fileValidateMessage 插槽
}
FileItem 只包含上述字段,写入未声明的字段会报类型错误。
UploadFileItemSlotProps Interface
#fileItem、#thumbnail、#picInfo、#picPreviewIcon、#fileOperation、#previewFile、#fileValidateMessage 收到的参数。文件数据放在 file 字段中,另有文件卡片的属性。参数不含 key,可以直接写 <Upload.FileCard v-bind="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?: StyleValue; // 来自 itemStyle
onRemove: () => void; // 移除该文件
onRetry: () => void; // 重新上传该文件
onReplace: () => void; // 替换该文件
onPreviewClick?: () => void; // 仅在监听了 Upload 的 previewClick 时存在
}
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 属性传入对象时的配置项:
Vue 中该类型的导出名为 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 通过 defineExpose 暴露 insert、upload、openFileDialog,可以用模板引用调用,类型为 UploadHandle,说明见上方 Upload 的 Exposes 表。
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 键只停靠一次
文案规范
- 上传按钮
- 关于表单按钮的文案规范,参考按钮Button组件的文案规范
- 帮助文本
- 帮助文本使用语句书写规范,首字母大写,可以不需要句号
- 用户出错提示
- 清晰地告诉用户为什么文件无法被上传,并且告知用户如何操作能够成功上传
- 帮助文本使用语句书写规范,首字母大写
- 简洁的用语让用户能够一眼读懂,比如
File size must be less than 20MB,File type must be .gif, .jpg, .png or .svg
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $color-upload-text | var(--semi-color-text-0) | 上传文件卡片等默认文本颜色 |
| $color-upload_assist-text | var(--semi-color-text-2) | 上传文件列表标题及文件大小文本颜色 |
| $color-upload-border | var(--semi-color-border) | 图片墙添加按钮及拖拽区域描边颜色 |
| $color-upload_card-bg-hover | var(--semi-color-fill-1) | 上传文件卡片背景色 - 悬浮 |
| $color-upload_card-bg | var(--semi-color-fill-0) | 上传文件卡片背景色 - 默认 |
| $color-upload_card_fail-bg-hover | var(--semi-color-danger-light-hover) | 上传失败文件卡片背景色 - 悬浮 |
| $color-upload_card_fail-bg | var(--semi-color-danger-light-default) | 上传失败文件卡片背景色 - 默认 |
| $color-upload_clear-text | var(--semi-color-primary) | 上传清空按钮文本颜色 |
| $color-upload_drag_area-bg-hover | var(--semi-color-primary-light-default) | 上传可拖拽区域背景颜色 - 悬浮 |
| $color-upload_drag_area-border-hover | var(--semi-color-primary) | 上传可拖拽区域描边颜色 - 悬浮 |
| $color-upload_drag_area_disabled-text | var(--semi-color-disabled-text) | 上传可拖拽区域禁用文本颜色 |
| $color-upload_drag_area-bg | var(--semi-color-tertiary-light-default) | 上传可拖拽区域背景颜色 - 默认 |
| $color-upload_drag_area_icon | var(--semi-color-primary) | 上传可拖拽区域图标颜色 |
| $color-upload_drag_area_tips-text | var(--semi-color-primary) | 上传可拖拽区域提示文本颜色 |
| $color-upload_drag_area_main-text-default | var(--semi-color-text-0) | 上传可拖拽区主要提示文本颜色 - 默认 |
| $color-upload_drag_area_main-text-hover | var(--semi-color-text-0) | 上传可拖拽区主要提示文本颜色 - 悬浮 |
| $color-upload_drag_area_main-text-active | var(--semi-color-text-0) | 上传可拖拽区主要提示文本颜色 - 按下 |
| $color-upload_drag_area_sub-text-default | var(--semi-color-text-0) | 上传可拖拽区次要提示文本颜色 - 默认 |
| $color-upload_drag_area_sub-text-hover | var(--semi-color-text-0) | 上传可拖拽区次要提示文本颜色 - 悬浮 |
| $color-upload_drag_area_sub-text-active | var(--semi-color-text-0) | 上传可拖拽区次要提示文本颜色 - 按下 |
| $color-upload_file_card_fail_info-text | var(--semi-color-danger) | 上传文件卡片失败提示信息文本颜色 |
| $color-upload_file_card_preview_placeholder-bg | var(--semi-grey-3) | 文件卡片默认预览背景颜色 |
| $color-upload_file_card_preview_placeholder-text | var(--semi-white) | 文件卡片默认预览图颜色 |
| $color-upload_file_card_retry-bg | var(--semi-color-white) | 图片墙上传卡片重新上传按钮背景颜色 |
| $color-upload_file_card_retry-text | var(--semi-color-primary) | 图片墙上传卡片重新上传按钮文本颜色 |
| $color-upload-icon | var(--semi-color-tertiary) | 图片墙上传图标加号颜色 |
| $color-upload_pic_add-bg-active | var(--semi-color-fill-2) | 图片墙上传背景色 - 按下 |
| $color-upload_pic_add-bg-hover | var(--semi-color-fill-1) | 图片墙上传背景色 - 悬浮 |
| $color-upload_pic_add-bg | var(--semi-color-fill-0) | 图片墙上传背景色 - 默认 |
| $color-upload_picture_file_card_loading_error-icon | var(--semi-color-danger) | 图片墙上传卡片加载及失败状态图标颜色 |
| $color-upload_picture_file_card_error-border | var(--semi-color-danger) | 图片墙上传卡片失败状态描边颜色 |
| $color-upload_picture_file_card_pic_info-text | var(--semi-color-white) | 图片墙图片信息(序号)文字颜色 |
| $color-upload_picture_file_card_close-icon | var(--semi-color-white) | 图片墙关闭图标颜色 |
| $color-upload_picture_file_card_hover-bg | var(--semi-color-overlay-bg) | 图片墙预览悬浮背景色 |
| $color-upload_preview-icon | var(--semi-color-text-2) | 上传文件卡片文本颜色 |
| $color-upload_retry-text | var(--semi-color-primary) | 上传文件卡片重新上传按钮文本颜色 |
| $color-upload_replace-text | var(--semi-color-white) | 图片墙上传卡片替换按钮文本颜色 |
| $height-upload_file_card | 52px | 上传文件卡片高度 |
| $width-upload_file_card | 250px | 上传文件卡片宽度 |
| $height-upload_file_pic_card | 96px | 图片墙上传卡片高度 |
| $width-upload_file_pic_card | 96px | 图片墙上传卡片宽度 |
| $width-upload_file_card_preview | 36px | 文件卡片预览图标宽度 |
| $height-upload_file_card_preview | 36px | 文件卡片预览图标高度 |
| $width-upload_file_card_preview_img | 36px | 文件卡片预览图片宽度 |
| $width-upload_file_card-icon | 11px | 文件卡片图标宽度 |
| $width-upload_picture_add-border | 2px | 图片墙添加按钮描边宽度 |
| $width-upload_picture_file_card_img | 96px | 图片墙上传卡片宽度 |
| $width-upload_picture_file_card_close | 16px | 图片墙上传卡片删除按钮图标宽度 |
| $width-upload_file_card_retry | 24px | 重新上传按钮宽度 |
| $width-upload_file_card_retry-icon | 13px | 重新上传按钮图标宽度 |
| $width-upload_picture_file_card_loading-icon | 14px | 上传加载 spin 宽度 |
| $width-upload_drag_area-border | 2px | 可拖拽上传描边宽度 |
| $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-marginTop | 4px | 上传文件卡片进度顶部外边距 |
| $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-top | 8px | 图片墙上传卡片删除按钮顶部位置 |
| $spacing-upload_picture_file_card_close-right | 8px | 图片墙上传卡片删除右侧位置 |
| $spacing-upload_picture_file_card_loading_error-bottom | 6px | 图片墙上传卡片加载中图标底部位置 |
| $spacing-upload_picture_file_card_loading_error-right | 6px | 图片墙上传卡片报错图标底部位置 |
| $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_card | var(--semi-border-radius-medium) | 上传文件卡片圆角 |
| $radius-upload_file_card_preview | var(--semi-border-radius-small) | 上传文件卡片预览图圆角 |
| $radius-upload_picture_add | var(--semi-border-radius-small) | 图片墙上传文件添加按钮圆角 |
| $radius-upload_picture_file_card_img | var(--semi-border-radius-small) | 图片墙上传卡片圆角 |
| $radius-upload_picture_file_card_close | var(--semi-border-radius-circle) | 图片墙上传卡片删除按钮圆角 |
| $radius-upload_file_card_retry | var(--semi-border-radius-circle) | 上传文件卡片重新上传圆角 |
| $radius-upload_drag_area | var(--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-fontWeight | 600 | 可拖拽上传提示文本字重 |
| $font-upload_picture_file_card_pic_info-fontSize | 12px | 图片墙图片信息字体大小 |
| $font-upload_picture_file_card_pic_info-fontWeight | 600 | 图片墙图片信息文本字重 |
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 真实值是否符合预期。
- 如果你设置了