uniapp开发的APP下载图片到手机相册功能的实现

14593次阅读 600人点赞 作者: WuBin 发布时间: 2023-02-14 15:46:40
扫码到手机查看

下载图片地址

不要下载php动态脚本合成的图片

这里要特别注意一个问题,比如我使用php动态生成的图片,下载后,显示的后缀变成了.php!所以尽量不要下载脚本动态合成的图片!

还有以下几点需要注意:

下载普通图片

我们先定义接收的数据

data() {
		return {
			// 传递进来的base64格式
			fileBase64: '',
			// 传递进来的图片地址 不支持php动态生成的,会下载成php文件
			fileimgUrl: ''
		}
},

要使用手机相册,必须要先获得授权,所以要先检测用户给没给你相册的授权。这里我推荐使用uni封装好的插件。

App权限判断和提示 https://ext.dcloud.net.cn/plugin?id=594  我们只需要复制permission.js到我们的工程目录下即可,然后在文件中引用。
import permision from '@/common/js/permission.js';

由于安卓权限判断比较复杂,所以相册的权限封装成一个函数

async requestAndroidPermission(permisionID) {
	var result = await permision.requestAndroidPermission(permisionID)
	let status = false;
	if (result == 1) {
		// 已获得授权
		status = true;
	} else if (result == 0) {
		// 未获得授权
		status = false;
	} else {
		// 被永久拒绝权限
		status = false;
	}
	return status;
},

实现APP图片的下载

/**
	* 判断app是否有添加图片到手机相册的权限
*/
appSaveImage(imgurl) {
	const msg = '该功能需要相册权限,请前往设置页面进行授权';
	this.fileimgUrl = imgurl;
	let that = this;
	switch (uni.getSystemInfoSync().platform) {
		case 'android':
			const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
			if(status) { 
				that.downLoadImg();
			} else {
				// 没有授权
				uni.showModal({
					content: msg,
					showCancel: true,
					success() {
						permision.gotoAppPermissionSetting();
					}
				});
			}
			break;
		case 'ios':
			if(permision.judgeIosPermission("photoLibrary")) {
				that.downLoadImg();
			} else {
				// 没有授权
				uni.showModal({
					content: msg,
					showCancel: true,
					success() {
						permision.gotoAppPermissionSetting();
					}
				});
			}
			break;
	}
},
/**
	* 下载图片资源,保存图片到系统相册
	*/
downLoadImg() {
	let that = this;
	uni.showLoading({
		title: '保存中,请稍后'
	});
	uni.downloadFile({
		url: that.fileimgUrl,
		success: (res) => {
			uni.hideLoading();
			console.log(res)
			if (res.statusCode === 200) {
				uni.saveImageToPhotosAlbum({
					filePath: res.tempFilePath,
					success: function() {
						uni.showToast({
							title: "保存成功",
							icon: "none"
						});
					},
					fail: function() {
						uni.showToast({
							title: "保存失败,请稍后重试",
							icon: "none"
						});
					}
				});
			} else {
				// 当状态码为400时候
				uni.showToast({
					title: "保存失败,请稍后重试",
					icon: "none"
				});
			}
		},
		fail: (err) => {
			uni.showToast({
				title: "失败啦",
				icon: "none"
			});
		}
	})
},

注意,根据APP权限插件的介绍,this.requestAndroidPermission传入的是一个字符串!不是一个常量!

android.permission.READ_EXTERNAL_STORAGE外部存储(含相册)读取权限
res.statusCode如果失败了,是有可能得到400这个数值的。如果同样的文件 一个能下载成功 一个下载会在complete回调里提示状态码400,考虑传入url是否被转码了 尝试利用 解码之后 再传入decodeURL(url),参考

将base64转存为图片

主要就是用这个api:https://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.Bitmap.save

appSaveBase64(base64) {
	const msg = '该功能需要相册权限,请前往设置页面进行授权';
	this.fileBase64 = base64;
	console.log(this.fileBase64)
	let that = this;
	switch (uni.getSystemInfoSync().platform) {
		case 'android':
			const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
			if(status) { 
				that.downLoadBase64();
			} else {
				// 没有授权
				uni.showModal({
					content: msg,
					showCancel: true,
					success() {
						permision.gotoAppPermissionSetting();
					}
				});
			}
			break;
		case 'ios':
			if(permision.judgeIosPermission("photoLibrary")) {
				that.downLoadBase64();
			} else {
				// 没有授权
				uni.showModal({
					content: msg,
					showCancel: true,
					success() {
						permision.gotoAppPermissionSetting();
					}
				});
			}
			break;
	}
},
downLoadBase64() {
	const base64 = this.fileBase64;
	const bitmap = new plus.nativeObj.Bitmap("test");
	uni.showLoading({
		title: '保存中,请稍后'
	});
	bitmap.loadBase64Data(
		base64, 
		// 加载Base64图片数据成功
		function() {
			const url = "_doc/" + new Date().getTime() + ".png";  // url为时间戳命名方式
			// console.log('saveHeadImgFile', url) 
			bitmap.save(
				url, 
				{
					overwrite: true,  // 是否覆盖
					quality: 'quality'  // 图片清晰度
				}, 
				// successcallback 保存图片操作成功回调
				(i) => {
					uni.hideLoading();
					uni.saveImageToPhotosAlbum({
						filePath: url,
						success: function() {
							uni.showToast({
								title: '海报保存成功',
								icon: 'none'
							})
							bitmap.clear();
						}
					});
				}, 
				// errorcallback 保存图片操作失败回调
				(e) => {
					uni.showToast({
						title: '海报保存失败,请稍候重试',
						icon: 'none'
					});
					bitmap.clear();
				}
			);
		}, 
		// 加载Base64图片数据失败
		(e) => {
			uni.showToast({
				title: '海报加载失败',
				icon: 'none'
			})
			bitmap.clear()
		}
	);
},

主要逻辑思路跟下载图片链接的一样,先判断有没有权限,没有权限就跳转手机设置开启权限,当有权限的时候,就执行下载。

uni-app将图片存入系统的官方api是uni.saveImageToPhotosAlbum(OBJECT),需要给定一个文件路径filePath,但是这个路径我们是没办法拿到的。

解决思路:手机转存base64需要用到这个方法:newplus.nativeObj.Bitmap,可以参考这个base64例子,把base64转成bitmap文件对象,保存到系统相册(但是此时某些手机上无法显示出来,其实是保存成功的),然后使用uni.saveImageToPhotoAlbum方法将图片成功保存并显示。

1、Bitmap是原生图片对象,其有个方法是loadBase64Data;于是我们首先创建一个bitmap对象:

2、然后使用loadBase64Data将base64字符串转换为bitmap文件对象:

3、调用bimap.save方法,将图片文件存入手机存储(记得bitmap.clear(),比较占内存

4、uni.saveImageToPhotoAlbum方法将图片成功保存并显示

代码封装与使用

为方便日后调用,我将代码全部封装成一个mixin,在vue中引用调用方法即可。

(已经根据四中IOS的权限机制问题,更新了整体代码)

注意,其中的saveImage、isAuth只适用于小程序,不支持app。

/**
 * 
判断用户是否已授权,已授权返回成功,执行保存图片到相册;
如果用户拒绝授权,再次点击保存图片时,引导用户开启权限;
 */
import permision from '@/common/js/permission.js';

const downloadImg =  {
	data() {
		return {
			// 传递进来的base64格式
			fileBase64: '',
			// 传递进来的图片地址 不支持php动态生成的,会下载成php文件
			fileimgUrl: ''
		}
	},
	methods: {
		async requestAndroidPermission(permisionID) {
		    var result = await permision.requestAndroidPermission(permisionID)
		    let status = false;
		    if (result == 1) {
				// 已获得授权
		        status = true;
		    } else if (result == 0) {
				// 未获得授权
		        status = false;
		    } else {
				// 被永久拒绝权限
		        status = false;
		    }
		    return status;
		},
		appSaveBase64(base64) {
			const msg = '该功能需要相册权限,请前往手机设置进行授权';
			this.fileBase64 = base64;
			console.log(this.fileBase64)
			let that = this;
			switch (uni.getSystemInfoSync().platform) {
				case 'android':
					const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
					if(status) { 
						that.downLoadBase64();
					} else {
						// 没有授权
						uni.showModal({
							content: msg,
							showCancel: true,
							success() {
								permision.gotoAppPermissionSetting();
							}
						});
					}
					break;
				case 'ios':
					/**
					 * 由于ios的机制问题,你只有用过该功能它的权限列表才会出现该权限的设置,而安卓的权限直接就在列表里
					*/
				   
					if(permision.judgeIosPermission("photoLibrary")) {
						that.downLoadBase64();
					} else {
						// 没有授权
						
						// 获取是否是第一次启动相册 false的话就是第一次使用
						const iosFirstPhoto = uni.getStorageSync('iosFirstPhoto');
						if(!iosFirstPhoto) {
							   that.downLoadBase64();
							   //设为true后就代表不是第一次开启相机
							   uni.setStorageSync('iosFirstPhoto', 'true');
						} else {
							uni.showModal({
								content: msg,
								showCancel: true,
								success(res) {
									// 点击确定才执行
									if(res.confirm) {
										permision.gotoAppPermissionSetting();
									}
								}
							});
						}
					}
					break;
			}
		},
		downLoadBase64() {
			const base64 = this.fileBase64;
			const bitmap = new plus.nativeObj.Bitmap("test");
			uni.showLoading({
				title: '保存中,请稍后'
			});
			bitmap.loadBase64Data(
				base64, 
				// 加载Base64图片数据成功
				function() {
			        const url = "_doc/" + new Date().getTime() + ".png";  // url为时间戳命名方式
			        // console.log('saveHeadImgFile', url) 
			        bitmap.save(
						url, 
						{
							overwrite: true,  // 是否覆盖
							quality: 'quality'  // 图片清晰度
						}, 
						// successcallback 保存图片操作成功回调
						(i) => {
							uni.hideLoading();
							uni.saveImageToPhotosAlbum({
								filePath: url,
								success: function() {
									uni.showToast({
										title: '海报保存成功',
										icon: 'none'
									})
									bitmap.clear();
								}
							});
						}, 
						// errorcallback 保存图片操作失败回调
						(e) => {
							uni.showToast({
								title: '海报保存失败,请稍候重试',
								icon: 'none'
							});
							bitmap.clear();
						}
					);
				}, 
				// 加载Base64图片数据失败
				(e) => {
			        uni.showToast({
			            title: '海报加载失败',
			            icon: 'none'
			        })
			        bitmap.clear()
			    }
			);
		},
		/**
		 * 判断app是否有添加图片到手机相册的权限
		*/
		appSaveImage(imgurl) {
			const msg = '该功能需要相册权限,请前往设置页面进行授权';
			this.fileimgUrl = imgurl;
			let that = this;
			switch (uni.getSystemInfoSync().platform) {
				case 'android':
					const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
					if(status) { 
						that.downLoadImg();
					} else {
						// 没有授权
						uni.showModal({
							content: msg,
							showCancel: true,
							success() {
								permision.gotoAppPermissionSetting();
							}
						});
					}
					break;
				case 'ios':
					if(permision.judgeIosPermission("photoLibrary")) {
						that.downLoadImg();
					} else {
						// 没有授权
						uni.showModal({
							content: msg,
							showCancel: true,
							success() {
								permision.gotoAppPermissionSetting();
							}
						});
					}
					break;
			}
		},
        /**
         * 下载图片资源,保存图片到系统相册
         */
        downLoadImg() {
			let that = this;
			uni.showLoading({
				title: '保存中,请稍后'
			});
			uni.downloadFile({
				url: that.fileimgUrl,
				success: (res) => {
					uni.hideLoading();
					console.log(res)
					if (res.statusCode === 200) {
						uni.saveImageToPhotosAlbum({
							filePath: res.tempFilePath,
							success: function() {
								uni.showToast({
									title: "保存成功",
									icon: "none"
								});
							},
							fail: function() {
								uni.showToast({
									title: "保存失败,请稍后重试",
									icon: "none"
								});
							}
						});
					} else {
						// 当状态码为400时候
						uni.showToast({
							title: "保存失败,请稍后重试",
							icon: "none"
						});
					}
				},
				fail: (err) => {
					uni.showToast({
						title: "失败啦",
						icon: "none"
					});
				}
			})
		},
        /*
         * 引导用户开启权限
         */
		isAuth() {
			uni.showModal({
				content: '由于您还没有允许保存图片到您相册里,无法进行保存,请点击确定允许授权',
				success: (res) => {
					if (res.confirm) {
						uni.openSetting({
							success: (result) => {
								console.log(result.authSetting);
							}
						});
					}
				}
			});
		},
		/**
		 * 保存图片  只适用于小程序
		 */
		saveImage(imgUrl) {
			let that = this;
			// 向用户发起授权请求
			uni.authorize({
				scope: 'scope.writePhotosAlbum',
				success: () => {
		            // 已授权
					that.downLoadImg();
				},
				fail: () => {
		            // 拒绝授权,获取当前设置
					uni.getSetting({
						success: (result) => {
							if (!result.authSetting['scope.writePhotosAlbum']) {
								that.isAuth()
							}
						}
					});
				}
			})
		}
    }
}

export default downloadImg;

使用

import downloadImg from '@/common/js/mixin_downloadImage.js'; 

mixins: [ downloadImg ],

// #ifdef APP-PLUS 
this.appSaveBase64(Base64);
// #endif

IOS机制原因,设置中无相册

由于ios的机制问题,你只有用过该功能它的权限列表才会出现该权限的设置,而安卓的权限直接就在列表里,比如下图

首先必须在manifest.json中添加模块,在uni里,照片和相册都归属于camera模块,但是他是HX3.6.11+新增,我的电脑是3.6.4所以,我们选择手动添加。

功能模块 https://uniapp.dcloud.net.cn/tutorial/app-modules.html

打包后缺少某些模块提示的解决方法:https://ask.dcloud.net.cn/article/283

模块名称模块标识功能说明支持平台
Barcode(扫码)Barcode调用相机扫码功能HBuilderX3.6.11+新增Android、iOS
Bluetooth(低功耗蓝牙)Bluetooth使用设备蓝牙功能Android、iOS
Camera&Gallery(相机和相册)Camera调用相机拍照,访问或修改相册HBuilderX3.6.11+新增Android、iOS
/* 5+App特有相关 */
    "app-plus" : {
        "compatible" : {...},
        ....
        /* 模块配置 */
        "modules" : {
            "Webview-x5" : {},
            "OAuth" : {},
            "Share" : {},
            "Camera" : {}
        },

由于IOS的机制,必须要求先调用,否则设置中不能有相册选项。因为当先判断有无相册权限的时候,会因为没有权限而直接进入提示,从而打开app的设置,但是此时设置中由于没有调用相册的功能,不会有开启相册这个选项。所以,我考虑使用缓存区缓存一个值,当第一次开启保存到相册的操作的时候,直接去调用保存,这样无论用户同意不同意,我们都会在设置中添加一个“相册”的选项!(上图右1),之后,再次触发时候走正常的判断流程即可。

case 'ios':
		/**
		 * 由于ios的机制问题,你只有用过该功能它的权限列表才会出现该权限的设置,而安卓的权限直接就在列表里
		*/
		
		if(permision.judgeIosPermission("photoLibrary")) {
			that.downLoadBase64();
		} else {
			// 没有授权
			
			// 获取是否是第一次启动相册 false的话就是第一次使用
			const iosFirstPhoto = uni.getStorageSync('iosFirstPhoto');
			if(!iosFirstPhoto) {
					that.downLoadBase64();
					//设为true后就代表不是第一次开启相机
					uni.setStorageSync('iosFirstPhoto', 'true');
			} else {
				uni.showModal({
					content: msg,
					showCancel: true,
					success(res) {
						// 点击确定才执行
						if(res.confirm) {
							permision.gotoAppPermissionSetting();
						}
					}
				});
			}
		}
		break;
}

详细的整体代码见《三》

相关资料

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:uniapp保存,图片,相册,saveImageToPhotosAlbum
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    8757次阅读 562人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    3674次阅读 274人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    13775次阅读 888人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    12532次阅读 422人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    11038次阅读 881人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    15139次阅读 1207人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    14612次阅读 1191人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    28356次阅读 2230人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    24476次阅读 1885人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录