Vue 中实现 PDF 文件上传

在 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>

三、关键注意事项

  1. 文件类型验证

    • 通过 file.type === 'application/pdf' 验证 MIME 类型,但部分环境可能不准确,建议同时检查文件名后缀 file.name.endsWith('.pdf')
  2. 文件大小限制:后端通常也会有大小限制,前端验证仅作为友好提示,需和后端保持一致(例如 10MB)。

  3. FormData 格式 :文件上传必须使用 FormData 包装,且 axios 会自动设置 Content-Type: multipart/form-data不要手动设置此请求头(否则可能导致后端解析失败)。

  4. Electron 环境特殊处理

    • 如果需要直接读取本地文件路径(非通过 input 选择),可使用 Electron 的dialog.showOpenDialog选择文件:

      javascript 复制代码
      const { 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 组件或原生实现即可。

相关推荐
JohnYan2 小时前
Bun技术评估 - 29 Docker集成
javascript·后端·docker
玉宇夕落2 小时前
JavaScript 执行状态全景图:从调用栈到事件循环,深入理解异步机制
javascript
ohyeah2 小时前
深入理解 JavaScript 数组:从创建到遍历的完整指南
前端·javascript
顾三殇2 小时前
【TRAE】AI 编程:颠覆全栈开发,基于 TRAE AI 编程完成 Vue 3 + Node.js + MySQL 企业级项目实战,从环境搭建到部署上线
vue.js·ai编程·trae·ai 开发工具
一室易安3 小时前
模仿elementUI 中Carousel 走马灯卡片模式 type=“card“ 的自定义轮播组件 图片之间有宽度
前端·javascript·elementui
在下胡三汉3 小时前
创建轻量级 3D 资产 - Three.js 中的 GLTF 案例
开发语言·javascript·3d
脸大是真的好~3 小时前
黑马JAVAWeb -Vue工程化 - Element Plus- 表格-分页条-中文语言包-对话框-Form表单
前端·javascript·vue.js
程序猿_极客3 小时前
【期末网页设计作业】HTML+CSS+JS 香港旅游网站设计与实现 (附源码)
javascript·css·html
blog_wanghao3 小时前
PDF文件内容出现重叠现象解析
c++·pdf