卖不甜枣 发表于 2024-12-23 02:52:12

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]
查看完整版本: uniapp APP端 ios保存图片报错办理方案