Uniapp IOS 和 Android 下的文件写入用户目录

最近需要在 APP 中实现文件的导出功能,研究了有一会的将文件保存到用户目录,并打开文件预览。为了防止后续再次踩坑,所以快速记录一下配置方式。

文件读写授权

在 uniapp 中要首先 IOS 和 Android 将文件保存到用户本地目录, 首先需要在 manifest.json 配置文件读写权限, 如果不配置读写权限则会保存到 Android 的包名路径/ IOS 容器路径中

json 复制代码
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
          "<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>"
         ]
      }
      "apple": {
        "plistcmds": [
          "Set :NSFileProviderDomainUsageDescription 需要访问文件以保存用户数据",
          "Set :UIFileSharingEnabled true",
          "Set :LSSupportsOpeningDocumentsInPlace true"
        ]
      }

在 Android 下, 需要明确指定以 file://storage/emulated/0/ 为开头的文件路径,才能保存到本地目录。而在 IOS 中, 可以直接指定 _doc 目录

文件下载保存并打开

为了放置文件重复保存, 建议先使用 resolveLocalFileSystemURL 首先判断一下文件是否下载过了, 如果文件已经存在, 那么就直接执行例如文件打开的操作

如果文件不存在, 就使用 plus.downloader.createDownload 将文件下载到指定的目录,再使用 plus.io.convertLocalFileSystemURL 将下载文件转为平台路径,最后在执行例如文件打开的操作

javascript 复制代码
/**
 * 保存并打开文件
 * @param url 文件网络地址
 */
async function saveFile(url: string) {
  const fileName = url.split("/").pop()?.split("?")[0];

  console.log(url);
  // #ifdef APP-PLUS
  let realAbs = "";
  const platform = uni.getSystemInfoSync().platform;
  if (platform === "ios") {
    realAbs = "_doc";
  }

  if (platform === "android") {
    let granted = await permision.requestAndroidPermission(
      "android.permission.WRITE_EXTERNAL_STORAGE"
    );
    if (granted === GrantedStatusEnum.GRANTED) {
      realAbs = "file://storage/emulated/0/Download";
    } else {
      // 文件保存到 /Download
      realAbs = "_downloads";
      notify.showNotify({
        message: `未能授权文件权限,文件将临时保存`,
        type: "danger",
      });
    }
  }

  // 平台路径
  const relPath = `${realAbs}/${fileName}`;

  // 文件判存
  plus.io.resolveLocalFileSystemURL(
    relPath,
    async () => {
      console.log("[saveFile] 文件已存在,直接打开");

      if (platform === "android") {
        await uni.showToast({
          title: `文件已保存至: /Download/${fileName}`,
          icon: "none",
        });
      }

      openDocument(relPath);
    },
    () => {
      console.log("[saveFile] 文件不存在,开始下载");
      const task = plus.downloader.createDownload(
        url,
        { filename: relPath },
        async (d, status) => {
          if (status === 200) {
            if (platform === "android") {
              await uni.showToast({
                title: `文件已保存至: /Download/${fileName}`,
                icon: "none",
              });
            }

            const realAbs = plus.io.convertLocalFileSystemURL(d.filename);
            openDocument(realAbs);
          } else {
            notify.showNotify({
              message: `文件导出失败, 请稍后重试`,
              type: "danger",
            });
          }
        }
      );
      task.start();
    }
  );
  // #endif

  // #ifdef H5
  location.href = url;
  // #endif
}

function openDocument(absPath: string) {
  plus.runtime.openFile(absPath, {}, (res) => {
    if (res.code === -1) {
      notify.showNotify({
        message: "无法打开文件,请检查是否安装对应应用",
        type: "danger",
      });
    }
  });
}

结束语

目前 IOS 的保存路径还没弄清楚,现在 IOS 需要文件预览打开后手动保存一次。指定 _downloads 似乎没有生效,可能需要手动请求一次权限才能完成写入,所以这里先投机取巧写入了 _doc, 等后续有时间研究好了再完善这个权限配置。

相关推荐
zhuà!4 分钟前
腾讯地图TMap标记反显,新增标记
前端·javascript·vue.js
未知原色6 分钟前
web worker使用总结(包含多个worker)
前端·javascript·react.js·架构·node.js
ttod_qzstudio17 分钟前
CSS改变图片颜色方法介绍
前端·css
curdcv_po38 分钟前
我接入了微信小说小程序官方阅读器
前端·微信小程序
程序员鱼皮1 小时前
什么是 RESTful API?凭什么能流行 20 多年?
前端·后端·程序员
www_stdio1 小时前
让大语言模型拥有“记忆”:多轮对话与 LangChain 实践指南
前端·langchain·llm
inferno1 小时前
JavaScript 基础
开发语言·前端·javascript
cindershade1 小时前
Intersection Observer 的实战方案
前端
青莲8431 小时前
Kotlin Flow 深度探索与实践指南——中部:实战与应用篇
android·前端
cindershade1 小时前
事件委托(Event Delegation)的原理
前端