1、报错信息
- {
- "errMsg": "saveImageToPhotosAlbum:fail [Gallery:3302]未能完成操作。(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[matchKey] == original); return dataItem ? dataItem[originalKey] : 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企服之家,中国第一个企服评测及商务社交产业平台。 |