鸿蒙OSUniApp 实现图片上传与压缩功能#三方框架 #Uniapp

UniApp 实现图片上传与压缩功能

前言

在移动应用开发中,图片上传是一个非常常见的需求。无论是用户头像、朋友圈图片还是商品图片,都需要上传到服务器。但移动设备拍摄的图片往往尺寸较大,直接上传会导致流量消耗过大、上传时间过长,影响用户体验。因此,图片压缩成为了移动应用开发中的必备技能。

通过 UniApp 实现图片上传与压缩功能,既能满足用户体验需求,又能减轻服务器负担。今天就来分享一下我在实际项目中使用的图片上传与压缩方案,希望能对大家有所帮助。

技术方案分析

在 UniApp 中实现图片上传与压缩,主要涉及以下几个方面:

  1. 图片选择:通过 uni.chooseImage() 实现
  2. 图片压缩:通过 canvas 实现
  3. 图片上传:通过 uni.uploadFile() 实现

这个方案的优点是:

  • 压缩在客户端进行,减轻了服务器压力
  • 减少了网络流量,提高了上传速度
  • 可以根据不同场景设置不同的压缩参数

具体实现

1. 图片选择

首先实现图片选择功能:

// 选择图片
chooseImage() {return new Promise((resolve, reject) => {uni.chooseImage({count: 9, // 最多可选择的图片张数sizeType: ['original', 'compressed'], // 可选择原图或压缩后的图片sourceType: ['album', 'camera'], // 从相册选择或使用相机拍摄success: (res) => {resolve(res.tempFilePaths);},fail: (err) => {reject(err);}});});
}

2. 图片压缩实现

压缩图片是整个功能的核心,我们使用 canvas 来实现:

/*** 图片压缩* @param {String} src 图片路径* @param {Number} quality 压缩质量(0-1)* @param {Number} maxWidth 最大宽度* @param {Number} maxHeight 最大高度*/
compressImage(src, quality = 0.8, maxWidth = 800, maxHeight = 800) {return new Promise((resolve, reject) => {// 获取图片信息uni.getImageInfo({src: src,success: (imgInfo) => {// 计算压缩后的尺寸let width = imgInfo.width;let height = imgInfo.height;// 等比例缩放if (width > maxWidth || height > maxHeight) {const ratio = Math.max(width / maxWidth, height / maxHeight);width = Math.floor(width / ratio);height = Math.floor(height / ratio);}// 创建canvas上下文const ctx = uni.createCanvasContext('compressCanvas', this);// 绘制图片到canvasctx.drawImage(src, 0, 0, width, height);// 将canvas转为图片ctx.draw(false, () => {setTimeout(() => {uni.canvasToTempFilePath({canvasId: 'compressCanvas',fileType: 'jpg',quality: quality,success: (res) => {// 获取压缩后的图片路径resolve(res.tempFilePath);},fail: (err) => {reject(err);}}, this);}, 300); // 延迟确保canvas绘制完成});},fail: (err) => {reject(err);}});});
}

在页面中需要添加对应的 canvas 元素:

<canvas canvas-id="compressCanvas" style="width: 0px; height: 0px; position: absolute; left: -1000px; top: -1000px;"></canvas>

3. 图片上传实现

图片上传时,我们往往需要添加额外的参数,比如表单字段、用户 token 等:

/*** 上传图片到服务器* @param {String} filePath 图片路径* @param {String} url 上传地址* @param {Object} formData 附加数据*/
uploadFile(filePath, url, formData = {}) {return new Promise((resolve, reject) => {uni.uploadFile({url: url,filePath: filePath,name: 'file', // 服务器接收的字段名formData: formData,header: {// 可以添加自定义 header,如 token'Authorization': 'Bearer ' + uni.getStorageSync('token')},success: (res) => {// 这里需要注意,返回的数据是字符串,需要手动转为 JSONlet data = JSON.parse(res.data);resolve(data);},fail: (err) => {reject(err);}});});
}

4. 完整的上传流程

将以上三个步骤组合,实现完整的图片上传流程:

// 实现完整的上传流程
async handleUpload() {try {// 显示加载提示uni.showLoading({title: '上传中...',mask: true});// 选择图片const imagePaths = await this.chooseImage();// 用于存储上传结果const uploadResults = [];// 循环处理每张图片for (let i = 0; i < imagePaths.length; i++) {// 压缩图片const compressedPath = await this.compressImage(imagePaths[i],0.7,  // 压缩质量800,  // 最大宽度800   // 最大高度);// 获取原图和压缩后的图片大小进行对比const originalInfo = await this.getFileInfo(imagePaths[i]);const compressedInfo = await this.getFileInfo(compressedPath);console.log(`原图大小: ${(originalInfo.size / 1024).toFixed(2)}KB, 压缩后: ${(compressedInfo.size / 1024).toFixed(2)}KB`);// 上传压缩后的图片const uploadResult = await this.uploadFile(compressedPath,'https://your-api.com/upload',{type: 'avatar', // 附加参数userId: this.userId});uploadResults.push(uploadResult);}// 隐藏加载提示uni.hideLoading();// 提示上传成功uni.showToast({title: '上传成功',icon: 'success'});// 返回上传结果return uploadResults;} catch (error) {uni.hideLoading();uni.showToast({title: '上传失败',icon: 'none'});console.error('上传错误:', error);}
}// 获取文件信息
getFileInfo(filePath) {return new Promise((resolve, reject) => {uni.getFileInfo({filePath: filePath,success: (res) => {resolve(res);},fail: (err) => {reject(err);}});});
}

进阶优化

以上代码已经可以基本满足图片上传与压缩需求,但在实际项目中,我们还可以进一步优化:

1. 添加图片预览功能

在上传前,通常需要让用户预览选择的图片:

// 预览图片
previewImage(current, urls) {uni.previewImage({current: current, // 当前显示图片的路径urls: urls, // 需要预览的图片路径列表indicator: 'number',loop: true});
}

2. 使用 uniCloud 上传

如果你使用 uniCloud 作为后端服务,可以利用其提供的云存储功能简化上传流程:

// 使用 uniCloud 上传
async uploadToUniCloud(filePath) {try {const result = await uniCloud.uploadFile({filePath: filePath,cloudPath: 'images/' + Date.now() + '.jpg'});return result.fileID; // 返回文件ID} catch (error) {throw error;}
}

3. 添加上传进度显示

对于大图片,添加上传进度能提升用户体验:

uploadFileWithProgress(filePath, url, formData = {}) {return new Promise((resolve, reject) => {const uploadTask = uni.uploadFile({url: url,filePath: filePath,name: 'file',formData: formData,success: (res) => {let data = JSON.parse(res.data);resolve(data);},fail: (err) => {reject(err);}});uploadTask.onProgressUpdate((res) => {console.log('上传进度', res.progress);// 更新进度条this.uploadProgress = res.progress;});});
}

4. 针对鸿蒙系统的适配

随着国产操作系统鸿蒙的普及,我们也需要考虑在鸿蒙系统上的兼容性。虽然目前 UniApp 官方还没有专门针对鸿蒙系统的适配文档,但我们可以通过一些方法来优化:

// 检测当前系统
checkSystem() {const systemInfo = uni.getSystemInfoSync();console.log('当前系统:', systemInfo.platform);// 鸿蒙系统目前会被识别为 android,可以通过 brand 和 model 辅助判断const isHarmonyOS = systemInfo.brand === 'HUAWEI' && /HarmonyOS/i.test(systemInfo.system);if (isHarmonyOS) {console.log('当前是鸿蒙系统');// 针对鸿蒙系统进行特殊处理// 例如:调整压缩参数、使用不同的 API 等}return systemInfo;
}

根据我的测试,在鸿蒙系统上,有时 canvas 绘制需要更长的延迟时间,可以适当调整:

// 针对鸿蒙系统的 canvas 延迟调整
const delay = isHarmonyOS ? 500 : 300;
setTimeout(() => {uni.canvasToTempFilePath({// 配置项...});
}, delay);

实际案例

下面是一个完整的实际案例,用于实现商品发布页面的图片上传功能:

<template><view class="container"><view class="image-list"><!-- 已选图片预览 --><view class="image-item" v-for="(item, index) in imageList" :key="index"><image :src="item.path" mode="aspectFill" @tap="previewImage(index)"></image><view class="delete-btn" @tap.stop="deleteImage(index)">×</view></view><!-- 添加图片按钮 --><view class="add-image" @tap="handleAddImage" v-if="imageList.length < 9"><text class="add-icon">+</text><text class="add-text">添加图片</text></view></view><!-- 上传按钮 --><button class="upload-btn" @tap="submitUpload" :disabled="imageList.length === 0">上传图片 ({{imageList.length}}/9)</button><!-- 压缩画布(隐藏) --><canvas canvas-id="compressCanvas" style="width: 0px; height: 0px; position: absolute; left: -1000px; top: -1000px;"></canvas><!-- 上传进度条 --><view class="progress-bar" v-if="isUploading"><view class="progress-inner" :style="{width: uploadProgress + '%'}"></view><text class="progress-text">{{uploadProgress}}%</text></view></view>
</template><script>
export default {data() {return {imageList: [], // 已选图片列表isUploading: false, // 是否正在上传uploadProgress: 0, // 上传进度isHarmonyOS: false // 是否鸿蒙系统};},onLoad() {// 检测系统const systemInfo = this.checkSystem();this.isHarmonyOS = systemInfo.brand === 'HUAWEI' && /HarmonyOS/i.test(systemInfo.system);},methods: {// 添加图片async handleAddImage() {try {const imagePaths = await this.chooseImage();// 添加到图片列表for (let path of imagePaths) {this.imageList.push({path: path,compressed: false,compressedPath: '',uploaded: false,fileID: ''});}} catch (error) {console.error('选择图片失败:', error);}},// 预览图片previewImage(index) {const urls = this.imageList.map(item => item.path);uni.previewImage({current: this.imageList[index].path,urls: urls});},// 删除图片deleteImage(index) {this.imageList.splice(index, 1);},// 提交上传async submitUpload() {if (this.imageList.length === 0) {uni.showToast({title: '请至少选择一张图片',icon: 'none'});return;}this.isUploading = true;this.uploadProgress = 0;uni.showLoading({title: '准备上传...',mask: true});try {// 上传结果const uploadResults = [];// 总进度let totalProgress = 0;// 遍历所有图片进行压缩和上传for (let i = 0; i < this.imageList.length; i++) {let item = this.imageList[i];// 如果还没压缩过,先压缩if (!item.compressed) {uni.showLoading({title: `压缩第 ${i+1}/${this.imageList.length} 张图片`,mask: true});try {const compressedPath = await this.compressImage(item.path,0.7,800,800);// 更新图片信息this.imageList[i].compressed = true;this.imageList[i].compressedPath = compressedPath;// 获取压缩前后的大小对比const originalInfo = await this.getFileInfo(item.path);const compressedInfo = await this.getFileInfo(compressedPath);console.log(`图片 ${i+1}: 原图 ${(originalInfo.size / 1024).toFixed(2)}KB, 压缩后 ${(compressedInfo.size / 1024).toFixed(2)}KB, 压缩率 ${((1 - compressedInfo.size / originalInfo.size) * 100).toFixed(2)}%`);} catch (error) {console.error(`压缩第 ${i+1} 张图片失败:`, error);// 如果压缩失败,使用原图this.imageList[i].compressedPath = item.path;this.imageList[i].compressed = true;}}// 准备上传uni.showLoading({title: `上传第 ${i+1}/${this.imageList.length} 张图片`,mask: true});try {// 使用压缩后的图片路径,如果没有则使用原图const fileToUpload = item.compressedPath || item.path;// 上传图片const result = await this.uploadFileWithProgress(fileToUpload,'https://your-api.com/upload',{type: 'product',index: i});// 更新图片信息this.imageList[i].uploaded = true;this.imageList[i].fileID = result.fileID || result.url;uploadResults.push(result);// 更新总进度totalProgress = Math.floor((i + 1) / this.imageList.length * 100);this.uploadProgress = totalProgress;} catch (error) {console.error(`上传第 ${i+1} 张图片失败:`, error);uni.showToast({title: `第 ${i+1} 张图片上传失败`,icon: 'none'});}}uni.hideLoading();this.isUploading = false;uni.showToast({title: '所有图片上传完成',icon: 'success'});// 返回上传结果,可以传给父组件或进行后续处理this.$emit('uploadComplete', uploadResults);} catch (error) {uni.hideLoading();this.isUploading = false;console.error('上传过程出错:', error);uni.showToast({title: '上传失败,请重试',icon: 'none'});}},// 其他方法实现(chooseImage, compressImage, uploadFile等,同前面的实现)}
};
</script><style lang="scss">
.container {padding: 20rpx;
}.image-list {display: flex;flex-wrap: wrap;
}.image-item {width: 220rpx;height: 220rpx;margin: 10rpx;position: relative;border-radius: 8rpx;overflow: hidden;image {width: 100%;height: 100%;}.delete-btn {position: absolute;top: 0;right: 0;width: 44rpx;height: 44rpx;background-color: rgba(0, 0, 0, 0.5);color: #ffffff;text-align: center;line-height: 44rpx;font-size: 32rpx;z-index: 10;}
}.add-image {width: 220rpx;height: 220rpx;margin: 10rpx;background-color: #f5f5f5;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 8rpx;border: 1px dashed #dddddd;.add-icon {font-size: 60rpx;color: #999999;}.add-text {font-size: 24rpx;color: #999999;margin-top: 10rpx;}
}.upload-btn {margin-top: 40rpx;background-color: #007aff;color: #ffffff;border-radius: 8rpx;&:disabled {background-color: #cccccc;}
}.progress-bar {margin-top: 30rpx;height: 40rpx;background-color: #f5f5f5;border-radius: 20rpx;overflow: hidden;position: relative;.progress-inner {height: 100%;background-color: #007aff;transition: width 0.3s;}.progress-text {position: absolute;top: 0;left: 0;right: 0;bottom: 0;display: flex;justify-content: center;align-items: center;font-size: 24rpx;color: #ffffff;}
}
</style>

鸿蒙系统适配经验

前面已经简单提到了鸿蒙系统的适配,下面来详细说一下我在实际项目中遇到的问题和解决方案:

  1. 画布延迟问题:在鸿蒙系统上,canvas 绘制后转图片需要更长的延迟时间,建议延长 setTimeout 时间。

  2. 文件系统差异:有些文件路径的处理方式可能与 Android 有所不同,建议使用 UniApp 提供的 API 进行文件操作,而不要直接操作路径。

  3. 图片格式支持:在鸿蒙系统上,对 WebP 等格式的支持可能有限,建议统一使用 JPG 或 PNG 格式。

  4. 内存管理:鸿蒙系统对内存的管理略有不同,处理大图片时需要注意内存释放。可以在完成上传后,主动清空临时图片:

// 清空临时文件
clearTempFiles() {for (let item of this.imageList) {// 如果存在压缩后的临时文件,尝试删除if (item.compressedPath && item.compressedPath !== item.path) {uni.removeSavedFile({filePath: item.compressedPath,complete: () => {console.log('清理临时文件');}});}}
}

总结

通过本文介绍的方法,我们可以在 UniApp 中实现图片上传与压缩功能,主要包括以下几个步骤:

  1. 使用 uni.chooseImage() 选择图片
  2. 使用 canvas 进行图片压缩
  3. 使用 uni.uploadFile() 上传图片
  4. 添加进度显示和预览功能
  5. 针对鸿蒙系统做特殊适配

在实际项目中,可以根据需求调整压缩参数,比如对头像类图片可以压缩得更小,而对需要展示细节的商品图片,可以保留更高的质量。

希望本文能够帮助大家更好地实现 UniApp 中的图片上传与压缩功能。如果有任何问题或建议,欢迎在评论区交流讨论!

参考资料

  1. UniApp 官方文档:https://uniapp.dcloud.io/
  2. Canvas API 参考:https://uniapp.dcloud.io/api/canvas/CanvasContext

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/906118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

已经装了pygame但pycharm显示没有该模块/软件包无法加载出来下载pygame

首先&#xff0c;如果你已经通过pip install pygame或者其他什么命令下载好了pygame &#xff08;可以通过pip list查看&#xff0c;有pygame说明pygame已经成功安装在当前python环境中&#xff09;。然而&#xff0c;如果你在 PyCharm 中仍然看不到 pygame&#xff0c;可能是因…

第6章 实战案例:基于 STEVAL-IDB011V1 板级 CI/CD 全流程

在前五章中,我们完成了嵌入式 CI/CD 从环境搭建、编译自动化、测试自动化、发布分发到监控回归的全技术链条。本章将以 STEVAL-IDB011V1(搭载 BlueNRG-355)评估板为实战载体,手把手演示如何在 GitLab CI(或 Jenkins)上,构建一条从 Git Push → 编译 → 测试 → 刷写 → …

系统架构设计(十四):解释器风格

概念 解释器风格是一种将程序的每个语句逐条读取并解释执行的体系结构风格。程序在运行时不会先被编译为机器码&#xff0c;而是动态地由解释器分析并执行其语义。 典型应用&#xff1a;Python 解释器、JavaScript 引擎、Bash Shell、SQL 引擎。 组成结构 解释器风格系统的…

1-机器学习的基本概念

文章目录 一、机器学习的步骤Step1 - Function with unknownStep2 - Define Loss from Training DataStep3 - Optimization 二、机器学习的改进Q1 - 线性模型有一些缺点Q2 - 重新诠释机器学习的三步Q3 - 机器学习的扩展Q4 - 过拟合问题&#xff08;Overfitting&#xff09; 一、…

SQL里where条件的顺序影响索引使用吗?

大家好&#xff0c;我是锋哥。今天分享关于【SQL里where条件的顺序影响索引使用吗&#xff1f;】面试题。希望对大家有帮助&#xff1b; SQL里where条件的顺序影响索引使用吗&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 SQL 查询中&#xff0c;W…

计算机科技笔记: 容错计算机设计05 n模冗余系统 TMR 三模冗余系统

NMR&#xff08;N-Modular Redundancy&#xff0c;N 模冗余&#xff09;是一种通用的容错设计架构&#xff0c;通过引入 N 个冗余模块&#xff08;N ≥ 3 且为奇数&#xff09;&#xff0c;并采用多数投票机制&#xff0c;来提升系统的容错能力与可靠性。单个模块如果可靠性小于…

中级网络工程师知识点7

1.存储区城网络SAN可分为IP-SAN,FC-SAN两种&#xff0c;从部署成本和传输效率两个方面比较两种SAN&#xff0c;比较结果为FCSAN部署成本更高 2.RAID2.0技术的优势&#xff1a; &#xff08;1&#xff09;自动负载均衡&#xff0c;降低了存储系统故障率 &#xff08;2&#x…

在Ubuntu24.04中配置开源直线特征提取软件DeepLSD

在Ubuntu24.04中配置开源直线特征提取软件DeepLSD 本文提供在Ubuntu24.04中配置开源直线特征提取软件DeepLSD的基础环境配置、列出需要修改的文件内容&#xff0c;以及报错解决方案集锦。 基础的编译安装环境 python3.8.12CUDA12gcc/g 9.5&#xff08;系统自带的g-13版本太新…

Nginx+Lua 实战避坑:从模块加载失败到版本冲突的深度剖析

Nginx 集成 Lua (通常通过 ngx_http_lua_module 或 OpenResty) 为我们提供了在 Web 服务器层面实现动态逻辑的强大能力。然而,在享受其高性能和灵活性的同时,配置和使用过程中也常常会遇到各种令人头疼的问题。本文将结合实际案例,深入分析在 Nginx+Lua 环境中常见的技术问题…

Vue.js组件开发进阶

Vue.js 是一个渐进式 JavaScript 框架&#xff0c;广泛用于构建用户界面。组件是 Vue.js 的核心概念之一&#xff0c;允许开发者将 UI 拆分为独立、可复用的模块。本文将深入探讨 Vue.js 组件的开发&#xff0c;涵盖从基础到高级的各个方面。 组件的基本概念 在 Vue.js 中&am…

蓝桥杯19682 完全背包

问题描述 有 N 件物品和一个体积为 M 的背包。第 i 个物品的体积为 vi​&#xff0c;价值为 wi​。每件物品可以使用无限次。 请问可以通过什么样的方式选择物品&#xff0c;使得物品总体积不超过 M 的情况下总价值最大&#xff0c;输出这个最大价值即可。 输入格式 第一行…

深度学习之用CelebA_Spoof数据集搭建一个活体检测-一些模型训练中的改动带来的改善

实验背景 在前面的深度学习之用CelebA_Spoof数据集搭建一个活体检测-模型搭建和训练&#xff0c;我们基于CelebA_Spoof数据集构建了一个用SqueezeNe框架进行训练的活体2D模型&#xff0c;采用了蒸馏法进行了一些简单的工作。在前面提供的训练参数中&#xff0c;主要用了以下几…

2025年PMP 学习二十 第13章 项目相关方管理

第13章 项目相关方管理 序号过程过程组过程组1识别相关方启动2规划相关方管理规划3管理相关方参与与执行4监控相关方参与与监控 相关方管理&#xff0c;针对于团队之外的相关方的&#xff0c;核心目标是让对方为了支持项目&#xff0c;以达到项目目标。 文章目录 第13章 项目相…

GO语言语法---For循环、break、continue

文章目录 1. 基本for循环&#xff08;类似其他语言的while&#xff09;2. 经典for循环&#xff08;初始化;条件;后续操作&#xff09;3. 无限循环4. 使用break和continue5 . 带标签的循环&#xff08;可用于break/continue指定循环&#xff09;1、break带标签2、continue带标签…

CSS- 4.4 固定定位(fixed) 咖啡售卖官网实例

本系列可作为前端学习系列的笔记&#xff0c;代码的运行环境是在HBuilder中&#xff0c;小编会将代码复制下来&#xff0c;大家复制下来就可以练习了&#xff0c;方便大家学习。 HTML系列文章 已经收录在前端专栏&#xff0c;有需要的宝宝们可以点击前端专栏查看&#xff01; 点…

分布式微服务系统架构第132集:Python大模型,fastapi项目-Jeskson文档-微服务分布式系统架构

加群联系作者vx&#xff1a;xiaoda0423 仓库地址&#xff1a;https://webvueblog.github.io/JavaPlusDoc/ https://1024bat.cn/ https://github.com/webVueBlog/fastapi_plus 这个错误是由于 Python 3 中已经将线程的 isAlive() 方法更名为 is_alive()&#xff0c;但你的调试工…

react路由中Suspense的介绍

好的&#xff0c;我们来详细解释一下这个 AppRouter 组件的代码。 这个组件是一个在现代 React 应用中非常常见的模式&#xff0c;特别是在使用 React Router v6 进行路由管理和结合代码分割&#xff08;Code Splitting&#xff09;来优化性能时。 JavaScript const AppRout…

C语言内存函数与数据在内存中的存储

一、c语言内存函数 1、memcpy函数是一个标准库函数&#xff0c;用于内存复制。功能上是用来将一块内存中的内容复制到另一块内存中。用户需要提供目标地址、源地址以及要复制的字节数。例如结构体之间的复制。 memcpy函数的原型是&#xff1a;void* memcpy&#xff08;void* …

层次原理图

层次原理图简介 层次原理图&#xff08;Hierarchical Schematic&#xff09;是一种常用于电子工程与系统设计的可视化工具&#xff0c;通过分层结构将复杂系统分解为多个可管理的子模块。它如同“设计蓝图”&#xff0c;以树状结构呈现整体与局部的关系&#xff1a;顶层展现系…

流程编辑器Bpmn与LogicFlow学习

工作流技术如何与用户交互结合&#xff08;如动态表单、任务分配&#xff09;处理过 XML 与 JSON 的转换自定义过 bpmn.js 的样式&#xff08;如修改节点颜色、形状、图标&#xff09;扩展过上下文菜单&#xff08;Palette&#xff09;或属性面板&#xff08;Properties Panel&…