微信小程序文件下载实现方式(UniApp)

前言

在 uni-app 中处理文件下载,核心优势在于跨平台兼容

uni-app 封装了 uni.downloadFile,同时针对不同平台(H5、App、小程序)提供了差异化的保存 API。

以下是 uni-app 中文件下载的完整方案:

核心流程对比

平台 下载API 保存/打开方式
微信小程序 uni.downloadFile uni.saveImageToPhotosAlbum / uni.openDocument / uni.shareFileMessage
App (Android/iOS) uni.downloadFileplus.downloader uni.saveImageToPhotosAlbum / plus.io 写入本地 / uni.openDocument
H5 uni.downloadFile (受限) 创建 <a> 标签触发浏览器下载

关键区别

App 端大文件强烈建议使用 plus.downloader(支持后台下载、断点续存、系统通知栏进度),uni.downloadFile 在 App 端仅适合小文件。

通用下载

下载是先下载到临时目录,再保存到不同的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 跨平台文件下载并保存
* @param {string} url - 文件地址
* @param {string} fileName - 文件名(含扩展名)
*/
export function downloadAndSave(url, fileName) {
// #ifdef H5
// ===== H5 平台:直接触发浏览器下载 =====
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return;
// #endif

// ===== App / 小程序平台 =====
uni.showLoading({ title: '下载中 0%', mask: true });

const task = uni.downloadFile({
url,
success(res) {
uni.hideLoading();
if (res.statusCode !== 200) {
return uni.showToast({ title: '下载失败', icon: 'none' });
}
saveFileByType(res.tempFilePath, fileName);
},
fail(err) {
uni.hideLoading();
console.error('下载失败:', err);
uni.showToast({ title: '网络异常', icon: 'none' });
}
});

task.onProgressUpdate((res) => {
uni.showLoading({ title: `下载中 ${res.progress}%`, mask: true });
});
}

按文件类型保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function saveFileByType(tempFilePath, fileName) {
const ext = fileName.split('.').pop().toLowerCase();
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
const videoExts = ['mp4', 'mov', 'avi', 'mkv'];
const docExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];

if (imageExts.includes(ext)) {
// ---- 保存图片 ----
uni.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: () => uni.showToast({ title: '已保存到相册' }),
fail: () => uni.showToast({ title: '保存失败', icon: 'none' })
});
} else if (videoExts.includes(ext)) {
// ---- 保存视频 ----
uni.saveVideoToPhotosAlbum({
filePath: tempFilePath,
success: () => uni.showToast({ title: '已保存到相册' }),
fail: () => uni.showToast({ title: '保存失败', icon: 'none' })
});
} else if (docExts.includes(ext)) {
// ---- 打开文档(预览+右上角菜单可保存/转发)----
uni.openDocument({
filePath: tempFilePath,
fileType: ext,
showMenu: true,
fail: () => uni.showToast({ title: '打开失败', icon: 'none' })
});
} else {
// ---- 其他文件:分享/转发 ----
// #ifdef MP-WEIXIN
uni.shareFileMessage({
filePath: tempFilePath,
fileName,
fail: () => uni.showToast({ title: '分享取消', icon: 'none' })
});
// #endif

// #ifdef APP-PLUS
// App端可用 plus.io 保存到自定义目录
saveToAppLocal(tempFilePath, fileName);
// #endif
}
}

APP端

大文件下载

plus.downloader

当文件 > 50MB 或需要后台下载时,必须用此方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// #ifdef APP-PLUS
export function downloadLargeFile(url, fileName) {
const dtask = plus.downloader.createDownload(
url,
{
filename: `_doc/download/${fileName}`, // 指定保存路径
retryInterval: 5, // 重试间隔(秒)
priority: 1 // 优先级
},
(download, status) => {
if (status === 200) {
uni.showToast({ title: '下载完成' });
// 下载完成后可用 uni.openDocument 或 plus.runtime.openFile 打开
uni.openDocument({
filePath: download.filename,
showMenu: true
});
} else {
uni.showToast({ title: `下载失败(${status})`, icon: 'none' });
}
}
);

// 监听进度
dtask.addEventListener('statechanged', (task) => {
if (task.totalSize && task.downloadedSize) {
const progress = Math.round((task.downloadedSize / task.totalSize) * 100);
uni.showLoading({ title: `下载中 ${progress}%`, mask: true });
}
});

dtask.start();
return dtask; // 返回任务对象,外部可调用 dtask.abort() 取消
}
// #endif

自定义目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// #ifdef APP-PLUS
function saveToAppLocal(tempFilePath, fileName) {
plus.io.resolveLocalFileSystemURL('_doc/download/', (entry) => {
entry.getFile(fileName, { create: true }, (fileEntry) => {
plus.io.copyFile(tempFilePath, fileEntry.toLocalURL(), () => {
uni.showModal({
title: '保存成功',
content: `文件已保存至: ${fileEntry.toLocalURL()}`,
showCancel: false
});
}, () => {
uni.showToast({ title: '保存失败', icon: 'none' });
});
});
}, () => {
// 目录不存在则创建
plus.io.resolveLocalFileSystemURL('_doc/', (root) => {
root.getDirectory('download', { create: true }, () => {
saveToAppLocal(tempFilePath, fileName); // 递归重试
});
});
});
}
// #endif

注意事项

注意点 说明
条件编译 uni-app 的核心能力,务必用 #ifdef / #ifndef 区分平台逻辑,避免 H5 调用 plus 报错
域名白名单 小程序端仍需在后台配置 downloadFile合法域名;App/H5 无此限制
权限声明 App 端保存相册需在 manifest.json → App模块配置 → Permissions 中勾选 WRITE_EXTERNAL_STORAGE;iOS 还需在 Info.plist 添加 NSPhotoLibraryAddUsageDescription
H5 跨域 H5 下载第三方资源受 CORS 限制,若服务端未配 Access-Control-Allow-Origin,需通过后端代理转发
临时文件清理 uni.downloadFile 产生的临时文件在 App 重启后可能被清除;如需持久化,App 用 plus.io.copyFile,小程序用 uni.getFileSystemManager().saveFile()
文件名中文乱码 URL 中的中文文件名建议先 encodeURIComponent 编码;openDocumentshareFileMessagefileName 参数传原始中文名即可
base64 转文件 如果接口返回的是 base64 而非 URL,先用 uni.base64ToArrayBuffer + FileSystemManager.writeFile 转为临时文件,再走上述保存流程

选型建议

1
2
3
4
5
文件大小 < 20MB  →  uni.downloadFile(全平台统一)
文件大小 ≥ 20MB → App: plus.downloader / 小程序: downloadFile+进度提示 / H5: <a>标签
需要断点续传 → 仅 App: plus.downloader(原生支持)
文档仅需预览不需保存 → uni.openDocument(全平台)
下载到App私有目录 → plus.io(仅App)

总结

uni-app 文件下载的关键是条件编译 + 平台差异化保存策略。小文件用 uni.downloadFile 一套代码搞定;App 端大文件务必切换到 plus.downloader;H5 端回归浏览器原生下载机制。始终记得处理相册授权失败和域名白名单这两个高频踩坑点。