更新记录

0.1.0(2026-05-14) 下载此版本

更新日志

0.1.0

  • 初版创建 filekit-downloads UTS 插件目录结构
  • 新增 saveToDownloads(options) Promise API
  • 新增 SaveToDownloadsOptionsSaveToDownloadsResultSaveToDownloadsError 类型定义
  • Android 端基于 MediaStore.Downloads 实现保存到公共 Download/<subDir> 目录
  • 支持 _doc/_downloads/_www/tempFilePathsavedFilePath 等本地文件路径
  • 支持 renamereplaceerror 三种重名策略
  • 支持按文件名后缀自动推断 MIME 类型
  • 新增错误对象封装与可读错误码
  • 新增项目内联调页与插件 README 初稿

平台兼容性

uni-app(4.25)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × × 10.0 × ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × × ×

filekit-downloads

将 uni-app App 端本地临时文件或私有目录文件,保存到 Android 公共 Download/<subDir> 目录的 UTS 插件。

简介

filekit-downloads 首版专注解决一个问题:

  • _doc/_downloads/_www/uni.downloadFile 返回的 tempFilePathuni.saveFile 返回的 savedFilePath 等本地文件
  • 保存到 Android 公共下载目录 DownloadDownload/<subDir>

当前实现基于 Android MediaStore.Downloads,适合 uni-app App Android 场景下的文件导出、附件留存、本地临时文件转存等需求。

当前支持

  • 平台:App-Android
  • HBuilderX 建议最低版本:4.25+
  • Android 最低版本:29(Android 10)
  • API 风格:Promise
  • 保存目标:Download / Download/<subDir>
  • 重名策略:rename / replace / error
  • MIME:支持自动推断,兜底 application/octet-stream

兼容性说明

  • HBuilderX
    • 建议使用 4.25 或更高版本进行 UTS 插件开发、调试和打包
    • 插件 package.json 已声明 engines.HBuilderX = ^4.25.0
  • Android
    • 当前 utssdk/app-android/config.json 已声明 minSdkVersion = 29
    • 即首版最低支持 Android 10
  • iOS
    • 当前未提供实现

系统权限说明

插件本身需要的系统权限

当前版本插件本身不要求额外申请 Android 存储读写权限

原因:

  • 当前实现仅支持 Android 10+
  • 插件基于 MediaStore.Downloads 保存文件到公共 Download 目录
  • 当前实现不依赖传统外部存储直接路径写入方式
  • 插件最低 Android 版本为 29

当前权限结论

  • READ_EXTERNAL_STORAGE:插件本身不要求
  • WRITE_EXTERNAL_STORAGE:插件本身不要求
  • MANAGE_EXTERNAL_STORAGE:插件本身不要求,也不建议申请

需要区分的上游场景

虽然插件本身不要求额外存储权限,但业务侧在“获取源文件”阶段,仍可能依赖其他系统权限或系统能力:

  • 文件来自 uni.chooseImage、相机、相册等能力时
    • 是否需要相机/相册相关权限,取决于上游业务 API
  • 文件来自远程下载时
    • 需要网络能力,但这是下载链路需求,不是本插件保存能力本身的要求
  • 文件来自应用私有目录(如 _doc/tempFilePathsavedFilePath)时
    • 当前插件一般可直接处理,不需要额外存储权限

插件市场/接入文档建议表述

本插件当前版本基于 Android 10+ 的 MediaStore.Downloads 实现公共 Download 目录保存能力,插件本身不要求额外申请存储读写权限。
如业务上游涉及拍照、选图、远程下载等能力,请按对应 uni-app API 的要求申请相关权限。

不在当前范围内

  • iOS 实现
  • Android 9 及以下兼容分支
  • 远程下载能力封装
  • 打开文件能力
  • 业务提示 UI
  • 权限引导页面

适用场景

  • uni.chooseImage 选择后的临时文件保存到公共下载目录
  • uni.downloadFile 下载完成后的本地文件转存到公共目录
  • 将业务运行过程中生成的 _doc/ 文件导出给用户
  • 将 PDF、图片、Office、视频等文件保存到系统可见目录

安装方式

将插件放入项目 uni_modules 目录。

uni_modules/
└─ filekit-downloads/

然后在页面中直接引入:

import { saveToDownloads } from '@/uni_modules/filekit-downloads'

API

saveToDownloads(options)

将本地文件保存到 Android 公共 Download 目录。

saveToDownloads(options: SaveToDownloadsOptions): Promise<SaveToDownloadsResult>

SaveToDownloadsOptions

type SaveToDownloadsOptions = {
  srcPath: string
  fileName: string
  mimeType?: string
  subDir?: string
  conflictStrategy?: 'rename' | 'replace' | 'error'
  scanImmediately?: boolean
}

参数说明

  • srcPath
    • 必填
    • 本地文件路径,必须是文件,不能是目录
    • 支持 _doc/_downloads/_www/tempFilePathsavedFilePath
  • fileName
    • 必填
    • 保存到下载目录后的文件名,例如 test.jpg合同.pdf
  • mimeType
    • 可选
    • 不传时按文件名后缀自动推断
  • subDir
    • 可选
    • 例如传 MyAppFile,实际保存到 Download/MyAppFile
  • conflictStrategy
    • 可选,默认建议使用 rename
    • rename:同名自动重命名
    • replace:删除已有同名文件后保存
    • error:遇到同名文件直接报错
  • scanImmediately
    • 预留字段
    • 当前版本暂未启用额外扫描逻辑

SaveToDownloadsResult

type SaveToDownloadsResult = {
  success: boolean
  fileName: string
  mimeType: string
  relativePath: string
  contentUri: string
  absolutePath?: string
  message?: string
}

返回值说明

  • success
    • 是否保存成功
  • fileName
    • 最终保存的文件名
  • mimeType
    • 最终使用的 MIME 类型
  • relativePath
    • 例如 DownloadDownload/MyAppFile
  • contentUri
    • Android 10+ 下的结果标识
  • absolutePath
    • 可选,不保证所有系统环境都稳定返回
  • message
    • 简单结果描述

SaveToDownloadsError

type SaveToDownloadsError = {
  code: string
  message: string
  detail?: string
}

使用示例

示例 1:选择图片后保存

<script setup>
import { ref } from 'vue'
import { saveToDownloads } from '@/uni_modules/filekit-downloads'

const tempFilePath = ref('')
const fileName = ref('')

const buildFileName = (filePath) => {
  const normalized = (filePath || '').split('?')[0]
  const dotIndex = normalized.lastIndexOf('.')
  const ext = dotIndex >= 0 ? normalized.slice(dotIndex) : '.jpg'
  return `image-${Date.now()}${ext}`
}

const chooseAndSave = () => {
  uni.chooseImage({
    count: 1,
    sizeType: ['original'],
    success: async (res) => {
      tempFilePath.value = res.tempFilePaths[0] || ''
      fileName.value = buildFileName(tempFilePath.value)

      try {
        const result = await saveToDownloads({
          srcPath: tempFilePath.value,
          fileName: fileName.value,
          subDir: 'MyAppFile',
          conflictStrategy: 'rename'
        })
        console.log('保存成功', result)
      } catch (err) {
        console.error('保存失败', err)
      }
    }
  })
}
</script>

示例 2:uni.downloadFile 成功后再保存

import { saveToDownloads } from '@/uni_modules/filekit-downloads'

const downloadUrl = 'https://example.com/demo.pdf'

uni.downloadFile({
  url: downloadUrl,
  success: async (res) => {
    if (res.statusCode !== 200) {
      console.error('下载失败', res)
      return
    }

    if (!res.tempFilePath || res.tempFilePath.endsWith('/')) {
      console.error('无效临时文件路径', res.tempFilePath)
      return
    }

    try {
      const result = await saveToDownloads({
        srcPath: res.tempFilePath,
        fileName: 'demo.pdf',
        subDir: 'MyDownload',
        conflictStrategy: 'rename'
      })
      console.log('保存成功', result)
    } catch (err) {
      console.error('保存失败', err)
    }
  },
  fail: (err) => {
    console.error('下载接口调用失败', err)
  }
})

错误码

当前版本已规划并使用以下错误码:

  • INVALID_SRC_PATH
  • FILE_NOT_FOUND
  • INVALID_FILE_NAME
  • INSERT_MEDIASTORE_FAILED
  • OPEN_OUTPUT_STREAM_FAILED
  • COPY_STREAM_FAILED
  • UNSUPPORTED_PLATFORM
  • FILE_ALREADY_EXISTS

注意事项

1. srcPath 必须是文件路径

以下情况不能直接传给插件:

  • 目录路径
  • 下载临时目录
  • 末尾带 / 的路径

例如:

_doc/uniapp_temp_xxx/download/

这是目录,不是实际文件。

2. uni.downloadFilesuccess 不等于业务下载成功

uni.downloadFile 进入 success 回调时,仍然需要判断:

  • res.statusCode === 200
  • res.tempFilePath 是有效文件路径

如果返回:

{
  "statusCode": 400,
  "errMsg": "downloadFile:ok"
}

说明下载接口流程返回成功,但服务端拒绝了该请求,不能继续保存。

3. 常见的 HTTP 400 原因

  • 下载地址需要鉴权,但请求未带 token / cookie / 签名参数
  • 下载 URL 已过期
  • 下载地址不是直链文件地址
  • 服务端对请求头有要求
  • 服务端对来源或参数有校验

4. 文件名建议由业务层明确提供

虽然插件支持 MIME 自动推断,但仍建议业务层传入明确的 fileName,这样更利于:

  • 用户在下载目录中识别文件
  • 保证后缀正确
  • 控制重名策略行为

实现说明

当前 Android 实现基于 MediaStore.Downloads

  • 写入 DISPLAY_NAME
  • 写入 MIME_TYPE
  • 写入 RELATIVE_PATH
  • 使用 IS_PENDING 控制写入状态
  • 通过 ContentResolver.openOutputStream() 写入文件内容

这种方式更符合 Android 10+ 公共存储访问规范。

调试建议

  • 优先先测本地图片保存链路,再测远程下载后保存链路
  • 先验证 tempFilePath 是否为真实文件路径
  • 如下载返回非 200,先独立排查服务端下载地址
  • 如要测试远程下载,建议先用公开可访问的直链文件验证插件链路

路线图

  • 补齐 utssdk-test 标准自测结构
  • 补齐插件市场文档材料
  • 评估 Android 9 及以下兼容方案
  • 评估 iOS 空实现或后续支持方式

版本信息

  • 当前版本:0.1.0
  • 当前阶段:首版联调中

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。