uniapp APP端 ios保存图片报错办理方案
1、报错信息{
"errMsg": "saveImageToPhotosAlbum:fail 未能完成操作。(PHPhotosErrorDomain错误3302。),",
"errCode": -100,
"code": -100
} 2、分析
在安卓端测试uni.saveImageToPhotosAlbum保存图片正常,在ios端报错,缘故起因大概是:因为后端对于预览图片的接口直接是通过传入文件id,然后后端查询到路径,然后回写图片数据,没有利用nginx,导致图片的url没有后缀,uniapp通过uni.downloadFile下载到暂时路径没有后缀名,安卓正常,ios报错。
3、办理方案
办理方案大抵分为两步:
(1)、通过downloadFile的暂时路径然后再进行一次另存,增加后缀名
(2)、然后调用saveImageToPhotosAlbum进行保存
关键方法如下:
/**
* 重命名文件,加上指定后缀
* @param {string} oldPath - 原文件路径
* @param {string} fileSuffix - 文件后缀,例如 '.jpg'
* @returns {Promise<string>} - 返回重命名后的文件路径字符串
*/
renameImage(oldPath, fileSuffix) {
return new Promise((resolve, reject) => {
plus.io.resolveLocalFileSystemURL(
oldPath,
(entry) => {
entry.getParent(
(parentEntry) => {
const newName = entry.name + fileSuffix;
entry.moveTo(
parentEntry,
newName,
(newFileEntry) => {
resolve(newFileEntry.fullPath);
},
(err) => {
console.error("文件移动失败", err);
reject(`文件移动失败: ${err.message}`);
}
);
},
(err) => {
console.error("获取父目录失败", err);
reject(`获取父目录失败: ${err.message}`);
}
);
},
(err) => {
console.error("解析文件路径失败", err);
reject(`解析文件路径失败: ${err.message}`);
}
);
});
}, // 异步保存图片方法
async saveImage() {
try {
// 直接下载图片
const downloadResult = await new Promise((resolve, reject) => {
uni.downloadFile({
url: this.imageUrl,
success: resolve,
fail: reject,
});
});
if (downloadResult.statusCode === 200) {
// 下载成功后保存图片到相册
const newPath = await this.renameImage(
downloadResult.tempFilePath,
this.fileSuffix
);
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
filePath: newPath,
success: resolve,
fail: reject,
});
});
uni.showToast({
title: "保存成功",
icon: "success",
});
this.closeOptionsPopup();
} else {
uni.showToast({
title: "保存失败",
icon: "none",
});
throw new Error("下载失败");
}
} catch (err) {
console.error("操作失败", err);
uni.showToast({
title: "操作失败",
icon: "none",
});
}
}, 4、整体页面代码
<template><view class="notify"> <uni-card v-for="(item, index) in notifyList" :key="index" :title=" findDataInfo( dictData.notification_categories, item.attachTag, 'value', 'text' ) " > <text class="uni-body"> {{ item.attachName }} </text> <!-- <uni-list> <uni-list-item title="查看附件" showArrow></uni-list-item> </uni-list> --> <view slot="actions" class="card-actions no-border"> <view class="card-actions-item" @click="viewAttachments(item.attachId, item.attachExt)" > <uni-icons type="bars" size="18" color="#999"></uni-icons> <text class="card-actions-item-text">查看附件</text> </view> </view> </uni-card> <!-- 图片预览的弹出框 --> <uni-popup ref="popup" type="center" :is-mask-click="false"> <view class="popup-container"> <view class="image-wrapper"> <image :src="imageUrl" mode="aspectFit" class="preview-image" @longpress="openOptionsPopup" ></image> </view> <uni-icons type="closeempty" size="25" class="close-button" @click="closeImagePopup" ></uni-icons> </view> </uni-popup> <!-- 保存图片选项的弹出框 --> <uni-popup ref="optionsPopup" type="bottom" :is-mask-click="true"> <view class="options-container"> <view class="option-item" @click="saveImage">保存图片</view> <!-- <view class="separator"></view> --> <view class="gap"></view> <view class="cancel-item" @click="closeOptionsPopup">取消</view> </view> </uni-popup></view></template><script>import { queryNotifyList } from "@/api/work/notify";import { getDictsByCodeValue } from "@/api/index";import { toast } from "@/utils/common";import config from "@/config";export default {components: {},data() { return { //字典值 dicts: [ { codeName: "notification_categories", codeId: "NotificationCategories", }, ], dictMap: new Map(), //字典数据 dictData: {}, notifyList: [], imageUrl: "", fileSuffix: "", };},computed: {},methods: { //查询关照列表 getNotifyList() { const param = { pageNumber: 1, pageSize: 10000, params: { attachType: "01", }, }; queryNotifyList(param).then((res) => { this.notifyList = res.rows; }); }, //查看附件 viewAttachments(attachId, attachExt) { if (!attachId) { toast("暂无附件"); return; } const baseUrl = config.baseUrl; if (attachExt === ".pdf") { this.$tab.navigateTo( "/pages/common/pdfview/index?url=" + baseUrl + "/rest/platform/attachment/showPdf?picId=" + attachId ); } else { this.imageUrl = baseUrl + "/rest/platform/attachment/showPic?picId=" + attachId; this.fileSuffix = attachExt; this.openImagePopup(); } }, // 异步保存图片方法
async saveImage() {
try {
// 直接下载图片
const downloadResult = await new Promise((resolve, reject) => {
uni.downloadFile({
url: this.imageUrl,
success: resolve,
fail: reject,
});
});
if (downloadResult.statusCode === 200) {
// 下载成功后保存图片到相册
const newPath = await this.renameImage(
downloadResult.tempFilePath,
this.fileSuffix
);
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
filePath: newPath,
success: resolve,
fail: reject,
});
});
uni.showToast({
title: "保存成功",
icon: "success",
});
this.closeOptionsPopup();
} else {
uni.showToast({
title: "保存失败",
icon: "none",
});
throw new Error("下载失败");
}
} catch (err) {
console.error("操作失败", err);
uni.showToast({
title: "操作失败",
icon: "none",
});
}
}, /**
* 重命名文件,加上指定后缀
* @param {string} oldPath - 原文件路径
* @param {string} fileSuffix - 文件后缀,例如 '.jpg'
* @returns {Promise<string>} - 返回重命名后的文件路径字符串
*/
renameImage(oldPath, fileSuffix) {
return new Promise((resolve, reject) => {
plus.io.resolveLocalFileSystemURL(
oldPath,
(entry) => {
entry.getParent(
(parentEntry) => {
const newName = entry.name + fileSuffix;
entry.moveTo(
parentEntry,
newName,
(newFileEntry) => {
resolve(newFileEntry.fullPath);
},
(err) => {
console.error("文件移动失败", err);
reject(`文件移动失败: ${err.message}`);
}
);
},
(err) => {
console.error("获取父目录失败", err);
reject(`获取父目录失败: ${err.message}`);
}
);
},
(err) => {
console.error("解析文件路径失败", err);
reject(`解析文件路径失败: ${err.message}`);
}
);
});
}, // 获取字典数据 async getDictsData() { for (const dict of this.dicts) { const response = await getDictsByCodeValue(dict.codeId); this.dictMap.set(dict.codeName, response); } // 将字典名和对应数据的数组转换为对象 this.dictData = Object.fromEntries(Array.from(this.dictMap.entries())); }, /** * @dataList数据聚集 * @original源数据 * @matchKey要匹配的key * @originalKey 最终需要获取的key */ findDataInfo(dataList, original, matchKey, originalKey) { if (!dataList) { return original; } const dataItem = dataList.find((item) => item == original); return dataItem ? dataItem : original; // 如果找不到,则返回原始ID }, openImagePopup() { this.$refs.popup.open(); }, closeImagePopup() { this.$refs.popup.close(); }, openOptionsPopup() { this.$refs.optionsPopup.open(); }, closeOptionsPopup() { this.$refs.optionsPopup.close(); },},watch: {},async created() { await this.getDictsData(); await this.getNotifyList();},// 页面周期函数--监听页面加载onLoad() {},// 页面周期函数--监听页面初次渲染完成onReady() {},// 页面周期函数--监听页面表现(not-nvue)onShow() {},// 页面周期函数--监听页面隐藏onHide() {},// 页面周期函数--监听页面卸载onUnload() {},// 页面处理函数--监听用户下拉动作// onPullDownRefresh() { uni.stopPullDownRefresh(); },// 页面处理函数--监听用户上拉触底// onReachBottom() {},// 页面处理函数--监听页面滚动(not-nvue)// onPageScroll(event) {},// 页面处理函数--用户点击右上角分享// onShareAppMessage(options) {},};</script><style lang="scss">.notify {padding-bottom: 10px;}.card-actions {display: flex;flex-direction: row;justify-content: space-around;align-items: center;height: 45px;border-top: 1px #eee solid;}.card-actions-item {display: flex;flex-direction: row;align-items: center;}.card-actions-item-text {font-size: 12px;color: #666;margin-left: 5px;}.no-border {border-width: 0;}/* 弹出框样式 */.popup-container {position: relative;width: 100%;height: 100%;background-color: white;border-radius: 10px;overflow: hidden;display: flex;justify-content: center; /* 水平居中 */align-items: center; /* 垂直居中 */}/* 图片包装样式 */.image-wrapper {width: 100%;height: 100%;display: flex;justify-content: center; /* 水平居中 */align-items: center; /* 垂直居中 */}/* 预览图片样式 */.preview-image {object-fit: contain;}/* 关闭按钮样式 */.close-button {position: absolute;top: 10px;right: 10px;background-color: transparent;border: none;font-size: 24px;color: black;}/* 选项弹出框样式 */.options-container {padding: 0;background-color: #f5f5f5;border-top-left-radius: 12px;border-top-right-radius: 12px;box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);overflow: hidden; /* 确保圆角效果 */}/* 利用项样式 */.option-item {padding: 15px;font-size: 15px;text-align: center;background-color: white;}/* 分隔线样式 */.separator {height: 1px;background-color: #e5e5e5;}/* 隔断样式 */.gap {height: 6px;background-color: #f5f5f5;}/* 取消按钮样式 */.cancel-item {padding: 15px;font-size: 15px;text-align: center;background-color: white;}</style>
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]