Skip to content

上传

image-20250913131202059

FileList

files是FileList类型而不是File[]

FileList是一个类数组,

特性FileListArray
可否 forEach / map / filter
可否使用扩展运算符 [...files]
可否直接 push / pop
类型推断FileListany[]

FileList 不是数组,用 Array.from 转成真数组,类型、链式调用、兼容性 都更稳妥:

ts
const files = Array.from(fileInput.value?.files ?? []);

FormData格式传输

File对象和Blob对象都无法直接传输给后端,需要使用formData传输

ts
import axios from "axios"

const fd = new FormData()
fd.append("username", "alice")
fd.append("file", new File(["hello"], "hello.txt", { type: "text/plain" }))

await axios.post("/upload", fd)

axios 会自动识别 FormData,并设置:

  • Content-Type: multipart/form-data(带 boundary)
  • FormData 序列化成 multipart/form-data 格式上传
image-20250913130927458

base64格式传输

切片上传

ts
<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <div v-if="progress > 0">上传进度:{{ progress }}%</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'

const CHUNK_SIZE = 1024 * 1024 // 每片 1MB

const progress = ref(0)

const handleFileChange = async (event: Event) => {
  const file = (event.target as HTMLInputElement).files?.[0]
  if (!file) return

  const totalChunks = Math.ceil(file.size / CHUNK_SIZE)
  
  for (let i = 0; i < totalChunks; i++) {
    const start = i * CHUNK_SIZE
    const end = Math.min(file.size, start + CHUNK_SIZE)
    const chunk = file.slice(start, end)

    const formData = new FormData()
    formData.append('chunk', chunk)
    formData.append('fileName', file.name)
    formData.append('index', i.toString())
    formData.append('total', totalChunks.toString())

    await axios.post('/upload/chunk', formData)

    // 更新进度
    progress.value = Math.round(((i + 1) / totalChunks) * 100)
  }

  // 通知服务器合并
  await axios.post('/upload/merge', { fileName: file.name, totalChunks })
  alert('上传完成')
}
</script>

断点续传

ts
<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <div v-if="progress > 0">上传进度:{{ progress }}%</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'

const CHUNK_SIZE = 1024 * 1024 // 每片 1MB
const progress = ref(0)

// 本地记录已上传的片索引
const uploadedChunks = ref<Set<number>>(new Set())

const handleFileChange = async (event: Event) => {
  const file = (event.target as HTMLInputElement).files?.[0]
  if (!file) return

  const totalChunks = Math.ceil(file.size / CHUNK_SIZE)

  // 1️⃣ 查询服务器已上传的片索引
  const { data: serverUploaded } = await axios.get('/upload/status', {
    params: { fileName: file.name }
  })
  uploadedChunks.value = new Set(serverUploaded) // 假设服务器返回已上传的片索引数组

  // 2️⃣ 上传未完成的片
  for (let i = 0; i < totalChunks; i++) {
    if (uploadedChunks.value.has(i)) {
      // 跳过已上传的片
      progress.value = Math.round(((i + 1) / totalChunks) * 100)
      continue
    }

    const start = i * CHUNK_SIZE
    const end = Math.min(file.size, start + CHUNK_SIZE)
    const chunk = file.slice(start, end)

    const formData = new FormData()
    formData.append('chunk', chunk)
    formData.append('fileName', file.name)
    formData.append('index', i.toString())
    formData.append('total', totalChunks.toString())

    await axios.post('/upload/chunk', formData)

    progress.value = Math.round(((i + 1) / totalChunks) * 100)
  }

  // 3️⃣ 通知服务器合并
  await axios.post('/upload/merge', { fileName: file.name, totalChunks })
  alert('上传完成')
}
</script>