若依框架实战:手把手教你搞定视频上传与预览(Vue3 + Element Plus版)
若依框架实战:Vue3+Element Plus视频上传与预览全流程解析
在Vue技术栈升级浪潮中,若依框架(Ruoyi)作为企业级快速开发平台,其Vue3+Element Plus版本带来了显著的性能提升和开发体验优化。本文将深入探讨如何基于新版技术栈实现视频上传与预览功能,解决开发者在迁移过程中遇到的实际问题。
1. 环境准备与项目配置
1.1 初始化若依Vue3项目
首先确保已安装Node.js 16+版本,然后通过以下命令创建项目:
npm init vue@latest ruoyi-vue3-video --template vue-ts cd ruoyi-vue3-video npm install安装Element Plus及相关依赖:
npm install element-plus @element-plus/icons-vue axios1.2 配置基础请求模块
在src/utils/request.ts中配置axios实例:
import axios from 'axios' import { getToken } from '/@/utils/auth' const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 5000 }) service.interceptors.request.use(config => { if (getToken()) { config.headers['Authorization'] = 'Bearer ' + getToken() } return config })2. 视频上传组件重构
2.1 Composition API实现核心逻辑
使用Vue3的setup语法糖重构上传逻辑:
<script setup lang="ts"> import { ref } from 'vue' import { ElMessage } from 'element-plus' import { getToken } from '/@/utils/auth' const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload' const headers = { Authorization: 'Bearer ' + getToken() } const videoUrl = ref('') const uploadProgress = ref(0) const isUploading = ref(false) const beforeUpload = (file: File) => { const validTypes = ['video/mp4', 'video/ogg', 'video/webm'] const isLt50M = file.size / 1024 / 1024 < 50 if (!validTypes.includes(file.type)) { ElMessage.error('仅支持MP4/OGG/WEBM格式') return false } if (!isLt50M) { ElMessage.error('视频大小不能超过50MB') return false } isUploading.value = true return true } const handleProgress = (event: ProgressEvent) => { uploadProgress.value = Math.round((event.loaded / event.total) * 100) } const handleSuccess = (response: any) => { videoUrl.value = response.data.url isUploading.value = false uploadProgress.value = 0 } </script>2.2 模板结构优化
采用Element Plus的Upload组件实现更优雅的UI:
<template> <el-upload class="video-uploader" :action="uploadUrl" :headers="headers" :show-file-list="false" :before-upload="beforeUpload" :on-progress="handleProgress" :on-success="handleSuccess" > <video v-if="videoUrl" :src="videoUrl" controls class="video-preview" /> <el-icon v-else class="upload-icon"> <Plus /> </el-icon> <el-progress v-if="isUploading" type="circle" :percentage="uploadProgress" class="upload-progress" /> </el-upload> </template>3. 后端接口适配与优化
3.1 文件上传接口调整
Spring Boot后端需要调整文件接收方式:
@RestController @RequestMapping("/common") public class FileUploadController { @PostMapping("/upload") public AjaxResult uploadFile( @RequestParam("file") MultipartFile file, HttpServletRequest request) { try { String originalName = file.getOriginalFilename(); String extension = originalName.substring(originalName.lastIndexOf(".")); if (!Arrays.asList(".mp4", ".ogg", ".webm").contains(extension)) { return AjaxResult.error("不支持的文件格式"); } String fileName = UUID.randomUUID() + extension; String filePath = RuoYiConfig.getUploadPath() + fileName; File dest = new File(filePath); file.transferTo(dest); String url = ServletUtils.getRequestBaseUrl(request) + "/profile/upload/" + fileName; return AjaxResult.success().put("url", url); } catch (Exception e) { return AjaxResult.error(e.getMessage()); } } }3.2 跨域与文件大小配置
在application.yml中添加配置:
spring: servlet: multipart: max-file-size: 50MB max-request-size: 50MB ruoyi: profile: /path/to/upload4. 高级功能实现
4.1 视频预览优化
实现封面图生成与预览:
<script setup> import { ref, watch } from 'vue' const videoRef = ref<HTMLVideoElement | null>(null) const posterUrl = ref('') watch(videoUrl, (url) => { if (url) { generatePoster() } }) const generatePoster = () => { if (videoRef.value) { const canvas = document.createElement('canvas') canvas.width = 300 canvas.height = 200 const ctx = canvas.getContext('2d') videoRef.value.addEventListener('loadeddata', () => { ctx?.drawImage(videoRef.value, 0, 0, canvas.width, canvas.height) posterUrl.value = canvas.toDataURL('image/jpeg') }) } } </script>4.2 分片上传实现
对于大文件上传,可采用分片上传策略:
const chunkSize = 5 * 1024 * 1024 // 5MB const uploadByChunks = async (file: File) => { const chunks = Math.ceil(file.size / chunkSize) const fileMd5 = await calculateFileMd5(file) for (let i = 0; i < chunks; i++) { const start = i * chunkSize const end = Math.min(file.size, start + chunkSize) const chunk = file.slice(start, end) const formData = new FormData() formData.append('file', chunk) formData.append('chunkNumber', i.toString()) formData.append('totalChunks', chunks.toString()) formData.append('identifier', fileMd5) await axios.post('/common/chunk-upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) uploadProgress.value = Math.round(((i + 1) / chunks) * 100) } await axios.post('/common/merge', { filename: file.name, identifier: fileMd5, totalChunks: chunks }) }5. 性能优化与错误处理
5.1 上传队列管理
实现并发控制和失败重试机制:
class UploadQueue { private maxConcurrent = 3 private queue: UploadTask[] = [] private activeCount = 0 add(task: UploadTask) { this.queue.push(task) this.run() } private async run() { if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) { return } this.activeCount++ const task = this.queue.shift() try { await task.execute() } catch (error) { if (task.retryCount < 3) { task.retryCount++ this.queue.unshift(task) } else { console.error('Upload failed after retries') } } finally { this.activeCount-- this.run() } } }5.2 类型安全增强
为上传组件添加完整的TypeScript类型定义:
interface UploadResponse { code: number msg: string data: { url: string name: string size: number } } interface UploadProgressEvent { loaded: number total: number percent?: number } interface UploadRequestOptions { action: string file: File filename: string headers?: Record<string, string> onProgress?: (event: UploadProgressEvent) => void onSuccess?: (response: UploadResponse) => void onError?: (error: Error) => void }在项目实际部署中,我们发现视频格式检测不能仅依赖前端验证,后端必须进行二次校验。同时,对于移动端上传场景,建议增加压缩选项和网络状态检测功能。
