useUploadWithProgress

View source
带进度监控的文件上传 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>

单文件、多文件与额外字段

filesFile 为单文件、传 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 重置为 0
  • error 保持 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>>
执行上传。
abort()
() => void
中止当前上传,status 切到 'aborted'

Changelog

No recent changes
Copyright © 2025 - 2026 YiXuan - MIT License