---
title: "useUploadWithProgress"
description: "带进度监控的文件上传 composable，支持业务校验、超时、取消。"
seo_title: "useUploadWithProgress"
seo_description: "Upload files with real-time progress, abort support, custom form fields and shared endpoint/auth/business-code handling with the API system."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/composables/use-upload-with-progress"
---
# useUploadWithProgress

> 带进度监控的文件上传 composable，支持业务校验、超时、取消。

## 用法

`useUploadWithProgress` 基于原生 `XMLHttpRequest` 实现，复用 `@movk/nuxt` 的端点、认证、业务校验、Toast、`movk:api:*` hook 体系。返回的 `data` 是按 `dataKey` 解包后的业务数据，与 `useApiFetch<T>` 一致。

```vue
<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>
```

> \[\!NOTE\]
> 
> 适用于需要展示上传进度的场景。对于无需进度的小文件，可以直接使用 
> 
> $api
> 
>  发起请求。

## 基础多文件上传

`fieldName` 决定 FormData 字段名（默认 `'file'`）；`upload()` 返回 `{ data, error, aborted }`：

```vue [ComposablesUseUploadProgressBasicExample.vue]
<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` 注入额外表单字段一并提交：

```ts
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' }
})
```

```vue [ComposablesUseUploadProgressSingleExample.vue]
<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` 包含超时信息：

```vue [ComposablesUseUploadProgressTimeoutExample.vue]
<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>
```

## 跨域携带凭证

```ts
await upload('https://other.example/upload', file, { withCredentials: true })
```

## 取消上传

```ts
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` 中覆盖同名头（用户优先）。

```ts
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默认 'file'。用户优先于自动 auth 头。超时毫秒数，0 或省略不超时。解包后的业务数据。ReturnsPromise<TransferResult<T>>：{ data: T | null, error: ApiError | Error | null, aborted: boolean }。

**abort()** (`() => void`): 中止当前上传，status 切到 'aborted'。

## Changelog

See commit history for [src/runtime/composables/useUploadWithProgress.ts](https://github.com/mhaibaraai/movk-nuxt/commits/main/src/runtime/composables/useUploadWithProgress.ts).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
