useUploadWithProgress
Usage
useUploadWithProgress is built on the native XMLHttpRequest and reuses @movk/nuxt's endpoint, auth, business validation, Toast and movk:api:* hook system. The returned data is the business data unwrapped by dataKey, consistent with 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('Upload succeeded:', 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">Upload</UButton>
<UProgress v-if="status === 'pending'" :model-value="progress ?? undefined" :max="100" />
<UButton v-if="status === 'pending'" @click="abort">Cancel</UButton>
</template>
$api directly.Basic Multi-File Upload
fieldName determines the FormData field name (defaults to 'file'); upload() returns { data, error, aborted }:
<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>
Single File, Multiple Files and Extra Fields
Pass a File for single-file upload or a File[] for multiple files (appended under the same fieldName); fields injects extra form fields submitted alongside the file:
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' }
})
<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>
Timeout
timeoutMs sets the upload timeout in milliseconds (0 or omitted means no timeout). A timeout triggers the failure branch and error.message contains the timeout information:
<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>
Cross-Origin Credentials
await upload('https://other.example/upload', file, { withCredentials: true })
Abort Upload
const { status, upload, abort } = useUploadWithProgress()
upload('/api/upload', largeFile)
function handleCancel() {
if (status.value === 'pending') abort()
}
After aborting:
statuschanges to'aborted';progressresets to0errorremainsnull(not treated as failure)onSuccess/onErrorare not called; no Toast is shownupload()returns{ data: null, error: null, aborted: true }
Authentication
The token is automatically read from the session and injected into the Authorization header. You can override it with the same header name in headers (user value takes priority).
await upload('/api/upload', file, {
headers: { Authorization: 'Bearer override' }
})
API
useUploadWithProgress<T>()
useUploadWithProgress<T>(): { progress, status, data, error, upload, abort }
Returns
null is retained as a possible value but does not occur in practice for uploads.'idle' | 'pending' | 'success' | 'error' | 'aborted'.dataKey.ApiError; HTTP/network errors are Error. null on abort.Parameters
'file'.Returns
Promise<TransferResult<T>>: { data: T | null, error: ApiError | Error | null, aborted: boolean }.
status switches to 'aborted'.