微信小程序文件下载

由于产品需要给用户批量发送文件的功能,恰巧又是第一次接触小程序下载,也有很多坑,于是写了此篇文章供自己学习

目录

前置条件

实现思路

[路 0:让用户在系统文件管理器看到 ZIP](#路 0:让用户在系统文件管理器看到 ZIP)

[路 1:分享给好友或文件传输助手,窃取微信原生果实(用户接受不了)](#路 1:分享给好友或文件传输助手,窃取微信原生果实(用户接受不了))

[路 2:放弃「让用户在系统文件管理器看到 ZIP」](#路 2:放弃「让用户在系统文件管理器看到 ZIP」)

[路 3:只适配 电脑版微信](#路 3:只适配 电脑版微信)

[路 4:引导用户前往浏览器下载即可](#路 4:引导用户前往浏览器下载即可)

ok,接下来将相对完美的方案

一、当用户点击zip文件时

二、当用户点击文档图片视频时

另外感谢csdn的自动保存功能,由于下班着急走直接关电脑了,第二天发现文章空白,然后突然飘来历史草稿几个字哈哈哈


前置条件

思路很明确就是要把文件下载到用户手机上,让其能在手机中查看(后来想一想,用户也没必要在在自己手机中文件管理中查看啊,直接在小程序内浏览,但无疑会加重小程序的缓存)

首先先了解下前置条件,小程序是不允许将文件随意下载到手机系统文件内的,为了避免恶意攻击,文件只能存放在小程序的沙箱中,也是为了避免重复发送网络请求获取文件。

那么zip压缩包就无法直接被下载到用户手机中,微信原生中Android是可以通过主动保存zip到文件管理中的,但ios就行不通了

由于经过一天的努力也没有看到体面一点的下载方式,经过多次逼问豆包更是得到如下回复:

但是!!!!!!!!!!!!!!!!!!!!!!!!!!!

正所谓上有政策下有对策,欢迎广大程序员用你们的秘方砸死我

实现思路

路 0:让用户在系统文件管理器看到 ZIP

一开始通过文档,以为直接保存到文件即可像微信那样将文件存放到文件管理中,但经过实践发现getFileSystemManager().saveFile的保存文件并非保存到系统盘,而是会将文件保存到小程序沙箱中,虽可通过Android/data/com.tencent.mm/MicroMsg/wxanewfiles找到被保存的文件,但这种方式对用户来讲太糟糕了,也仅限安卓用户。

javascript 复制代码
const res = await uni.downloadFile({
					url: zipUrl
				});
				
				const fs= uni.getFileSystemManager();
				fs.saveFile({
					tempFilePath:res.tempFilePath,
					success:(suc)=>{
						console.log(suc)
						uni.showToast({
							title: "下载成功"+suc.savedFilePath,
							icon: "none"
						});
					},
					fail:(fail)=>{
						uni.showToast({
							title: "下载失败,请稍后重试",
							icon: "none"
						});
					}
				})

路 1:分享给好友或文件传输助手,窃取微信原生果实(用户接受不了)

通过分享功能发送到微信中,在微信中打开zip即可美美预览下载

javascript 复制代码
wx.shareFileMessage({
					filePath: res.tempFilePath,
					success: (suc) => {
						console.log(suc)
					},
					fail(fail) {
						console.log(fail)
					}
				});

路 2:放弃「让用户在系统文件管理器看到 ZIP」

小程序里正常走:下载 → 私有沙盒能用小程序内部解压、内部浏览,但用户自己在文件管理找不到适合不需要用户导出、只需要内部使用的场景。

通过wx.downloadFile拿到压缩包的临时路径,然uni.getFileSystemManager()获取文件管理器使用unzip进行解压,解压后的数据格式为文件名1,文件名2

javascript 复制代码
// 主动解压文件,用户手动下载解压后的文件
	const downLoadFile = () => {
		wx.downloadFile({
			url: zipUrl,
			success(res) {
				if (res.statusCode === 200) {
					const tempPath = res.tempFilePath; // 临时路径
					// 解压文件
					const fs = uni.getFileSystemManager(); //获取文件管理器
					fs.unzip({
						zipFilePath: tempPath,
						targetPath: uni.env.USER_DATA_PATH + "/unzip", // 解压目录
						success: () => {
							console.log("✅ 解压成功");
							fs.readdir({
								dirPath: `${uni.env.USER_DATA_PATH}/unzip`,
								success(result) {
									console.log("📂 解压出来的文件:", result.files);
									fileList.value = result.files
									// files 就是所有文件名数组
									// 例如:["logo.png","info.txt","demo.jpg"]
								}
							});
						},
						fail: (err) => {
							console.log("❌ 解压失败", err);
						},
					});
				}
			},
			fail() {
				wx.showToast({
					title: "下载失败",
					icon: "none"
				});
			}
		});
	}

将文件存放到沙箱的unzip目录内,然后通过文件名获取这些文件,判断其类型后用对应的api打开

javascript 复制代码
//打开文件
	const openFile = (fileName) => {
		const fullPath = `${uni.env.USER_DATA_PATH}/unzip/${fileName}`;

		// 获取文件后缀
		const ext = fileName.split('.').pop().toLowerCase();

		// 图片类型 → 用 previewImage 打开
		if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) {
			uni.previewImage({
				urls: [fullPath]
			});
		}
		// 文档类型 → 用 openDocument
		else if (['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'].includes(ext)) {
			uni.openDocument({
				filePath: fullPath
			});
		}
		// 其他格式 → 提示不支持
		else {
			uni.showToast({
				title: '不支持打开此类型',
				icon: 'none'
			});
		}
	}

路 3:只适配 电脑版微信

用 wx.saveFileToDisk(Object object)电脑端可以弹出另存为,存到电脑本地,完美能用手机安卓 /iOS 直接放弃原生保存

javascript 复制代码
wx.saveFileToDisk({
  filePath: `${wx.env.USER_DATA_PATH}/hello.txt`,
  success(res) {
    console.log(res)
  },
  fail(res) {
    console.error(res)
  }
})

tip:使用uni.saveImageToPhotosAlbum或保存视频的api 也是可以保存zip到本地的,但也只有微信pc端小程序可以实现

路 4:引导用户前往浏览器下载即可

emmm...帮用户复制下网址吧

javascript 复制代码
uni.setClipboardData({
				data: zipUrl,
				success: () => {
					uni.showModal({
						title: "请用浏览器下载",
						content: "已复制链接,打开 Safari/Chrome 粘贴即可下载ZIP",
						showCancel: false
					});
				}
			});

ok,接下来将相对完美的方案

将文件分为zip和 (文档,图片,视频) 两类,允许用户预览和下载,

一、当用户点击zip文件时

进行设备信息判断,如果时pc端小程序则直接使用思路3,如果为手机端小程序则提供两个选择,分别为思路1和思路4

这里需要注意的是,一定要做出选项后再去下载zip到沙箱,再进行转发不然用户一直点取消你不炸了么

二、当用户点击文档图片视频时

拿到文件的后缀判断文件类型,分别调用对应的api即可,下载也是一样的

预览时图片和视频是允许使用网络路径的,但文档预览时必须使用临时地址或者沙箱内地址

有点击就有可能会出现并发,可以通过状态锁(跟防抖不太一样,毕竟用户一直点,你就一直不给下载,他就一直点,你就一直不给下载,他就一直点,你就一直不给下载..............................................................)和遮罩来避免,思路:定义一个状态,只要当前有文件正在准备打开,就拦截所有的后续点击。当用户点击下载后弹出遮罩层禁止用户进行傻瓜操作

最后无论下载成功还是失败记得流程结束后把状态锁打开!!!

代码如下

javascript 复制代码
const zipFileList = ref([])
	// 下载压缩包
	const downLoadZip = async (url) => {
		const sys = uni.getSystemInfoSync(); //判断用户为ios还是andrion
		if (["android", "devtools", "ios", "ohos"].includes(sys.platform)) {
			uni.showActionSheet({
				itemList: ["发送给文件传输助手", "复制链接到浏览器下载"],
				success: function(res) {
					if (res.tapIndex == 0) {
						uni.downloadFile({
							url,
							success(res) {
								if (res.statusCode === 200) {
									wx.shareFileMessage({
										filePath: res.tempFilePath // 下载后的临时路径
									})
								} else {
									uni.showToast({
										title: '文件损坏',
										icon: 'none'
									});
								}
							},
							fail: function(res) {
								uni.showToast({
									title: '请稍后重试',
									icon: 'none'
								});
							}
						})
					} else {
						uni.setClipboardData({
							data: url,
							success: () => {
								uni.showModal({
									title: "请用浏览器下载",
									content: "已复制链接,请打开手机自带浏览器(如Safari/Chrome)粘贴网址进行下载",
									showCancel: false
								});
							}
						});
					}
				}
			})

		} else if (["windows", "mac"].includes(sys.platform)) {
			uni.downloadFile({
				url,
				success(res) {
					if (res.statusCode === 200) {
						wx.saveFileToDisk({
							filePath: res.tempFilePath,
							success() {
								wx.showToast({
									title: '保存成功'
								});
							},
							fail(error) {
								uni.showToast({
									title: "保存失败",
									icon: "none"
								})
							}
						})
					} else {
						uni.showToast({
							title: '文件损坏',
							icon: 'none'
						});
					}
				},
				fail: function(res) {
					uni.showToast({
						title: '文件损坏',
						icon: 'none'
					});
				}
			})

		} else {
			uni.setClipboardData({
				data: url,
				success: () => {
					uni.showModal({
						title: "请用浏览器下载",
						content: "已复制链接,请打开手机自带浏览器(如Safari/Chrome)粘贴网址进行下载",
						showCancel: false
					});
				}
			});
		}
	}
	// 打开文件
	const previewMyFile = async (fileUrl) => {
		// 完整网络地址
		const fullNetworkUrl = serverUrl + fileUrl;
		// 文件名
		const fileName = fileUrl.split('/')[fileUrl.split('/').length - 1]
		// 获取文件后缀
		const ext = fileName.split('.').pop().toLowerCase();
		// 1.压缩包
		if (['zip', 'rar', '7z'].includes(ext)) {
			downLoadZip(fullNetworkUrl);
			return;
		}
		// 2. 图片网络直开
		if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) {
			uni.previewImage({
				urls: [fullNetworkUrl]
			});
			return;
		}
		if (['mp4', 'mov'].includes(ext)) {
			uni.previewMedia({
				sources: [{
					url: fullNetworkUrl,
					type: "video"
				}]
			});
			return;
		}
		// 3. 文档类:必须走 沙箱缓存 逻辑
		if (['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'].includes(ext)) {

			if (isHandlingPreview) return; // 【预览锁】生效
			isHandlingPreview = true;

			uni.showLoading({
				title: '正在打开...',
				mask: true
			});

			try {
				const fs = uni.getFileSystemManager();
				const dirPath = `${uni.env.USER_DATA_PATH}/myFile`;
				const fullPath = `${dirPath}/${fileName}`;

				// 第一步:查沙箱缓存
				try {
					fs.accessSync(fullPath);
					// 有缓存,直接打开,结束流程
					uni.openDocument({
						filePath: fullPath,
						showMenu: true
					});
					return;
				} catch (e) {
					// 无缓存,继续往下执行下载
				}

				// 第二步:沙箱无缓存,下载临时文件
				const res = await uni.downloadFile({
					url: fullNetworkUrl
				});
				if (res.statusCode === 200) {
					try {
						// 建目录,存沙箱
						try {
							fs.accessSync(dirPath);
						} catch (err) {
							fs.mkdirSync(dirPath, true);
						}
						fs.saveFileSync(res.tempFilePath, fullPath);
						// 打开沙箱文件
						uni.openDocument({
							filePath: fullPath,
							showMenu: true
						});
					} catch (saveErr) {
						// 兜底:存沙箱失败,用临时路径直接打开
						uni.openDocument({
							filePath: res.tempFilePath,
							showMenu: true
						});
					}
				} else {
					uni.showToast({
						title: '文件读取失败',
						icon: 'none'
					});
				}
			} catch (err) {
				uni.showToast({
					title: '请稍后重试',
					icon: 'none'
				});
			} finally {
				uni.hideLoading();
				setTimeout(() => {
					isHandlingPreview = false;
				}, 300); // 解锁
			}
			return;
		}
		// 4. 其他未知格式
		uni.showToast({
			title: '不支持预览此格式,请尝试下载',
			icon: 'none'
		});
	}
	// 下载文件
	const downloadToPhone = async (fileUrl) => {
		// 完整网络地址
		const fullNetworkUrl = serverUrl + fileUrl;
		// 文件名
		const fileName = fileUrl.split('/')[fileUrl.split('/').length - 1]
		// 获取文件后缀
		const ext = fileName.split('.').pop().toLowerCase();
		// 1.压缩包
		if (['zip', 'rar', '7z'].includes(ext)) {
			downLoadZip(fullNetworkUrl);
			return;
		}
		if (isHandlingDownload) return; // 生效
		isHandlingDownload = true;

		uni.showLoading({
			title: '正在下载...',
			mask: true
		});
		try {
			const fs = uni.getFileSystemManager();
			const dirPath = `${uni.env.USER_DATA_PATH}/myFile`;
			const fullPath = `${dirPath}/${fileName}`;
			let isFileExist = true;
			// 第一步:查沙箱缓存
			try {
				fs.accessSync(fullPath);
				isFileExist = true;
			} catch (e) {
				isFileExist=false
			}
			if(!isFileExist){
				// 第二步:沙箱无缓存,下载临时文件
				const res = await uni.downloadFile({
					url: fullNetworkUrl
				});
				if (res.statusCode === 200) {
					try {
						// 建目录,存沙箱
						try {
							fs.accessSync(dirPath);
						} catch (err) {
							fs.mkdirSync(dirPath, true);
						}
						fs.saveFileSync(res.tempFilePath, fullPath);
						
					} catch (saveErr) {
						// 如果沙箱存不进去直接下载res.tempFilePath
						// 1.直接下载
						if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) {
							uni.saveImageToPhotosAlbum({
								filePath: res.tempFilePath
							})
							return;
						}
						if (['mp4', 'mov'].includes(ext)) {
							uni.saveVideoToPhotosAlbum({
								filePath: res.tempFilePath
							})
							return;
						}
						// 2.无法直接下载 文档类
						if (['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'].includes(ext)) {
							uni.openDocument({
								filePath: res.tempFilePath,
								showMenu: true
							});
						}
						return;
					}
				} else {
					uni.showToast({
						title: '请稍后重试',
						icon: 'none'
					});
				}
			}
			
			// 1.直接下载
			if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) {
				uni.saveImageToPhotosAlbum({
					filePath: fullPath
				})
				return;
			}
			if (['mp4', 'mov'].includes(ext)) {
				uni.saveVideoToPhotosAlbum({
					filePath: fullPath
				})
				return;
			}
			// 2.无法直接下载 文档类
			if (['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'].includes(ext)) {
				uni.openDocument({
					filePath: fullPath,
					showMenu: true
				});
			}
		} catch (err) {
			uni.showToast({
				title: '请稍后重试',
				icon: 'none'
			});
		} finally {
			uni.hideLoading();
			setTimeout(() => {
				isHandlingDownload = false;
			}, 300); // 解锁
		}
		return;
	}
	

另外感谢csdn的自动保存功能,由于下班着急走直接关电脑了,第二天发现文章空白,然后突然飘来历史草稿几个字哈哈哈

相关推荐
橘子海全栈攻城狮4 小时前
【最新源码】鸟博士微信小程序 023
spring boot·后端·web安全·微信小程序·小程序
Yuujs4 小时前
微信小程序反编译保姆级教程
微信小程序·小程序
m0_647057964 小时前
微信小程序同声传译(WechatSI)通用接入教程
微信小程序·小程序·notepad++
梁辰兴4 小时前
微信小程序开发者工具下载与安装
微信·微信小程序·小程序·教程·软件安装·开发者工具
2501_915909068 小时前
深入解析Mock.js:功能、应用及实战案例,提升前端开发效率
android·ios·小程序·https·uni-app·iphone·webview
于先生吖8 小时前
前后端分离体育服务项目,场馆计费+线下赛事排行小程序部署开发教程
java·小程序·uni-app
黄华SJ520it8 小时前
上门回收旧衣小程序开发全流程解析
小程序·系统开发
万岳科技系统开发8 小时前
互联网医院小程序搭建如何快速上线?完整建设方案解析
小程序·apache
kke_8811 小时前
一年12个月,小程序UV的季节性波动规律
大数据·小程序·uv