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 的区别讲清楚。

相关推荐
花开盛夏^.^7 小时前
电脑打开多个浏览器Web窗口占用较大的运行内存的原因
前端·浏览器
蒸蒸yyyyzwd7 小时前
AI应用相关可能的面试问题
笔记
Dxy12393102167 小时前
JS DES 加密详解
开发语言·javascript·arcgis
充值内卷7 小时前
我给桌面加了个鱼缸:C++ 写后端,WebView2 里养一座 Three.js 小岛
开发语言·javascript·c++
馒头不想说话8 小时前
实体店老板的24小时AI运营员工,写、剪、发全自动,省下一整个团队工资
前端
叼烟扛炮8 小时前
[Git 实战] 学习笔记:从生成 SSH 密钥到解决 TortoiseGit 推送报错全指南
linux·运维·笔记·git·学习
蒸蒸yyyyzwd9 小时前
学习笔记 八股手撕
笔记·学习
椒哥10 小时前
一天被问几十次"允许",我统计了 517 次,然后写了个插件
前端·javascript·github
风骏时光牛马10 小时前
生成一个AI稳定性治理及疑难问题分析标题,只需要一个标题,不需要其他的任何文本。
前端
揣一尾游鱼yu10 小时前
Spring上(IOC)笔记
java·笔记·spring·ioc