Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览

一、前言

上传是"表单之外"的文件流场景:头像、Excel 导入、附件、视频。本篇四连:el-upload 基础与校验 → 自定义上传对接 062 篇 api → 头像即时预览 → 大文件分片上传思路。

二、el-upload 核心认知

el-upload 的两个工作模式:

text 复制代码
① 自动上传:选中文件立即发请求(action 指定接口)
② 手动上传:auto-upload=false → 用户点"提交"时才一起发(表单场景)

★ 企业真实做法:不用 action(因为要带 token/统一响应处理)
  用 :http-request 覆盖默认上传 → 内部调 062 篇的 api

关键 props 速查:

props 作用
action 上传地址(自定义上传时随便填个 # 也要写,组件要求必传)
:auto-upload 选完是否立即传
:before-upload 上传前校验,返回 false / Promise.reject 可拦截
:show-file-list 显示已上传文件列表
:limit / :on-exceed 数量限制 / 超出回调
:http-request 自定义上传函数(企业标配)
:on-success / :on-error 成功/失败回调(action 模式用)

三、完整可运行代码:头像上传(校验 + 预览 + 自定义上传)

vue 复制代码
<!-- src/views/AvatarUploadView.vue -->
<template>
  <el-upload
    :show-file-list="false"
    :before-upload="beforeUpload"
    :http-request="customUpload"
    accept="image/png, image/jpeg"
  >
    <el-avatar :size="80" :src="previewUrl">
      {{ previewUrl ? '' : '上传' }}
    </el-avatar>
    <p>点击头像更换(png/jpg,≤ 2MB)</p>
  </el-upload>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { UploadRequestOptions } from 'element-plus'
import { uploadAvatar } from '@/api/upload'   // 062 篇的 api 模块

const previewUrl = ref('')

// ① 上传前校验:类型 + 大小(双保险,accept 只是"文件选择器提示")
function beforeUpload(file: File) {
  const isImage = ['image/png', 'image/jpeg'].includes(file.type)
  const isLt2M = file.size / 1024 / 1024 < 2
  if (!isImage) {
    ElMessage.error('只能上传 png/jpg 图片')
    return false                       // ★ 返回 false 拦截上传
  }
  if (!isLt2M) {
    ElMessage.error('图片不能超过 2MB')
    return false
  }
  return true
}

// ② 自定义上传:内部走统一的 request(token、错误提示全自动,060 篇白封装了)
async function customUpload(options: UploadRequestOptions) {
  const fd = new FormData()
  fd.append('file', options.file)      // ★ 文件进 FormData,字段名和后端对齐

  const res = await uploadAvatar(fd)   // request.post('/upload/avatar', fd)
  if (res.code === 0) {
    previewUrl.value = res.data.url    // 服务器返回的图片地址
    ElMessage.success('头像更新成功')
  }
}
</script>
ts 复制代码
// src/api/upload.ts
import { post } from '@/utils/request'

/** 上传:Content-Type 交给浏览器自动设置 multipart 边界,request 里别手动写死 */
export function uploadAvatar(data: FormData) {
  return post<{ url: string }>('/upload/avatar', data)
}

【截图位置:点击头像选图 → 校验失败红提示 / 成功后头像刷新】

本地预览技巧(上传前就看到效果,弱网体验好):

ts 复制代码
// file.raw 是原生 File 对象(el-upload 包装层里的)
const url = URL.createObjectURL(options.file)
// ★ 用完/替换时释放,防内存泄漏
URL.revokeObjectURL(oldUrl)

四、手动上传模式:表单一起提交

附件场景:文件选了先"暂存",和表单一起提交:

vue 复制代码
<el-upload :auto-upload="false" :limit="3" :on-exceed="onExceed" v-model:file-list="fileList">
  <el-button>选择附件(最多 3 个)</el-button>
</el-upload>
<el-button type="primary" @click="onSubmitAll">提交</el-button>
ts 复制代码
const fileList = ref<any[]>([])

async function onSubmitAll() {
  const fd = new FormData()
  fd.append('title', title.value)
  fileList.value.forEach((f) => fd.append('files', f.raw))   // 多文件同名数组
  const res = await post('/article/create', fd)
  // 成功后清空列表
  fileList.value = []
}

五、大文件分片上传思路(概念 + 骨架代码)

500MB 视频:单个请求超时、失败从头再来。思路 = 切片 + 并发 + 合并:

text 复制代码
1. 前端切片:file.slice(0, 5MB)、file.slice(5MB, 10MB)...(File 继承 Blob 自带 slice)
2. 计算文件 hash(如 spark-md5)作为唯一标识 → 询问后端"传过没"(秒传/断点续传)
3. 并发上传所有分片(带 index 序号)
4. 全部完成后通知后端按序合并
ts 复制代码
// 切片骨架(面试常考思路)
async function chunkUpload(file: File, CHUNK_SIZE = 5 * 1024 * 1024) {
  const chunks = Math.ceil(file.size / CHUNK_SIZE)
  const tasks: Promise<unknown>[] = []
  for (let i = 0; i < chunks; i++) {
    const blob = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE)
    const fd = new FormData()
    fd.append('chunk', blob)
    fd.append('index', String(i))
    fd.append('hash', await getFileHash(file))   // spark-md5 计算
    tasks.push(post('/upload/chunk', fd))        // 进阶:并发控制(3 个一批)
  }
  await Promise.all(tasks)
  await post('/upload/merge', { hash: await getFileHash(file), chunks })
}

关键词记牢:slice 切片、hash 秒传、断点续传(跳过已传分片)、并发控制------面试和实战都够用,完整实现属后端配合的进阶课题。

六、踩坑记录

  1. FormData 被 request 默认转 JSON :axios 对 FormData 会自动设 multipart 边界------别手动设 Content-Type,手动写 application/json 文件就废了
  2. before-upload 返回 Promise.reject 但没提示:拦截了用户却不知道原因------reject 前先 ElMessage
  3. accept 以为能拦住非法文件:accept 只是文件选择器的过滤提示,拖拽/改后缀可绕过------before-upload 里再校验一次
  4. 对象 URL 不释放:createObjectURL 多次调用内存暴涨------替换/卸载时 revokeObjectURL
  5. 多文件上传数量失控:没配 :limit 和 :on-exceed------两者成对配置
  6. 分片并发拉满:200 个分片同时发,浏览器连接耗尽反而慢------并发控制(每批 3~6 个,Promise 池)
  7. 上传中页面关闭/刷新:进度丢失且服务端留垃圾分片------beforeunload 提示 + 后端分片过期清理

七、今日小结

  • el-upload 企业姿势::http-request 自定义上传,内部走统一 request(token/错误全复用)
  • 校验双保险:accept 提示 + before-upload 真拦截(类型 + 大小)
  • 表单场景:auto-upload=false 暂存 file-list,提交时进 FormData
  • 大文件关键词:slice 切片 → hash 秒传 → 并发上传 → 后端合并

下篇预告

后台做多了,试试移动端?下一篇 083 Vue3 移动端初探:Vant 4 按需引入 + 移动端适配方案(vw/rem),一套组件库和 PC 的区别讲清楚。

相关推荐
鬼手点金1 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
柳杉1 小时前
借助 Codex 和可视化工作流,从调研到完整实现,3D 大屏竟然可以这么简单
前端·ai编程·数据可视化
IMPYLH2 小时前
HTML 的 <wbr> 元素
前端·html
后季暖2 小时前
电商问数笔记(2)
笔记
xier_ran2 小时前
【infra之路】AI 编译器学习笔记(二):重拾 C++——移动语义与 LLVM 风格的类型转换
人工智能·笔记·学习
Dovis(誓平步青云)2 小时前
常用命令藏得太深?怎么做一个快捷命令坞
运维·前端·自动化·音视频·prometheus
IT_陈寒3 小时前
Vite的这个问题坑了我三天,原来如此解决!
前端·人工智能·后端
网易云信3 小时前
大模型能上岗做销售吗?我们让 12 款国产模型交了 420 份作业
前端·后端
蜗牛互联网3 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python