在 Vue 中实现 PDF 文件上传,核心是通过文件选择控件获取 PDF 文件,验证文件合法性(类型、大小等),再通过 axios 提交到后端接口。结合你的项目(使用 Element UI),以下是完整实现方案:
一、基础方案:使用 Element UI Upload 组件(推荐)
Element UI 的 el-upload 组件封装了文件选择、验证、上传等逻辑,使用简单且样式统一,适合快速集成。
1. 组件代码(单文件上传)
vue
<template>
<div class="pdf-upload">
<!-- 上传按钮 -->
<el-upload
class="upload-demo"
action="" <!-- 不直接用action,改为手动上传 -->
:auto-upload="false" <!-- 关闭自动上传,手动触发 -->
:on-change="handleFileChange" <!-- 文件选择变化时触发 -->
:accept=".pdf" <!-- 限制只能选择PDF文件 -->
:file-list="fileList" <!-- 已选择的文件列表 -->
:limit="1" <!-- 限制只能上传1个文件 -->
:on-exceed="handleExceed" <!-- 超过数量限制时触发 -->
>
<el-button size="small" type="primary">选择 PDF 文件</el-button>
<div slot="tip" class="el-upload__tip">
只能上传 PDF 格式文件,且大小不超过 10MB
</div>
</el-upload>
<!-- 上传按钮 -->
<el-button
size="small"
type="success"
@click="handleUpload"
:disabled="!fileList.length"
>
上传文件
</el-button>
</div>
</template>
<script>
import api from '@/api' // 引入封装的axios接口
export default {
data() {
return {
fileList: [] // 存储选择的文件
}
},
methods: {
// 文件选择变化时触发(验证文件)
handleFileChange(file, fileList) {
this.fileList = fileList.slice(-1) // 只保留最后选择的1个文件(配合limit=1)
// 验证文件类型
if (file.raw.type !== 'application/pdf') {
this.$message.error('请上传 PDF 格式的文件!')
this.fileList = [] // 清空无效文件
return false
}
// 验证文件大小(10MB = 10 * 1024 * 1024 bytes)
const maxSize = 10 * 1024 * 1024
if (file.size > maxSize) {
this.$message.error('文件大小不能超过 10MB!')
this.fileList = [] // 清空无效文件
return false
}
},
// 超过文件数量限制时触发
handleExceed(files, fileList) {
this.$message.warning(`最多只能上传 1 个 PDF 文件`)
},
// 手动触发上传
async handleUpload() {
if (!this.fileList.length) return
const file = this.fileList[0].raw // 获取原生文件对象
// 构造 FormData(文件上传必须用 FormData 格式)
const formData = new FormData()
formData.append('pdfFile', file) // 'pdfFile' 对应后端接口的参数名
// 如需额外参数,可继续 append,例如:
// formData.append('userId', 123)
try {
// 调用上传接口(需在 api 中定义)
const res = await api.file.uploadPdf(formData)
this.$message.success('文件上传成功!')
console.log('上传结果:', res)
// 上传成功后清空文件列表
this.fileList = []
} catch (error) {
this.$message.error('文件上传失败,请重试!')
console.error('上传错误:', error)
}
}
}
}
</script>
<style scoped>
.pdf-upload {
margin: 20px;
}
.el-upload__tip {
margin-top: 10px;
}
</style>
2. 封装上传接口(src/api/file.js)
javascript
import request from '@/utils/request'
export default {
// 上传 PDF 文件
uploadPdf(formData) {
return request({
url: '/api/file/upload-pdf', // 后端上传接口地址
method: 'post',
data: formData, // 直接传递 FormData 对象
// 上传进度配置(可选)
onUploadProgress: progressEvent => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100)
console.log(`上传进度:${percent}%`)
// 可在这里更新进度条状态
}
})
}
}
二、原生方案:使用 input [type="file"](不依赖 UI 组件)
如果不使用 Element UI,可直接用原生 input 控件实现,核心逻辑一致:
vue
<template>
<div class="native-upload">
<input
type="file"
accept=".pdf"
@change="handleFileSelect"
class="file-input"
>
<button @click="handleUpload" :disabled="!selectedFile">上传 PDF</button>
<p v-if="uploadProgress > 0 && uploadProgress < 100">
上传中:{{ uploadProgress }}%
</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
selectedFile: null,
uploadProgress: 0
}
},
methods: {
// 选择文件
handleFileSelect(e) {
const file = e.target.files[0]
if (!file) return
// 验证文件类型
if (file.type !== 'application/pdf' && !file.name.endsWith('.pdf')) {
alert('请选择 PDF 格式文件!')
e.target.value = '' // 清空选择
return
}
// 验证文件大小(10MB)
if (file.size > 10 * 1024 * 1024) {
alert('文件大小不能超过 10MB!')
e.target.value = ''
return
}
this.selectedFile = file
},
// 上传文件
async handleUpload() {
if (!this.selectedFile) return
const formData = new FormData()
formData.append('pdfFile', this.selectedFile)
try {
const res = await axios.post('/api/file/upload-pdf', formData, {
onUploadProgress: progress => {
this.uploadProgress = Math.round((progress.loaded / progress.total) * 100)
}
})
alert('上传成功!')
this.selectedFile = null
this.uploadProgress = 0
} catch (error) {
alert('上传失败!')
console.error(error)
}
}
}
}
</script>
<style>
.file-input {
margin: 10px 0;
}
button {
padding: 6px 12px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
三、关键注意事项
-
文件类型验证:
- 通过
file.type === 'application/pdf'验证 MIME 类型,但部分环境可能不准确,建议同时检查文件名后缀file.name.endsWith('.pdf')。
- 通过
-
文件大小限制:后端通常也会有大小限制,前端验证仅作为友好提示,需和后端保持一致(例如 10MB)。
-
FormData 格式 :文件上传必须使用
FormData包装,且 axios 会自动设置Content-Type: multipart/form-data,不要手动设置此请求头(否则可能导致后端解析失败)。 -
Electron 环境特殊处理:
-
如果需要直接读取本地文件路径(非通过 input 选择),可使用 Electron 的
dialog.showOpenDialog选择文件:javascriptconst { remote } = require('electron') const { dialog } = remote // 手动选择文件(不通过 input) async selectFileManually() { const result = await dialog.showOpenDialog({ filters: [{ name: 'PDF Files', extensions: ['pdf'] }], // 只显示 PDF properties: ['openFile'] }) if (!result.canceled) { const filePath = result.filePaths[0] // 读取文件并转换为 Blob 后上传(需用 fs 模块) const fs = require('fs') const fileBuffer = fs.readFileSync(filePath) const file = new Blob([fileBuffer], { type: 'application/pdf' }) this.selectedFile = file } }
-
四、后端接口要求
后端接口需支持 multipart/form-data 类型请求,以 Node.js(Express)为例,需使用 multer 中间件处理文件:
javascript
const express = require('express')
const multer = require('multer')
const app = express()
// 配置上传目录和文件名
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname)
})
const upload = multer({
storage,
limits: { fileSize: 10 * 1024 * 1024 }, // 限制10MB
fileFilter: (req, file, cb) => {
// 后端再次验证文件类型
if (file.mimetype !== 'application/pdf') {
return cb(new Error('只允许上传 PDF 文件'))
}
cb(null, true)
}
})
// 上传接口
app.post('/api/file/upload-pdf', upload.single('pdfFile'), (req, res) => {
res.json({
code: 200,
message: '上传成功',
data: { filePath: req.file.path }
})
})
通过以上方案,可在 Vue 中快速实现 PDF 文件上传功能,结合你的 Electron + Vue 项目场景,按需选择 Element 组件或原生实现即可。