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, 等后续有时间研究好了再完善这个权限配置。

相关推荐
胡志辉的博客4 分钟前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
Hilaku30 分钟前
作为面试官,我最怕遇到什么样的候选人?
前端·javascript·程序员
TiDi1 小时前
Pinia优化重复请求
前端
web3d5201 小时前
01-用 Leafletjs 10 分钟搭一张水利一张图(Vue3 + Vite 实战)
前端·javascript
晚安日记wanna1 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
TiDi1 小时前
吸顶导航交互实现
前端
kisshyshy1 小时前
从Props透传到自定义Hook:系统梳理React跨层级通信与逻辑复用
前端·架构·代码规范
yume_sibai1 小时前
05-Flutter实战项目
前端·flutter
涛涛ing2 小时前
React 19.3 发布,但真正的赢家是 StyleX:当 AI 成为框架的“第一用户”
前端
zhangminghuan2 小时前
Vue vs React 实战拓展面试题|摒弃基础八股,聚焦业务开发核心难点(2026前端进阶)
前端