项目场景如下
开发框架是uniapp,使用uniapp脚手架搭建,最终打包成H5部署在服务器上。
微信小程序的主体内容使用了<web-view>标签将H5的页面内容展示,H5中有页面存放了下载的路径。点击下载按钮下载文件,或者预览文件让用户手动保存。
难点
如果是pc端,下载用一个<a>标签就很容易,但是在小程序里的<web-view>标签中是行不通的。此外,小程序里的<web-view>与小程序的通行方式主要用postMessage,但是触发条件非常苛刻,参照微信的官方文档,只在小程序后退,组件销毁,还有分享时才会触发postMessage并一次性把值全部带出来,使用起来非常不变,对于小程序实际内容主体是<web-view>内嵌的spa的H5页面的情况下,销毁组件会带来很多麻烦,因此最后放弃了这个方案。
我的方法
首先必不可少的是安装jweixin-module模块
npm i jweixin-module
在main.js中将依赖绑定
import wx from "jweixin-module"
Vue.prototype.$wx = wx
H5对应页面点击下载时
假设你的文件路径是
https://sample.com/apiName.do?fileToken=ABCDEFG&fileName=HIJKLMN.png
这里有需要注意的3点
1是?会被微信小程序解析,所以需要替换成&,等传入微信再去拼接
2是fileName与fileToken,假设链接里有这些额外的参数,那这些参数因为很可能组成内容比较复杂,会有特殊字符,所以需要转义,然后在微信小程序的看情况有需要的话就解析回去。这里使用encodeURIComponent与decodeURIComponent去处理可能存在特殊字符的字段。
3是我的实际场景里,获取服务器的图片需要鉴权,所以我会额外带一个cookie,也使用&拼接在链接后。
let fileToken = "ABCDEFG"
let fileName = "HIJKLMN.png"
this.$wx.miniProgram.navigateTo({
url:"/pages/upload/page?link=https://sample.com/apiName.do&fileToken="+encodeURIComponent(fileToken) + "&fileName=" + encodeURIComponent(fileName)
})
小程序里,对应的/pages/upload/page页面
onLoad: function(options) {
let that = this;
wx.setStorageSync('uploadData', options)//保存传入的参数
//option为一个对象,内容就是{ link,fileToken,fileName,cookie等之前使用&拼上的数据}
let pages = getCurrentPages();
let previousPage = pages[pages.length - 2]; //上一个页面
previousPage.setData({
isDownLoadPageBack: true //在上一个页面设置标记,用来判断
})
wx.navigateBack({
delta: 1
})
},
返回到之前有web-view 的页面
data: {
isDownLoadPageBack: false,
},
onShow: function() {
wx.hideHomeButton();
if (this.data.isDownLoadPageBack) {
this.getDownLoadFile() //具体的微信下载文件的方法
}
//每次onShow执行完,还有上面的下载方法执行完后要把这个标记重置为false,这样不同情况触发的onShow才能区分是否是下载文件页面回来的。可能写的重复但是多写几次比较放心
this.setData({
isDownLoadPageBack: false
})
},
下载方法
getDownLoadFile: function() {
wx.showLoading({
title: '下载中', //提示文字
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
})
let that = this;
let options = wx.getStorageSync('uploadData')
let timeStamp = (Date.parse(new Date())) / 1000; //定义了时间戳拼接在文件名前防止名字重复
//这里是判断文件类型,是否是图片,后面用来判断并使用不同的展示方式
//写的很菜,凑合看下就行
let originFileName = decodeURIComponent(options.fileName)
let originFileNameSplit = originFileName.split('.')
let fileNameType = originFileNameSplit[originFileNameSplit.length - 1]
let imageType = ['gif', 'png', 'jpg', 'tif', 'bmp', 'webp', 'jpeg', 'JPG', ]
let myCookie = decodeURIComponent(options.cookie)
const downloadTask = wx.downloadFile({ //微信下载文件方法
//这里拼接成需要的格式
url: options.link + '?fileToken=' + options.fileToken + '&&fileName=' + options.fileName,
header: {
'cookie': myCookie
},
success(res) {
wx.getFileSystemManager().saveFile({ //微信保存文件,这个存储有点复杂
// 临时存储路径,先有临时存储路径方可使用wx官方的存储本地路径( wx.env.USER_DATA_PATH )
tempFilePath: res.tempFilePath,
//定义本地的存储路径及名称
filePath: wx.env.USER_DATA_PATH + '/' + timeStamp + originFileName,
success(res) {
const savedFilePath = res.savedFilePath;
wx.hideLoading()
if (imageType.includes(fileNameType)) {
wx.previewImage({
urls: [savedFilePath],
})
} else {
wx.openDocument({ //微信打开文件
filePath: savedFilePath,
showMenu: true,
success: function(res) {
that.setData({
waitWord: '返回请点击左上角',
isDownLoadPageBack: false
});
},
fail: function(err) {
console.log(res)
wx.showToast({
title: '预览失败',
icon: 'error',
duration: 1500
})
that.setData({
waitWord: '文件预览失败,请稍后重试',
isDownLoadPageBack: false
});
}
});
}
},
fail(err) {
wx.showToast({
title: '预览失败',
icon: 'error',
duration: 1500
})
that.setData({
waitWord: '文件预览失败,请稍后重试',
isDownLoadPageBack: false
});
}
})
},
fail(err) {
wx.hideLoading()
wx.showToast({
title: '下载失败',
icon: 'error',
duration: 1500
})
console.log(err)
that.setData({
waitWord: '文件下载失败,请稍后重试',
isDownLoadPageBack: false
});
}
})
downloadTask.onProgressUpdate((res) => {
console.log('下载进度', res.progress)
console.log('已经下载的数据长度', res.totalBytesWritten)
console.log('预期需要下载的数据总长度', res.totalBytesExpectedToWrite)
})
}
总结
大致过程就这样。缺点是点击下载时页面会一闪而过空白的微信页面再回来。如果有其他的更友好的实现方式也请不吝赐教。