Appearance
BizUploadFile 文件上传
列表形态的文件上传:数量 / 大小 / 类型前置校验,上传中 loading、失败标记可删重传、成功项支持下载。上传实现由业务侧以 request 注入,组件零接口。
基础用法
最多 3 个,单文件不超过 10MB,支持任意类型
v-model(0 个):(空)
request 返回文件地址即入列表;v-model 同步地址数组。
<script setup lang="ts">
/** 基础用法:request 注入上传实现,列表展示状态/下载/删除 */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const urls = ref<string[]>([])
/** 模拟上传接口:600ms 后返回文件地址 */
const upload = async (file: File): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 600))
return `https://files.example-cdn.cn/attachments/${encodeURIComponent(file.name)}`
}
</script>
<template>
<div>
<BizUploadFile v-model="urls" :request="upload" :limit="3" :max-size="10" tip="最多 3 个,单文件不超过 10MB,支持任意类型" />
<div class="tip">v-model({{ urls.length }} 个):{{ urls.length > 0 ? urls.join('、') : '(空)' }}</div>
</div>
</template>
<style scoped>
.tip {
margin-top: 8px;
font-size: 13px;
color: var(--el-text-color-secondary);
word-break: break-all;
}
</style>
显示代码
其他用法
仅支持 .pdf / .doc / .docx / .xlsx;上传一个文件名带 fail 的文件可看失败态
最近错误:(无)
accept 类型白名单、失败态演示(文件名含 fail)、error 事件。
<script setup lang="ts">
/** 其他用法:accept 类型限制 + 失败态演示(文件名含 fail 即失败)+ error 事件 */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const urls = ref<string[]>([])
const lastError = ref('(无)')
const upload = async (file: File): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 800))
if (file.name.toLowerCase().includes('fail')) {
throw new Error('模拟上传失败')
}
return `https://files.example-cdn.cn/attachments/${encodeURIComponent(file.name)}`
}
</script>
<template>
<div>
<BizUploadFile
v-model="urls"
:request="upload"
accept=".pdf,.doc,.docx,.xlsx"
:limit="5"
tip="仅支持 .pdf / .doc / .docx / .xlsx;上传一个文件名带 fail 的文件可看失败态"
@error="(message: string) => (lastError = message)"
/>
<div class="tip">最近错误:{{ lastError }}</div>
</div>
</template>
<style scoped>
.tip {
margin-top: 8px;
font-size: 13px;
color: var(--el-text-color-secondary);
word-break: break-all;
}
</style>
显示代码
合同附件上传
仅支持 pdf / word,单份不超过 10MB,最多 3 份;超限文件将被拦截
已上传 1 / 3 份(编辑场景回显已有附件地址,删除后同步 v-model)
法务场景:
accept=".pdf,.doc,.docx" 收紧类型、max-size=10 拦截超限、limit=3 控制份数,tip 提示口径;编辑场景回显已有附件地址。<script setup lang="ts">
/** 场景:合同附件上传——类型 / 大小收紧 + 提示文案 + 数量上限 */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const files = ref<string[]>([
'https://files.example-cdn.cn/contracts/2026-main-agreement.pdf',
])
const upload = async (file: File): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 700))
return `https://files.example-cdn.cn/contracts/${encodeURIComponent(file.name)}`
}
</script>
<template>
<div style="max-width: 520px">
<BizUploadFile
v-model="files"
:request="upload"
accept=".pdf,.doc,.docx"
:limit="3"
:max-size="10"
button-text="上传合同文件"
tip="仅支持 pdf / word,单份不超过 10MB,最多 3 份;超限文件将被拦截"
/>
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
已上传 {{ files.length }} / 3 份(编辑场景回显已有附件地址,删除后同步 v-model)
</div>
</div>
</template>
显示代码
验收材料上传
现场照片与验收报告 pdf 均可,最多 5 份、单个 ≤ 20MB
已上传 0 / 5 份
项目验收场景:图片与 pdf 混传(accept 逗号分隔)、上限 5 份 / 20MB,tip 写明口径。
<script setup lang="ts">
/** 场景:验收材料上传——多类型混传(图 + pdf),上限 5 份 */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const files = ref<string[]>([])
const upload = async (file: File): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 600))
return `https://files.example-cdn.cn/acceptance/${encodeURIComponent(file.name)}`
}
</script>
<template>
<div style="max-width: 520px">
<BizUploadFile
v-model="files"
:request="upload"
accept=".png,.jpg,.jpeg,.pdf"
:limit="5"
:max-size="20"
button-text="上传验收材料"
tip="现场照片与验收报告 pdf 均可,最多 5 份、单个 ≤ 20MB"
/>
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">已上传 {{ files.length }} / 5 份</div>
</div>
</template>
显示代码
版本附件管理
v1.3当前生效
v1.2初版(已作废)
文档版本链场景:上传即追加新版本(v1.2 → v1.3 → …),外部维护版本列表与当前生效版本。
<script setup lang="ts">
/** 场景:版本附件管理——外部列表展示版本号,重新上传即追加新版本 */
import { ref } from 'vue'
import { ElButton, ElMessage, ElTag } from 'element-plus'
import { BizUploadFile } from 'vue3-ep-biz-lib'
interface VersionFile {
version: string
url: string
note: string
}
const versions = ref<VersionFile[]>([
{ version: 'v1.2', url: 'https://files.example.cn/spec/v1.2.pdf', note: '初版(已作废)' },
{ version: 'v1.3', url: 'https://files.example.cn/spec/v1.3.pdf', note: '当前生效' },
])
const current = ref<string[]>(versions.value[1] ? [versions.value[1].url] : [])
const upload = async (file: File): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 700))
const version = `v1.${versions.value.length + 2}`
versions.value.push({ version, url: `https://files.example.cn/spec/${version}-${encodeURIComponent(file.name)}`, note: '最新上传' })
ElMessage.success(`已上传 ${version}`)
return versions.value[versions.value.length - 1].url
}
const removeVersion = (index: number): void => {
versions.value.splice(index, 1)
ElMessage.info('已从版本链移除')
}
</script>
<template>
<div style="max-width: 560px; display: flex; flex-direction: column; gap: 10px">
<BizUploadFile v-model="current" :request="upload" accept=".pdf" :limit="1" :max-size="30" button-text="上传新版本规格书" />
<div style="display: flex; flex-direction: column; gap: 6px">
<div
v-for="file in [...versions].reverse()"
:key="file.version"
style="display: flex; align-items: center; gap: 10px; padding: 6px 10px; border: 1px solid var(--el-border-color-lighter); border-radius: 6px; font-size: 13px"
>
<ElTag size="small">{{ file.version }}</ElTag>
<span style="flex: 1; color: var(--el-text-color-secondary)">{{ file.note }}</span>
<ElButton link type="danger" size="small" @click="removeVersion(versions.indexOf(file))">移除</ElButton>
</div>
</div>
</div>
</template>
显示代码
必传单文件(投标)
仅 pdf、单个 ≤ 50MB、仅 1 份;换标书请先删除
未上传时递交按钮禁用(必传校验联动)
投标场景:仅 1 份 pdf ≤ 50MB;未上传时「递交标书」按钮 disabled——必传校验与提交动作联动。
<script setup lang="ts">
/** 场景:必传单文件——投标文件未传时禁止提交 */
import { computed, ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const bidFile = ref<string[]>([])
const upload = async (file: File): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 600))
return `https://files.example.cn/bid/${encodeURIComponent(file.name)}`
}
const canSubmit = computed(() => bidFile.value.length > 0)
const onSubmit = (): void => {
if (!canSubmit.value) {
ElMessage.warning('请先上传加盖公章的投标文件')
return
}
ElMessage.success('标书已递交(示例)')
}
</script>
<template>
<div style="max-width: 520px">
<BizUploadFile
v-model="bidFile"
:request="upload"
accept=".pdf"
:limit="1"
:max-size="50"
button-text="上传投标文件"
tip="仅 pdf、单个 ≤ 50MB、仅 1 份;换标书请先删除"
/>
<div style="margin-top: 12px; display: flex; gap: 10px; align-items: center">
<ElButton type="primary" :disabled="!canSubmit" @click="onSubmit">递交标书</ElButton>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">未上传时递交按钮禁用(必传校验联动)</span>
</div>
</div>
</template>
显示代码
截图粘贴上传
支持 Ctrl + V 粘贴截图
鼠标悬停在本区域,报错截图直接 Ctrl+V,无需保存成文件
已粘贴 0 张;已上传地址:(空)
pasteable 开启剪贴板截图粘贴:鼠标悬停在组件区域内 Ctrl+V 直接上传(默认 hover 作用域——防止同页多实例重复接收与误截获其他编辑器的贴图),自动命名为「截图-时间戳」,走与选择上传相同的校验与 request 链路;按钮旁自动出现粘贴提示。<script setup lang="ts">
/** 截图粘贴上传:鼠标悬停在本组件区域内 Ctrl+V 直接上传(默认 hover 作用域,防同页多实例误截获),也可点击按钮选择文件 */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
import { ElButton, ElMessage } from 'element-plus'
const urls = ref<string[]>([])
const pasteCount = ref(0)
/** 模拟上传接口(真实项目替换为服务端请求) */
const request = (file: File): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => resolve(URL.createObjectURL(file)), 600)
})
}
const onPaste = (files: File[]): void => {
pasteCount.value += files.length
ElMessage.success(`已捕获 ${files.length} 张粘贴图片,正在上传`)
}
/** 文档站兜底:无截图在手时用画布造一张图模拟剪贴板粘贴事件 */
const simulatePaste = (): void => {
const canvas = document.createElement('canvas')
canvas.width = 320
canvas.height = 180
const ctx = canvas.getContext('2d')
if (ctx === null) return
const gradient = ctx.createLinearGradient(0, 0, 320, 180)
gradient.addColorStop(0, '#409eff')
gradient.addColorStop(1, '#67c23a')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, 320, 180)
ctx.fillStyle = '#fff'
ctx.font = '20px sans-serif'
ctx.fillText('模拟截图', 110, 98)
canvas.toBlob((blob) => {
if (blob === null) return
const transfer = new DataTransfer()
transfer.items.add(new File([blob], 'image.png', { type: 'image/png' }))
window.dispatchEvent(new ClipboardEvent('paste', { clipboardData: transfer }))
}, 'image/png')
}
</script>
<template>
<div>
<BizUploadFile v-model="urls" pasteable :request="request" tip="鼠标悬停在本区域,报错截图直接 Ctrl+V,无需保存成文件" @paste="onPaste" />
<p style="margin: 8px 0 0; font-size: 13px; color: var(--el-text-color-secondary)">
已粘贴 {{ pasteCount }} 张;已上传地址:{{ urls.length === 0 ? '(空)' : urls.join('、') }}
<ElButton link type="primary" style="margin-left: 8px" @click="simulatePaste">模拟粘贴一张图</ElButton>
</p>
</div>
</template>
显示代码
粘贴 + 选择混合
支持 Ctrl + V 粘贴截图
window 作用域演示:页面任意位置 Ctrl+V 均可粘贴(同页多实例时慎用,会全部接收);粘贴截图与选择文件共用 6 个名额
附件 0/6;最近一次拦截:(无)
paste-scope="window" 切换为页面任意位置粘贴均接收(单实例整页贴图场景;同页多实例会全部接收,慎用);粘贴的截图与手动选择的文件共用同一 limit 计数——工单附件「截图 + 文档」一次收齐,批量超限合并为单条提示。<script setup lang="ts">
/** 粘贴 + 选择混合上传:两种入口共用同一 limit 计数与上传链路,工单附件一次收齐 */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const urls = ref<string[]>([])
const errorText = ref('')
const request = (file: File): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => resolve(URL.createObjectURL(file)), 500)
})
}
const onError = (message: string): void => {
errorText.value = message
}
</script>
<template>
<div>
<BizUploadFile
v-model="urls"
pasteable
paste-scope="window"
:request="request"
:limit="6"
button-text="选择文件"
tip="window 作用域演示:页面任意位置 Ctrl+V 均可粘贴(同页多实例时慎用,会全部接收);粘贴截图与选择文件共用 6 个名额"
@error="onError"
/>
<p style="margin: 8px 0 0; font-size: 13px; color: var(--el-text-color-secondary)">
附件 {{ urls.length }}/6;最近一次拦截:{{ errorText === '' ? '(无)' : errorText }}
</p>
</div>
</template>
显示代码
粘贴校验与失败重传
支持 Ctrl + V 粘贴截图
最多 3 个、单个 2MB 以内;上传失败呈红字态,点 ✕ 删除后重新粘贴或选择即可重传
成功 0/3;最近提示:(无)
粘贴同样受
limit / max-size 前置校验;上传失败呈红字态,点 ✕ 删除后重新粘贴或选择即可重传。<script setup lang="ts">
/** 粘贴校验与失败处理:数量超限拦截 + 上传失败态(删除后可重新粘贴 / 选择重传) */
import { ref } from 'vue'
import { BizUploadFile } from 'vue3-ep-biz-lib'
const urls = ref<string[]>([])
const errorText = ref('')
let uploadCount = 0
/** 模拟不稳定网络:奇数次上传失败,失败项标记红字,删除后重试即可 */
const request = (file: File): Promise<string> => {
uploadCount += 1
return new Promise((resolve, reject) => {
setTimeout(() => {
if (uploadCount % 3 === 0) reject(new Error('network error'))
else resolve(URL.createObjectURL(file))
}, 500)
})
}
const onError = (message: string): void => {
errorText.value = message
}
</script>
<template>
<div>
<BizUploadFile
v-model="urls"
pasteable
:request="request"
:limit="3"
:max-size="2"
tip="最多 3 个、单个 2MB 以内;上传失败呈红字态,点 ✕ 删除后重新粘贴或选择即可重传"
@error="onError"
/>
<p style="margin: 8px 0 0; font-size: 13px; color: var(--el-text-color-secondary)">
成功 {{ urls.length }}/3;最近提示:{{ errorText === '' ? '(无)' : errorText }}
</p>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| model-value / v-model | string[] | [] | 已上传文件地址 |
| request | (file: File) => Promise<string> | — | 必填。上传实现(业务接口在业务侧),resolve 文件地址 |
| accept | string | '' | 接受的文件类型(如 '.pdf,.doc'),留空不限 |
| limit | number | 5 | 数量上限 |
| max-size | number | 20 | 单文件大小上限(MB) |
| button-text | string | '上传文件' | 触发按钮文案 |
| tip | string | '' | 底部提示文案 |
| pasteable | boolean | false | 开启剪贴板截图粘贴:剪贴板图片自动重命名(截图-时间戳)并走同一校验与上传链路(批量校验合并为单条提示);按钮旁出现「支持 Ctrl + V」提示 |
| paste-scope | 'hover' | 'window' | 'hover' | 粘贴接收作用域:hover 仅鼠标悬停在组件区域内时接收(默认,防同页多实例重复上传与误截获其他编辑器贴图);window 页面任意位置粘贴均接收 |
其余属性(
name/headers/multiple...)经v-bind="$attrs"全量透传 el-upload。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:model-value | (urls: string[]) | 文件列表变化(仅成功项) |
| error | (message: string) | 前置校验不过 / 上传失败 |
| paste | (files: File[]) | pasteable 开启后,剪贴板图片经校验通过并即将上传时触发 |
Slots
| 插槽名 | 说明 |
|---|---|
| — | 无插槽 |
Expose
| 属性/方法 | 说明 |
|---|---|
| clear | () => void 清空全部文件 |
| records | 当前记录(含上传中 / 失败态) |