文章目录
uniapp + 安卓APP + H5 + 微信小程序实现PDF文件的预览和下载
1、用到的技术及插件
uniapp、pdf.js(4.0.379,我的node是v16.15.0,这个版本正好)、微信小程序api
2、简述操作:
下载
安卓APP:点击“下载”后获取手机授权,可下载到手机“文件app”的最外层可访问目录下“file://storage/emulated/0/com.custom/”
H5:
安卓:点击“下载”后,基本上所有的浏览器均可弹窗下载
苹果:(推荐使用Safari浏览器,其他浏览器每个效果都不一样)点击“下载”后预览PDF文件,点击页面中间的“分享”按钮,选择“保存到文件”
微信小程序:
安卓:点击“下载”后预览PDF文件,点击右上角“...”三个点,选择“保存到手机”
苹果:点击“下载”后预览PDF文件,点击右上角“...”三个点,选择用“其他应用打开”,再点击“保存到文件”
预览
安卓APP:点击“预览”后通过pdf.js实现预览
H5:点击“预览”后通过pdf.js实现预览
微信小程序:点击“预览”后通过小程序内实现预览
3、上代码:(主要是写后端,前端不大熟,我感觉写的还凑活,不对的请指正嘻嘻)
提示:主要是通过一个接口获取服务器端文件的路径
1、在项目static目录下新建一个pdf目录,将pdf.js解压后两个目录个一个文件复制到该目录下
2、新建一个预览页面
<template>
<view>
<web-view :src = "url"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
url : '',
viewerUrl:'/static/pdf/web/viewer.html?file=',//刚解压的文件地址,用来渲染PDF的html
}
},
onLoad(options) {
this.url = this.viewerUrl + options.url//将插件地址与接收的文件地址拼接起来
},
}
</script>
以下为业务代码
// APP下载------------------------------------START----------------------------------------------------
handlePdfDownload(approvalId, attachmentName) {
uni.showLoading({
title: '下载中...'
});
let data = {
approvalId: approvalId,
downloadType: '1'
}
let _this = this
this.$api.requestPost('/api', data, true)
.then(res => {
if (res.code && res.code != 200) {
uni.hideLoading();
uni.showToast({
title: res.msg,
icon: 'none',
duration: 3000
});
return;
}
let fileUrl = `${url_config}filePath${res.data}`;
//条件编译,若为h5端则直接赋值文件地址
// #ifdef H5
this.download4H5(fileUrl, attachmentName)
// #endif
//条件编译,若为App端,则需要将本地文件系统URL转换为平台绝对路径
// #ifdef APP-PLUS
this.download4App(fileUrl, attachmentName)
// #endif
// #ifdef MP-WEIXIN
this.download4WX(fileUrl, attachmentName)
// #endif
})
},
async createDir(path) {
try {
// 申请本地存储读写权限
return new Promise((resolve, reject) => {
plus.android.requestPermissions([
'android.permission.WRITE_EXTERNAL_STORAGE',
'android.permission.READ_EXTERNAL_STORAGE',
'android.permission.INTERNET',
'android.permission.ACCESS_WIFI_STATE'
], function (e) {
if (e.deniedAlways.length > 0) { //权限被永久拒绝
reject(new Error('权限被永久拒绝'));
}
if (e.deniedPresent.length > 0) {
//权限被临时拒绝
reject(new Error('权限被临时拒绝'));
}
if (e.granted.length > 0) { //权限被允许
//调用依赖获取读写手机储存权限的代码
// _this.exportFile()
const File = plus.android.importClass('java.io.File');
let file = new File(path);
if (!file.exists()) { // 文件夹不存在即创建
if (file.mkdirs()) {
resolve(true); // 成功创建文件夹
} else {
reject(new Error('未能创建文件夹'));
//resolve(false); // 未能创建文件夹
}
} else {
resolve(true); // 文件夹已存在
}
}
}, function (e) {
});
});
} catch (error) {
console.error('权限请求失败:', error);
return false; // 返回 false 表示权限请求失败
}
},
async download4App(fileUrl, attachmentName) {
try {
// 获取文件夹路径
const path = '/storage/emulated/0/com.custom';
// 确保文件夹存在
const createDirResult = await this.createDir(path);
// 检查文件夹是否创建成功
if (createDirResult) {
// 处理文件名
let lastPointIndex = attachmentName.lastIndexOf('.');
let fileType = attachmentName.substring(lastPointIndex + 1);
let endFileName = attachmentName.substring(0, lastPointIndex) + '_' + new Date().getTime() + '.pdf';
// 开始文件下载
let task = plus.downloader.createDownload(fileUrl, {
filename: 'file://storage/emulated/0/com.custom/' + endFileName
}, function (d, status) {
if (status === 200) {
uni.hideLoading();
uni.showToast({
icon: 'none',
title: '成功下载到【手机文件->"com.custom"目录->' + endFileName + '】',
duration: 3000
});
// d.filename是文件在保存在本地的相对路径,使用下面的API可转为平台绝对路径
let fileSaveUrl = plus.io.convertLocalFileSystemURL(d.filename);
console.log(fileSaveUrl);
// plus.runtime.openFile(d.filename)//选择软件打开文件
} else {
uni.hideLoading();
uni.showToast({
icon: 'none',
title: '下载失败',
});
plus.downloader.clear();
}
});
uni.showLoading({
title: '下载中...'
});
task.start();
} else {
uni.hideLoading();
uni.showToast({
title: '权限请求失败',
icon: 'none',
});
console.error('权限请求失败');
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message,
icon: 'none',
});
console.error('下载过程中发生错误:', error);
}
},
download4H5(fileUrl, attachmentName) {
uni.downloadFile({
//需要预览的文件地址
url: fileUrl,
header: {
"Access-Control-Expose-Headers": 'Content-Disposition'
},
success: (res) => {
if (res.statusCode === 200) {
uni.hideLoading();
//下载成功,得到文件临时地址
console.log('下载成功', res.tempFilePath);
let newUrl = res.tempFilePath
// 创建一个临时的 <a> 元素用于下载
const link = document.createElement('a');
link.href = newUrl;
link.setAttribute('download', attachmentName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none',
})
}
},
fail() {
uni.hideLoading();
uni.showToast({
title: '下载异常',
icon: 'none',
})
}
});
},
download4WX(fileUrl, attachmentName) {
wx.downloadFile({
url: fileUrl,
success(res) {
// 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容
if (res.statusCode === 200) {
uni.hideLoading();
wx.openDocument({
filePath: res.tempFilePath,
showMenu: true, //关键点
success: function (res) {
console.log('打开文档成功')
},
fail: function (err) {
uni.showToast({
title: '文档打开失败',
icon: 'none',
duration: 3000
});
}
})
} else {
uni.hideLoading();
uni.showToast({
title: '文档下载失败',
icon: 'none',
});
}
},
fail() {
uni.hideLoading();
uni.showToast({
title: '下载异常',
icon: 'none',
})
}
})
},
// APP下载------------------------------------END----------------------------------------------------
// APP预览------------------------------------START----------------------------------------------------
handlePdfOpen(approvalId) {
uni.showLoading({
title: '正在打开文档...'
});
let data = {
approvalId: approvalId,
downloadType: '0'
}
this.$api.requestPost('/api', data, true)
.then(res => {
// uni.hideLoading();
if (res.code && res.code != 200) {
uni.hideLoading();
uni.showToast({
title: res.msg,
icon: 'none',
duration: 3000
});
return;
}
this.previewPdf(`${url_config}filePath${res.data}`)
})
},
previewPdf(fileUrl) {
//uniapp官方的下载api
uni.downloadFile({
//需要预览的文件地址
url: fileUrl,
header: {
"Access-Control-Expose-Headers": 'Content-Disposition'
},
success: (res) => {
if (res.statusCode === 200) {
//下载成功,得到文件临时地址
console.log('下载成功', res.tempFilePath);
uni.hideLoading();
//条件编译,若为h5端则直接赋值文件地址
// #ifdef H5
let newUrl = res.tempFilePath
// uni.hideLoading();
//这里新建一个vue页面,跳转并预览pdf文档
uni.navigateTo({
url: "/pages/pdfView?url=" + newUrl,
})
// #endif
//条件编译,若为App端,则需要将本地文件系统URL转换为平台绝对路径
// #ifdef APP-PLUS
let newUrl = plus.io.convertLocalFileSystemURL(res.tempFilePath)
// uni.hideLoading();
//这里新建一个vue页面,跳转并预览pdf文档
uni.navigateTo({
url: "/pages/pdfView?url=" + newUrl,
})
// #endif
// #ifdef MP-WEIXIN
let newUrl = res.tempFilePath
// 微信小程序中使用 wx.openDocument
wx.openDocument({
filePath: newUrl, // 文件路径
fileType: 'pdf', // 文件类型
success: function (res) {
console.log('文档打开成功');
},
fail: function (err) {
uni.showToast({
title: '文档打开失败',
icon: 'none',
duration: 3000
});
}
});
// #endif
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none',
})
}
},
fail() {
uni.hideLoading();
uni.showToast({
title: '下载异常',
icon: 'none',
})
}
});
},
// APP预览------------------------------------END----------------------------------------------------
4、注意的问题
1、H5预览出现“Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of”
nginx中加入如下配置
include mime.types;
types
{
application/javascript mjs;
}
2、pdf.js出现跨域问题
可以将 view.js 的以下代码注释掉
if (origin !== viewerOrigin && protocol !== 'blob:') {
throw new Error('file origin does not match viewer\'s');
}
3、pdf.js隐藏不需要的按钮
可以加上如下样式
style="visibility:hidden"
5、后续补充代码(下载进度,loading封装)
1、loading的封装(手机APP中使用uni的loading会导致闪烁,所以分开写)
data() {
return {
loading : null
}
},
methods: {
closeLoading(){
this.loading = null
// #ifdef APP-PLUS
plus.nativeUI.closeWaiting();
// #endif
// #ifdef H5 || MP-WEIXIN
uni.hideLoading();
// #endif
},
showLoading(title){
// #ifdef H5 || MP-WEIXIN
uni.showLoading({
title: title,
mask: true
});
// #endif
// #ifdef APP-PLUS
if(this.loading){
this.loading.setTitle(title)
}else{
this.loading = plus.nativeUI.showWaiting(title);
}
// #endif
},
showToast(title,duration){
if(!duration){
duration = 1500
}
uni.showToast({
title: title,
icon: 'none',
duration: duration
});
},
}
2、下载进度
1、H5 || 微信小程序
uni.downloadFile 增加一个返回参数,以下代码:
const downloadTask = uni.downloadFile(......)
downloadTask.onProgressUpdate((res) => {
this.showLoading('已下载 ' + res.progress + ' %')
});
2、安卓APP
let task = plus.downloader.createDownload(..............)
task.start();
let pre_percent = 0
task.addEventListener('statechanged', function (task, status) {
switch (task.state) {
case 1: // 开始
console.log('开始下载');
break;
case 2: // 已连接到服务器
console.log('已连接到服务器');
break;
case 3: // 正在下载
let percent = parseInt(parseFloat(task.downloadedSize) / parseFloat(task.totalSize) * 100)
if (percent > pre_percent) {
_this.showLoading('已下载 ' + percent + ' %' + " ")
pre_percent = percent
}
console.log(`已下载: ${task.downloadedSize} 总大小: ${task.totalSize}`);
break;
case 4: // 下载完成
console.log('下载完成');
break;
}
});