Vue3+Element Plus+Express实战文件上传

82 阅读6分钟

上传文件是前端常见的功能,比如说常见的场景有,登录用户的自定义用户头像,写博客或者新闻的列表封面图,商城系统的商品图等,考试系统上传试卷等。可以说上传文是每个前端开发者必备的技能。

本文将从全栈角度,使用 Node.js + Express 构建后端服务,搭配 Vue 3 + Element Plus 实现前端上传组件,涵盖以下核心内容:

  • 结构设计
  • 后端跨域配置
  • express 路由接口设计
  • multer 设置上传目录
  • multer 设置显示上单个文件大小
  • multer 接收多个文件和单个文件的方式
  • multer 文件命名
  • element-plus 中的el-upload的使用
  • el-upload 配置上传多个
  • el-upload 配置缩略图模式
  • el-upload 配置拖拽上传
  • el-upload 配置手动上传

一、 结构设计

后端结构

api/                          # 项目根目录(Node.js + Express 后端)
├── src/                      # 后端核心源码目录(保留您的原始结构)
│   ├── config/               # 配置文件目录
│   │   └── upload.js         # 上传配置(大小,个数等)
│   ├── controller/           # 业务逻辑控制器
│   │   └── UploadController.js # 处理文件上传的核心逻辑
│   ├── routes/               # 路由定义
│   │   └── index.js          # 接口路由配置
│   ├── utils/                # 工具函数
│   │   ├── upload.js         # 上传相关工具(校验/处理)
│   └── uploads/              # 文件存储目录
├── node_modules/             # Node.js 依赖库
├── package.json              # 项目配置和脚本
└── package-lock.json         # 依赖版本锁定文件

前端结构

web/                          # 项目根目录(Vue3 + Vite 前端项目)
├── .vscode/                  # VSCode 编辑器配置(如调试设置)
├── node_modules/             # 项目依赖库(npm/pnpm/yarn 安装)
├── public/                   # 静态资源目录(直接复制到构建输出)
│   └── favicon.ico           # 网站图标等(不会被 Vite 处理)
├── src/                      # 核心源代码目录
│   ├── assets/               # 静态资源(CSS/图片/字体等,会被 Vite 处理)
│   ├── components/           # 可复用 Vue 组件
│   │   └── upload/           # 文件上传相关组件
│   │       └── index.vue     # 上传组件主逻辑(Element Plus 集成)
│   ├── App.vue               # Vue 根组件
│   └── main.js               # 应用入口文件
├── .gitignore                # Git 忽略规则(排除 node_modules 等)
├── index.html                # 主 HTML 入口(Vite 注入脚本)
├── package.json              # 项目配置和依赖声明
├── pnpm-lock.yaml            # 依赖版本锁定文件(pnpm 专用)
├── README.md                 # 项目说明文档
└── vite.config.js            # Vite 构建配置(代理、插件等)

二、后端开发

1. 安装开发依赖

npm i express multer cors

2. src/index.js

const path = require("path");
const express = require("express");
const cors = require("cors");

const router = require("./routes");
const corsConfig = require("./config/cors");

const app = express();
// app.use(cors());
app.use(cors(corsConfig)); // 传入特定的配置
app.use("/uploads", express.static(path.resolve(process.cwd(), "uploads")));

app.use("/api", router);

app.listen(3000, () => {
  console.log("http://localhost:3000");
});


可以看到,在入口文件中:

  • 引入了express 来快速搭建后端服务
  • 引入了cors 配置来进行跨域的配置,可以进行源的配置,允许的请求方式的配置,允许的请求头配置,什么参数都不传的代表可以允许所有源,所有请求方式,所有请求头
  • 引入了外部的router, 并添加/api前缀

3. src/routes/index.js

const express = require("express");

const { fileCount } = require("../config/upload");
const { uploadController } = require("../controller/UploadController");
const { upload } = require("../utils/upload");
const router = express.Router();

router.post("/upload", upload.array("file", fileCount), uploadController);

module.exports = router;

在路由文件中:

  • 通过express.Router() 创建的路由系统进行请求的处理
  • upload.array("file", fileCount) 是multer 相关的配置,file 上传对应的字段名,fileCount 一次只能上传多少个,array是多个文件的时候,如果只是单个文件上传可以使用upload.array("single")
  • uploadController 处理请求响应相关逻辑

4. src/controller/UploadController.js

const upload = (req, res) => {
  console.log(req.files);
  if (!req.files?.length) {
    return res.json({
      code: 404,
      message: "未选择文件",
    });
  }
  // 将上传的文件数据返回给前端
  const uploads = req.files.map((file) => ({
    originalname: file.originalname,
    filename: file.filename,
    size: file.size,
    url: `http://localhost:3000/uploads/${file.filename}`,
  }));

  res.json({
    code: 200,
    message: "success",
    data: uploads,
  });
};

module.exports.uploadController = upload;


在UploadController 中:

  • 首先判断是否选择了文件,如果没有则直接返回未选择
  • 如果选择了文件 则处理数据格式通过data 字段将上传的数据信息返回给前端

5. src/utils/upload.js

const path = require("path");
const fs = require("fs");

const multer = require("multer");

const { fileSize } = require("../config/upload");
// 配置上传目录
const uploadDir = path.join(process.cwd(), "uploads");

// 如果目录不存在则创建一个对应的目录
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);

// Multer 配置,限制文件大小,存储规则
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, uploadDir),
  filename: (req, file, cb) => {
    const uniqueName = `${Date.now()}-${Math.random().toString(36).slice(2)}-${
      file.originalname
    }`;
    cb(null, uniqueName);
  },
});

const upload = multer({
  storage,
  limits: { fileSize },
});

module.exports.upload = upload;

在这个工具函数中:

  • 通过 multer.diskStorage 方法设置了上传的文件路径,上传的文件名
  • 当上传的文件夹不存在时,会自创建一个文件夹
  • 通过multer 方法的执行传的参数中的limits属性的fileSize 设置了单个文件的大小限制

6. src/config/cors

module.exports = {
  origin: "http://localhost:5173",
  methods: "GET,POST,PUT,DELETE",
  allowedHeaders: ["Content-type"],
};

在这个配置中主要配置了允许跨域请求的源,允许请求的方法,允许请求的请求头

7. src/config/upload.js

const fileSize = 1024 * 1024 * 10; // 限制每个文件上传的大小
const fileCount = 10; // 限制一次只能上传多少个文件

module.exports = {
  fileSize,
  fileCount,
};

7. packages.json中添加启动命令

"scripts": {
    "dev": "nodemon ./src/index.js"
  },

执行 npm run dev 启动项目

三、前端开发

1. 项目初始化和依赖安装

pnpm create vite web --template vue // 项目初始化
cd web // 切换到项目根目录
pnpm i element-plus
pnpm i unplugin-element-plus -D // element-plus手动引入时需要的vite插件
pnpm run dev // 启动项目

2. vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import ElementPlus from "unplugin-element-plus/vite";

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue(), ElementPlus()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"), // 配置别名
    },
  },
});

在这里面:

  • 添加了别名配置
  • 添加 unplugin-element-plus/vite 插件

3. App.vue

<template>
  <div>
    <upload />
  </div>
</template>

<script setup>
import upload from "@/components/upload/index.vue";
</script>

<style scoped></style>

4. src/compoents/upload/index.vue

<template>
  <div>
    <ElUpload
      action="http://localhost:3000/api/upload"
      v-model:file-list="fileList"
      method="post"
    >
      <el-button type="primary">上传文件</el-button>
    </ElUpload>
  </div>
</template>

<script setup>
import "element-plus/es/components/upload/style/css";
import { ref } from "vue";
import { ElUpload, ElButton } from "element-plus";
const fileList = ref([]);
</script>

<style scoped></style>

  • action 请求服务端的地址
  • 选择的文件列表
  • method 定义请求方式

演示效果:

upload.gif

查看api下面的uploads 就会发现多了刚才上传的文件

image.png

在浏览器的网络模块也能看到正常的返回值:

image.png

这样就完成了文件的上传功能。

5. 配置缩略图预览模式

安装element-plus 图标库

pnpm install @element-plus/icons-vue

在ElUpload组件上增加 list-type="picture-card", 导入相关组件:

import { ElUpload, ElIcon } from "element-plus";
import { Plus } from "@element-plus/icons-vue";

将模板中的

<el-button type="primary">上传文件</el-button>

修改成

<el-icon><Plus /></el-icon>

查看运行效果

没有选择图片上传之前

image.png

选择图片上传之后

image.png

6. 配置拖拽上传模式

在pc端的产品中,很多用户喜欢拖拽上传的方式,使用ElUpload实现拖拽上传也非常简单,只需要添加drag属性即可

image.png

7. 配置手动上传

ElUpload 组件默认选择完文件后就开始上传,但是有时候,我们需要选择完之后,看下效果,点击确定在上传。ElUpload 组件实现手动上传需要做如下修改:

  • 增加ref="uploadRef" 用来手动提价
  • :auto-upload="false" 阻止选择完成之后上传
  • <ElIcon><Plus></Plus></ElIcon> 移动到 template 中,并且template 上添加#trigger, 这是命名slot 的简写形式
  • 添加手动提交的按钮
<ElButton type="success" class="click-upload" @click="submitUpload"
        >点击上传</ElButton
      >

image.png

js 中,添加手动处理逻辑:

const uploadRef = ref();
const submitUpload = () => {
  uploadRef.value.submit();
};

来看下效果:

step-upload.gif 可以看到,我们选择文件的时候,并没向后端发送请求,点击了点击上传按钮之后才发送请求的。这样就实现了手动上传。

7. 配置一次可以选择多个文件进行上传

ElUpload 实现选择多个文件上传,其实也很简单,只需要添加组件上添加multiple 属性即可。

image.png

四、总结

本篇主要分享了文件上传的整个过程,后端使用Node, Express,multer实现上传功能,前端使用Vue3,Element Plus 实现前端的交互的,并详细介绍了常用的上传模式配置。

感谢你的收看,若您对Vue3开发感兴趣,可以关注Vue 知识储备专栏