当前位置: 首页 > news >正文

uniapp实战:混合使用组件与API,优雅实现图片与视频的上传与预览

1. 为什么需要混合使用组件与API?

在uniapp开发中,处理媒体文件上传是个高频需求。很多开发者一开始都会像我一样,想找个现成的组件一把梭搞定所有功能。uView的u-upload组件确实是个不错的选择,它封装了图片上传的完整流程:从相册选择、拍照、预览到上传一气呵成。但实际用起来就会发现,当涉及到视频处理时,这个组件就显得力不从心了。

最典型的问题就是视频预览功能。u-upload组件对图片的预览支持很完善,点击就能全屏查看,但换成视频就完全失效。我最初以为是自己的写法有问题,反复检查文档和示例代码,最后才确认是组件本身的限制。这时候就需要换个思路——为什么不把组件的优势和原生API结合起来用呢?

这种混合方案有几个明显好处:

  • 各取所长:u-upload处理图片更高效,原生API处理视频更灵活
  • 性能优化:避免用单一方案处理所有媒体类型导致的性能损耗
  • 体验统一:虽然底层实现不同,但给用户的交互体验可以保持一致

2. 图片上传的最佳实践

2.1 u-upload组件的正确打开方式

先来看图片上传的实现。u-upload组件已经帮我们封装好了大部分功能,只需要简单配置就能用:

<u-upload :fileList="imgList" @afterRead="imgRead" @delete="deletePic" name="1" multiple :previewFullImage="true" ></u-upload>

几个关键参数说明:

  • fileList:绑定已选择的图片列表
  • afterRead:选择图片后的回调
  • delete:删除图片的回调
  • multiple:是否支持多选
  • previewFullImage:开启全屏预览

这里有个小技巧:如果只需要上传单张图片,可以把multiple设为false,这时afterRead回调的参数event.file会是对象而非数组,处理起来更简单。

2.2 图片上传的完整流程

选择图片后的处理逻辑很关键,我整理了一个健壮的实现方案:

async imgRead(event) { // 统一转为数组处理 let lists = [].concat(event.file) let fileListLen = this.imgList.length // 先更新UI显示上传状态 lists.map((item) => { this.imgList.push({ ...item, status: 'uploading', message: '上传中' }) }) // 逐个上传 for (let i = 0; i < lists.length; i++) { try { const result = await this.uploadImg(lists[i], i) let item = this.imgList[fileListLen] this.imgList.splice(fileListLen, 1, Object.assign(item, { status: 'success', message: '', accessoryUrl: result })) fileListLen++ } catch (error) { // 处理上传失败 this.imgList[fileListLen].status = 'failed' this.imgList[fileListLen].message = '上传失败' } } }

这个实现有几个亮点:

  1. 统一处理单选和多选情况
  2. 实时更新上传状态反馈给用户
  3. 加入错误处理提升健壮性
  4. 使用async/await让异步代码更清晰

2.3 上传服务器的关键细节

实际项目中,上传接口通常需要携带认证信息和额外参数:

uploadImg(item, index) { return new Promise((resolve, reject) => { uni.uploadFile({ url: 'https://your-api.com/upload', filePath: item.url, name: 'file', header: { 'Authorization': `Bearer ${getToken()}` }, formData: { type: 'image', filename: item.name }, success: (res) => { const data = JSON.parse(res.data) resolve(data.url) }, fail: (err) => { reject(err) } }) }) }

注意几个细节:

  • header中传递认证token
  • formData可以附加业务参数
  • 服务端返回的数据需要JSON.parse处理
  • 使用Promise包装便于异步控制

3. 视频上传的灵活方案

3.1 为什么不用u-upload处理视频?

虽然u-upload组件在文档中说支持视频,但实际使用会发现几个问题:

  1. 视频预览功能无效
  2. 无法获取视频时长等元信息
  3. 上传进度反馈不准确
  4. 大视频文件处理性能差

这时候就需要请出uniapp的原生API——uni.chooseVideo,它专门为视频场景做了优化。

3.2 视频选择与上传实现

视频处理的整体流程与图片类似,但实现细节有所不同:

<view class="up-video-box"> <!-- 已选视频预览 --> <view v-for="(item,i) in videoList" :key="i"> <video :src="item.url"></video> <u-icon @click="deleteVideo(i)" name="close"></u-icon> </view> <!-- 选择视频按钮 --> <view @click="chooseVideo"> <u-icon name="camera-fill"></u-icon> </view> </view>

对应的JS逻辑:

chooseVideo() { uni.chooseVideo({ sourceType: ['camera', 'album'], maxDuration: 60, // 限制60秒 success: (res) => { this.videoList.push({ url: res.tempFilePath, name: res.tempFilePath.split('/').pop() }) this.uploadVideo(res.tempFilePath) } }) }

chooseVideo的几个实用参数:

  • sourceType:控制是拍照还是相册选择
  • maxDuration:限制视频时长
  • compressed:是否压缩视频

3.3 视频上传的注意事项

视频文件通常较大,上传时需要特别注意:

uploadVideo(filePath) { const task = uni.uploadFile({ url: 'https://your-api.com/upload', filePath, name: 'video', header: { 'Authorization': `Bearer ${getToken()}` }, formData: { type: 'video' }, success: (res) => { const data = JSON.parse(res.data) // 更新视频列表中的url为服务器返回的地址 }, fail: (err) => { console.error('上传失败', err) } }) // 监听上传进度 task.onProgressUpdate((res) => { console.log(`进度: ${res.progress}%`) }) }

相比图片上传,视频上传需要额外注意:

  1. 使用onProgressUpdate显示上传进度
  2. 处理可能的超时问题
  3. 考虑断点续传方案(对大视频特别重要)
  4. 服务端要做大小限制

4. 混合方案的架构设计

4.1 状态管理的统一处理

虽然图片和视频使用了不同技术方案,但在数据管理上应该保持统一:

data() { return { mediaList: [], // 统一管理所有媒体文件 imgList: [], // 专门用于u-upload绑定的图片列表 videoList: [] // 视频列表 } }

建议采用这种混合管理方式:

  • mediaList存储所有媒体文件的元信息
  • imgListvideoList分别用于对应组件的绑定

4.2 上传队列与并发控制

当用户同时选择多个文件时,需要合理控制上传并发:

class UploadQueue { constructor(maxConcurrent = 2) { this.queue = [] this.activeCount = 0 this.maxConcurrent = maxConcurrent } add(task) { this.queue.push(task) this.run() } run() { while (this.activeCount < this.maxConcurrent && this.queue.length) { const task = this.queue.shift() this.activeCount++ task().finally(() => { this.activeCount-- this.run() }) } } } // 使用示例 const uploadQueue = new UploadQueue(2) uploadQueue.add(() => this.uploadImg(img1)) uploadQueue.add(() => this.uploadVideo(video1))

这个简单的队列实现可以:

  • 控制同时上传的文件数量
  • 避免网络拥堵
  • 提供更好的用户体验

4.3 错误处理与重试机制

网络请求难免会失败,完善的错误处理很重要:

async uploadWithRetry(file, maxRetry = 3) { let retryCount = 0 while (retryCount < maxRetry) { try { return await this.uploadFile(file) } catch (error) { retryCount++ if (retryCount >= maxRetry) throw error await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)) } } }

这个重试机制会:

  1. 最多重试3次
  2. 每次重试间隔时间递增(1s, 2s, 3s)
  3. 超过重试次数后抛出错误

5. 用户体验优化技巧

5.1 上传进度可视化

对于大文件上传,进度反馈非常重要:

<u-line-progress :percent="progress" :showText="false" ></u-line-progress>

配合上传任务的进度事件:

task.onProgressUpdate((res) => { this.progress = res.progress })

5.2 文件大小与类型校验

在上传前做客户端校验可以节省带宽:

// 图片校验 function validateImage(file) { const maxSize = 5 * 1024 * 1024 // 5MB const types = ['image/jpeg', 'image/png'] if (file.size > maxSize) { uni.showToast({ title: '图片不能超过5M', icon: 'none' }) return false } if (!types.includes(file.type)) { uni.showToast({ title: '仅支持JPEG/PNG格式', icon: 'none' }) return false } return true }

5.3 预览功能的增强实现

对于视频预览,可以这样优化:

previewVideo(url) { uni.navigateTo({ url: `/pages/videoPlayer?src=${encodeURIComponent(url)}` }) }

然后在单独页面中使用uniapp的video组件全屏播放:

<template> <view> <video :src="src" controls autoplay style="width:100vw;height:100vh" ></video> </view> </template>

这种实现方式比直接在当前页面弹出播放器体验更好,还能支持全屏旋转等功能。

6. 实际项目中的踩坑记录

6.1 安卓与iOS的兼容性问题

在真机测试时发现几个平台差异:

  1. iOS选择视频时tempFilePath可能不带扩展名
  2. 安卓部分机型上传大文件容易超时
  3. 各平台对视频格式的支持不一致

解决方案:

// 统一处理文件名 function getFileName(path) { let name = path.split('/').pop() if (!name.includes('.')) { // 根据文件头判断类型 const type = path.startsWith('file://') ? getFileTypeFromHeader(path) : 'mp4' name += `.${type}` } return name }

6.2 内存泄漏问题

长时间使用媒体上传功能后,发现页面性能下降。经排查是以下几个原因:

  1. 上传完成的文件引用未释放
  2. 事件监听器未移除
  3. 大尺寸预览图缓存

解决方法:

beforeDestroy() { // 释放视频对象 this.videoList.forEach(item => { item.url = null }) // 清除图片缓存 this.imgList.forEach(item => { uni.removeSavedFile({ filePath: item.url }) }) }

6.3 微信小程序特殊限制

在微信小程序环境中,有几个额外限制需要注意:

  1. 单次上传不能超过10MB
  2. 不支持多文件同时上传
  3. 视频选择时长默认不超过60秒

对应的适配方案:

// 微信环境下启用压缩 if (uni.getSystemInfoSync().platform === 'ios') { options.compressed = false } else { options.compressed = true }

7. 性能优化进阶方案

7.1 分片上传大文件

对于超过20MB的视频文件,建议实现分片上传:

async function chunkUpload(file, chunkSize = 5 * 1024 * 1024) { const fileSize = file.size const chunks = Math.ceil(fileSize / chunkSize) const fileMd5 = await getFileMd5(file) for (let i = 0; i < chunks; i++) { const start = i * chunkSize const end = Math.min(fileSize, start + chunkSize) const chunk = file.slice(start, end) await uploadChunk({ chunk, index: i, fileMd5, chunks }) } await mergeChunks(fileMd5) }

7.2 客户端压缩方案

对于图片文件,可以在上传前进行压缩:

function compressImage(file) { return new Promise((resolve) => { uni.compressImage({ src: file.url, quality: 70, success: (res) => { resolve(res.tempFilePath) } }) }) }

7.3 断点续传实现

基于本地缓存实现断点续传:

// 上传前检查本地记录 const uploadedChunks = uni.getStorageSync(fileMd5) || [] if (uploadedChunks.includes(chunkIndex)) { continue // 跳过已上传分片 } // 上传成功后更新记录 uploadedChunks.push(chunkIndex) uni.setStorageSync(fileMd5, uploadedChunks)

8. 完整代码结构示例

最后给出一个完整的页面结构参考:

<template> <view class="container"> <!-- 图片上传区域 --> <view class="section"> <text class="title">图片上传</text> <u-upload :fileList="imgList" @afterRead="handleImageRead" @delete="removeImage" multiple :maxCount="9" ></u-upload> </view> <!-- 视频上传区域 --> <view class="section"> <text class="title">视频上传</text> <view class="video-uploader"> <view v-for="(video, index) in videoList" :key="index" class="video-item" > <video :src="video.url"></video> <u-icon @click="removeVideo(index)" name="close"></u-icon> </view> <view @click="chooseVideo" class="add-btn"> <u-icon name="plus"></u-icon> </view> </view> </view> <!-- 提交按钮 --> <button @click="submit">提交</button> </view> </template> <script> export default { data() { return { imgList: [], videoList: [] } }, methods: { // 图片处理相关方法 async handleImageRead(event) { // 实现图片上传逻辑 }, removeImage(index) { this.imgList.splice(index, 1) }, // 视频处理相关方法 chooseVideo() { uni.chooseVideo({ success: (res) => { this.uploadVideo(res.tempFilePath) } }) }, async uploadVideo(filePath) { // 实现视频上传逻辑 }, removeVideo(index) { this.videoList.splice(index, 1) }, // 提交处理 async submit() { if (!this.imgList.length && !this.videoList.length) { uni.showToast({ title: '请至少上传一个文件', icon: 'none' }) return } try { await this.uploadAll() uni.showToast({ title: '提交成功' }) } catch (error) { uni.showToast({ title: '提交失败', icon: 'none' }) } }, async uploadAll() { const imagePromises = this.imgList.map(img => this.uploadImage(img) ) const videoPromises = this.videoList.map(video => this.uploadVideo(video.url) ) await Promise.all([...imagePromises, ...videoPromises]) } } } </script> <style> /* 样式省略 */ </style>

这个实现包含了我们讨论的所有关键点:

  1. 图片使用u-upload组件
  2. 视频使用原生API
  3. 统一的上传状态管理
  4. 完整的用户交互流程
  5. 健壮的错误处理

在实际项目中,你可能还需要根据具体需求调整:

  • 添加上传进度显示
  • 增加文件大小和类型校验
  • 实现更复杂的分片上传逻辑
  • 优化移动端显示效果
http://www.cnnetsun.cn/news/1422171.html

相关文章:

  • 当四足机器狗遇上3D激光雷达:为何放弃Gmapping,选择Hector SLAM构建栅格地图?
  • 真心不骗你!碾压级的降AI率网站 —— 千笔·降AIGC助手
  • VS2010+OpenCV2.4.9环境下的Zbar二维码识别实战(附完整代码)
  • SpringBoot3与OAuth2.1深度整合:从/oauth/token到/oauth2/token的平滑迁移指南
  • 告别官方限制!这款Github 52.7K Stars的ChatGPT桌面客户端,老Mac/Win/Linux都能用
  • sdut-python-实验六-面向对象编程
  • Hutool之Http工具类URL编码问题解析
  • 从ImageNet到RingMo:为什么遥感领域需要专属基础模型?
  • 救命神器!全行业通用AI论文网站,千笔ai写作 VS 学术猹
  • OpenClaw定时任务实践:GLM-4.7-Flash实现24/7自动化监控
  • 如何用毫米波雷达实现8.6米非接触式生命体征监测?mmVital-Signs完整指南
  • LTspice层次化设计实战:如何像搭积木一样构建复杂电路(附SubCircuit.asc示例)
  • 告别标注烦恼:用GraphCL对比学习,5分钟搞定图节点无监督表示
  • eVTOL低空经济低空无人机AI识别自动处理图像项目蓝图设计方案:实现从图像采集、实时传输、AI识别到结果输出的全流程自动化
  • 单片机/C/C++八股:(十九)栈和堆的区别?
  • 单片机/C/C++八股:(二十)指针常量和常量指针
  • Three.js TSL实战:5分钟打造酷炫粒子鼠标跟随效果(附完整代码)
  • QCustomPlot图表范围控制完全指南:从rescaleAxes到setRange的5种应用场景
  • Anaconda管理深度学习训练环境:多版本Python控制
  • 嵌入式SHA256轻量实现:抗侧信道、恒定时间、MCU级哈希引擎
  • HarmonyOS开发实战指南(三)——从零构建鸿蒙原子化服务与Ability框架解析
  • 解决Overleaf中伪代码排版难题:从基础到高级配置全指南
  • 基于STM32+LiteOS的多传感器空气质量监测系统设计
  • java毕业设计基于springboot+vue的企业员工考勤管理系统
  • M2LOrder GPU算力适配方案:RTX 3060显存优化+FP16推理加速实测
  • 哪个降AI率的好?先看这5个评判标准再做选择
  • OpenClaw版本升级:Qwen3-32B兼容性测试与回滚方案
  • 效率直接起飞!AI论文网站 千笔·专业论文写作工具 VS Checkjie,全行业通用首选
  • ThinkPHP 6.x 安全漏洞深度解析:如何避免任意文件写入风险
  • Qwen3-ForcedAligner-0.6B应用实战:快速为卡拉OK音频生成精准歌词字幕