useUploadWithProgress
带进度监控的文件上传 composable,支持业务校验、超时、取消。
用法
useUploadWithProgress 基于原生 XMLHttpRequest 实现,复用 @movk/nuxt 的端点、认证、业务校验、Toast、movk:api:* hook 体系。返回的 data 是按 dataKey 解包后的业务数据,与 useApiFetch<T> 一致。
<script setup lang="ts">
const selected = ref<File[] | null>(null)
const { progress, status, data, error, upload, abort } = useUploadWithProgress<{
files: Array<{ name: string, size: number }>
}>()
async function handleUpload() {
const { data, error: err, aborted } = await upload('/api/upload', selected.value!, {
fieldName: 'files',
onSuccess: d => console.log('上传成功:', d.files)
})
if (aborted) return
if (err) console.error(err)
else console.log(data?.files)
}
</script>
<template>
<UFileUpload v-model="selected" multiple />
<UButton :disabled="!selected?.length" @click="handleUpload">上传</UButton>
<UProgress v-if="status === 'pending'" :model-value="progress ?? undefined" :max="100" />
<UButton v-if="status === 'pending'" @click="abort">取消</UButton>
</template>
适用于需要展示上传进度的场景。对于无需进度的小文件,可以直接使用
$api 发起请求。基础多文件上传
fieldName 决定 FormData 字段名(默认 'file');upload() 返回 { data, error, aborted }:
拖拽或点击选择文件
0% · 空闲
<script setup lang="ts">
const STATUS = {
idle: { color: 'neutral', label: '空闲' },
pending: { color: 'info', label: '上传中' },
success: { color: 'success', label: '完成' },
error: { color: 'error', label: '失败' },
aborted: { color: 'warning', label: '已取消' }
} as const
const selected = ref<File[] | null>(null)
const basic = useUploadWithProgress<{ files: Array<{ name: string, size: number }> }>()
</script>
<template>
<div class="flex flex-col gap-3">
<UFileUpload v-model="selected" multiple label="拖拽或点击选择文件" />
<div class="flex items-center gap-2">
<UButton
:loading="basic.status.value === 'pending'"
:disabled="!selected?.length"
icon="i-lucide-upload"
@click="basic.upload('/upload', selected!, { fieldName: 'files' })"
>
开始上传
</UButton>
<UButton
v-if="basic.status.value === 'pending'"
color="error"
variant="soft"
icon="i-lucide-x"
@click="basic.abort"
>
中止
</UButton>
<UBadge :color="STATUS[basic.status.value].color" variant="subtle">
{{ basic.progress.value ?? 0 }}% · {{ STATUS[basic.status.value].label }}
</UBadge>
</div>
<UProgress :model-value="basic.progress.value ?? undefined" :max="100" />
<UAlert
v-if="basic.error.value"
color="error"
variant="subtle"
:description="basic.error.value.message"
/>
<UCard v-if="basic.data.value">
<pre class="text-xs overflow-auto">{{ basic.data.value }}</pre>
</UCard>
</div>
</template>
单文件、多文件与额外字段
files 传 File 为单文件、传 File[] 为多文件(同 fieldName 追加);fields 注入额外表单字段一并提交:
await upload('/api/upload', file, { fieldName: 'file' })
await upload('/api/upload', [f1, f2, f3], { fieldName: 'files' })
await upload('/api/upload', file, {
fieldName: 'avatar',
fields: { userId: '123', visibility: 'private' }
})
选择单个文件
0% · 空闲
<script setup lang="ts">
const STATUS = {
idle: { color: 'neutral', label: '空闲' },
pending: { color: 'info', label: '上传中' },
success: { color: 'success', label: '完成' },
error: { color: 'error', label: '失败' },
aborted: { color: 'warning', label: '已取消' }
} as const
const selected = ref<File | null>(null)
const single = useUploadWithProgress()
</script>
<template>
<div class="flex flex-col gap-3">
<UFileUpload v-model="selected" label="选择单个文件" />
<div class="flex items-center gap-2">
<UButton
:loading="single.status.value === 'pending'"
:disabled="!selected"
icon="i-lucide-upload-cloud"
@click="single.upload('/upload', selected!, {
fields: { folder: 'avatars', visibility: 'private' }
})"
>
上传文件(携带 fields)
</UButton>
<UBadge :color="STATUS[single.status.value].color" variant="subtle">
{{ single.progress.value ?? 0 }}% · {{ STATUS[single.status.value].label }}
</UBadge>
</div>
<UAlert
v-if="single.error.value"
color="error"
variant="subtle"
:description="single.error.value.message"
/>
<UCard v-if="single.data.value">
<pre class="text-xs overflow-auto">{{ single.data.value }}</pre>
</UCard>
</div>
</template>
超时
timeoutMs 设定上传超时毫秒数(0 或省略不超时),超时触发失败分支,error.message 包含超时信息:
选择文件后模拟超时
空闲
<script setup lang="ts">
const STATUS = {
idle: { color: 'neutral', label: '空闲' },
pending: { color: 'info', label: '上传中' },
success: { color: 'success', label: '完成' },
error: { color: 'error', label: '失败' },
aborted: { color: 'warning', label: '已取消' }
} as const
const selected = ref<File[] | null>(null)
const timeout = useUploadWithProgress()
</script>
<template>
<div class="flex flex-col gap-3">
<UFileUpload v-model="selected" multiple label="选择文件后模拟超时" />
<div class="flex items-center gap-2">
<UButton
color="neutral"
:loading="timeout.status.value === 'pending'"
:disabled="!selected?.length"
icon="i-lucide-timer"
@click="timeout.upload('/upload?slow=2000', selected!, { timeoutMs: 500 })"
>
模拟超时
</UButton>
<UBadge :color="STATUS[timeout.status.value].color" variant="subtle">
{{ STATUS[timeout.status.value].label }}
</UBadge>
</div>
<UAlert
v-if="timeout.error.value"
color="error"
variant="subtle"
:description="timeout.error.value.message"
/>
</div>
</template>
跨域携带凭证
await upload('https://other.example/upload', file, { withCredentials: true })
取消上传
const { status, upload, abort } = useUploadWithProgress()
upload('/api/upload', largeFile)
function handleCancel() {
if (status.value === 'pending') abort()
}
取消后:
status变为'aborted',progress重置为0error保持null(不视为失败)- 不触发
onSuccess/onError,不弹 Toast upload()返回{ data: null, error: null, aborted: true }
认证
自动从 session 读取 token 注入到 Authorization 头;可在 headers 中覆盖同名头(用户优先)。
await upload('/api/upload', file, {
headers: { Authorization: 'Bearer override' }
})
API
useUploadWithProgress<T>()
useUploadWithProgress<T>(): { progress, status, data, error, upload, abort }
Returns
progress
Ref<number | null>
上传进度,0-100;类型与下载侧对齐保留
null 形态,上传场景实际不会出现。status
Ref<TransferStatus>
传输状态:
'idle' | 'pending' | 'success' | 'error' | 'aborted'。data
Ref<T | null>
按
dataKey 解包后的业务数据。error
Ref<ApiError | Error | null>
错误对象;业务错误为
ApiError,HTTP/网络错误为 Error。中止时为 null。upload()
(url: string, files: File | File[], options?: UploadWithProgressOptions<T>) => Promise<TransferResult<T>>
执行上传。
Parameters
url
string required
files
File | File[] required
options
UploadWithProgressOptions<T>
fieldName
string
默认
'file'。fields
Record<string, string | Blob>
headers
Record<string, string>
用户优先于自动 auth 头。
toast
RequestToastOptions | false
endpoint
string
skipBusinessCheck
boolean
timeoutMs
number
超时毫秒数,0 或省略不超时。
withCredentials
boolean
onSuccess
(data: T) => void
解包后的业务数据。
onError
(error: ApiError | Error) => void
Returns
Promise<TransferResult<T>>:{ data: T | null, error: ApiError | Error | null, aborted: boolean }。
abort()
() => void
中止当前上传,
status 切到 'aborted'。Changelog
No recent changes