uniapp编写微信小程序,实现在线直接预览PDF文件
需求:后端提供PDF的URL地址,通过webview实现在微信小程序中不分页直接预览PDF文件
一、问题:
客户提供的pdf文件过大18MB,并且有600多页,小程序页面出现白页及等待时间过久等问题,并报以下错误:

二、解决方法:
1.安装插件
//vue2,对应以下版本
npm install vue-pdf@4.2.0
npm install pdfjs-dist@2.5.207
2.实现代码:
<template>
<div>
<pdf v-for="i in numPages" :key="i" :src="pdfUrl" :page="i" style=" width: 100%">
</pdf>
<van-loading v-if="showLoading" class="loading" size="24px" type="spinner" vertical>加载中...</van-loading>
</div>
</template>
<script>
//引入pdf插件
import pdf from 'vue-pdf'
export default {
components:{ pdf },
data() {
return {
//pdf-url地址
pdfFile: '',
// pdf组件中写的地址
pdfUrl: '',
//全部页码
numPages: undefined,
// 是否显示loading
showLoading: true,
}
},
mounted () {
this.getTitlePdfurl ();
},
methods: {
//pdf全页预览
getTitlePdfurl () {
//通过设置定时器动态渲染PDF页面的方式成功规避页面加载时间过长及空白情况。
//当用户点击查看多个PDF时,会先清空当前PDF信息,然后加载新的PDF文件,并利用定时器逐页加载,避免了错误发生。
this.numPages = null
let newNumb = null
this.pdfUrl = pdf.createLoadingTask(this.pdfFile); //this.pdfFile是接口获取的pdf URL地址
this.pdfUrl.promise.then((pdf) => {
newNumb = pdf.numPages
this.showLoading = false
// 用个定时器解决报错Rendering cancelled
let timer = setInterval(() => {
this.numPages += 1
if (this.numPages === newNumb) {
clearInterval(timer)
}
}, 500)
},
},
};
</script>
3.完整代码:
通过点击小程序的列表页,跳转至webview页,将webview中的数据带至本页,通过分类(collectType)判断类别,并通过fileType(文件类型)来判断显示PDF或者DOCX
<template>
<div class="lawDet">
<div class="lawDet-hd mlr1">
<!-- 法律法规 -->
<div v-if="detailedInfo.collectType==='2'">
<div class="title mb1 f18">{{detailedInfo.lawName}}</div>
<div class="mgs f12">
<div>颁布单位:{{ detailedInfo.formulatingAuthority }}</div>
<div>法律性质:{{ legalNature[detailedInfo.legalNature]}}</div>
<div>颁布日期:{{ detailedInfo.releaseDate }}</div>
</div>
</div>
<!-- 审批手续 -->
<div v-if="detailedInfo.collectType==='3'">
<div class="title mb1 f18">{{detailedInfo.examineName}}</div>
<div class="mgs f12">
<div>创建时间:{{ detailedInfo.createTime }}</div>
<div class="info">
<span class="minTit">内容简述:</span>
<span class="minCon ell2">{{ detailedInfo.examineSketch?detailedInfo.examineSketch:'无' }}</span>
</div>
</div>
</div>
<!-- 制度建设 -->
<div v-if="detailedInfo.collectType==='5'">
<div class="title mb1 f18">{{detailedInfo.institutionalName}}</div>
<div class="mgs f12" v-if="detailedInfo.institutionalSketch">
<div>创建时间:{{ detailedInfo.createTime }}</div>
<div>内容简述:{{ detailedInfo.institutionalSketch?detailedInfo.institutionalSketch:'无' }}</div>
</div>
</div>
<!-- 教育培训 -->
<div v-if="detailedInfo.collectType==='4'">
<div class="title mb1 f18">{{detailedInfo.educationName}}</div>
<div class="mgs f12">
<div>创建时间:{{ detailedInfo.createTime }}</div>
<div>所属分类:{{ detailedInfo.educationTypeName }}</div>
<div class="info">
<span class="minTit">内容简述:</span>
<span class="minCon ell2">{{ detailedInfo.educationSketch?detailedInfo.educationSketch:'无' }}</span>
</div>
</div>
</div>
</div>
<!-- PDF区 -->
<div v-if="fileType==='pdf'" class="pdfArea mlr1">
<div class="pdf-con" :style="{height: fileHeight + 'px',}">
<pdf v-for="i in numPages" :key="i" :src="pdfUrl" :page="i" style=" width: 100%">
</pdf>
<van-loading v-if="showLoading" class="loading" size="24px" type="spinner" vertical>加载中...</van-loading>
</div>
</div>
<!-- docx区 -->
<div v-if="fileType==='docx'" class="docxArea" :style="{height: fileHeight + 'px',}">
<div ref="file" id="docxCon"></div>
</div>
</div>
</template>
<script>
import axios from "axios";// 引入axios用来发请求
import pdf from 'vue-pdf'
let docx = require("docx-preview");
export default {
components: {
pdf
},
data () {
return {
detailedInfo: {},
docxOptions: {
className: "gwp-docx", // string:默认和文档样式类的类名/前缀
inWrapper: false, // boolean:启用围绕文档内容的包装器渲染
ignoreWidth: true, // boolean:禁用页面的渲染宽度
ignoreHeight: false, // boolean:禁止渲染页面高度
ignoreFonts: false, // boolean:禁用字体渲染
breakPages: false, // boolean:在分页符上启用分页
ignoreLastRenderedPageBreak: true, // boolean:在 lastRenderedPageBreak 元素上禁用分页
experimental: false, // boolean:启用实验功能(制表符停止计算)
trimXmlDeclaration: true, // boolean:如果为true,解析前会从 xml 文档中移除 xml 声明
useBase64URL: false, // boolean:如果为true,图片、字体等会转为base 64 URL,否则使用URL.createObjectURL
useMathMLPolyfill: false, // boolean:包括用于 chrome、edge 等的 MathML polyfill。
showChanges: false, // boolean:启用文档更改的实验性渲染(插入/删除)
debug: false, // boolean:启用额外的日志记录
},
drawer: false,
//文档类型
fileType: '',
//pdf
pdfUrl: '',
//全部页码
numPages: undefined,
// 是否显示loading
showLoading: true,
// 定时器
timeoutId: null,
//word
wordUrl: '',
//ppt
pptUrl: '',
//文件区域高度
fileHeight: '',
//缩放值
scale: 0.8,
//法律性质
legalNature: {
1: '法律',
2: '行政法规',
3: '地方性法规',
},
}
},
created () {
document.title = '详细页'
this.getWxLawDetails()
},
mounted () {
this.getHeight();
},
methods: {
//获取微信小程序传来的多参数
getWxLawDetails () {
let wxDetaile = this.$route.query.detailedInfo //微信小程序带过来的数据
this.detailedInfo = JSON.parse(wxDetaile)
//微信小程序带过来的数据
//this.detailedInfo = { "createUser": "01j6e1h1ymdh44rmy5z6bzye5g", "createTime": "2024-09-09 22:08:23", "updateUser": "01j6e1h1ymdh44rmy5z6bzye5g", "verificationUuid": null, "id": "01j7bhtf4ffted8f9m7wwkyaxn", "lawName": "建筑设计防火规范(GB 50016-2014,2018年版)", "oldFileName": "建筑设计防火规范GB 50016-2014(2018).pdf", "newFileName": "law-20240909220805-1725890885785.pdf", "fileType": "pdf", "isRelease": "1", "lawTypeId": "01j6b7tqemame2bbxwbqaa8es5", "filePath": "https://private-storage.anzhengkeji.com.cn/law-20240909220805-1725890885785.pdf?e=1725947890&token=4jQMeYuZ571Jg3rWO-rZheYiqZ790m1Rq41sq7WP:F4oIJWjVzIi9YVLs_MAEuyO7QoU=", "isCollect": "2", "formulatingAuthority": "中华人民共和国住房和城乡建设部", "legalNature": "1", "timeliness": "2", }
this.pptUrl = this.detailedInfo.filePath
this.fileType = this.detailedInfo.fileType
const fileUrl = decodeURIComponent(this.detailedInfo.filePath)
if (this.fileType === 'pdf') {
let pdfFile = fileUrl
this.getTitlePdfurl(pdfFile)
} else if (this.fileType === 'docx') {
this.wordUrl = fileUrl
this.goPreview()
}
},
//预览docx
goPreview () {
axios({
responseType: "blob", // 因为是流文件,所以要指定blob类型
url: this.wordUrl,
}).then(({ data }) => {
let docxCon = document.getElementById("docxCon");
docx.renderAsync(data, docxCon, null, this.docxOptions); // 渲染到页面
docxCon.style.height = this.fileHeight / 0.5 + 'px'
//docx.renderAsync(data, this.$refs.file); // 直接渲染-----渲染到页面
});
},
//DOCX区域高度
getHeight () {
var divHeight = document.querySelector('.lawDet-hd').offsetHeight;
this.fileHeight = window.innerHeight - divHeight - 60
},
//pdf全页预览
getTitlePdfurl (pdfFile) {
//通过设置定时器动态渲染PDF页面的方式成功规避页面加载时间过长及空白情况。
//当用户点击查看多个PDF时,会先清空当前PDF信息,然后加载新的PDF文件,并利用定时器逐页加载,避免了错误发生。
this.numPages = null
let newNumb = null
this.pdfUrl = pdf.createLoadingTask(pdfFile);
this.pdfUrl.promise.then((pdf) => {
newNumb = pdf.numPages
this.showLoading = false
// 用个定时器解决报错Rendering cancelled
let timer = setInterval(() => {
this.numPages += 1
if (this.numPages === newNumb) {
clearInterval(timer)
}
}, 500)
})
},
},
};
</script>
<style lang='scss' scoped>
.lawDet {
margin-top: 12px;
.lawDet-hd {
.title {
line-height: 24px;
}
.mgs {
background-color: #f7f7f7;
border-radius: 4px;
padding: 6px 10px;
color: #666;
margin-bottom: 16px;
line-height: 20px;
position: relative;
.collection {
position: absolute;
right: 12px;
top: 37%;
}
}
.info {
display: flex;
align-items: baseline;
.minTit {
width: 60px;
flex-shrink: 0;
}
.minCon {
width: 256px;
line-height: 16px;
height: 34px;
}
}
}
.pdf-pagination {
display: flex;
justify-content: space-between;
.btn-pre,
.btn-next,
.pageNum {
background-color: #f4f4f5;
padding: 6px 14px;
}
}
.pdf-con {
border: 2px solid #f4f4f5;
margin-top: 12px;
height: 550px;
position: relative;
overflow-y: auto;
.loading {
position: absolute;
top: 40%;
left: 50%;
z-index: 10000;
margin-left: -26px;
}
}
.docxArea {
border: 2px solid #f4f4f5;
margin: 16px;
overflow: hidden;
padding-top: 10px;
}
}
</style>
4.uniapp微信小程序webview页面完整代码:
webview页面中使用cover-view实现悬浮收藏及下载功能
<template>
<view>
<web-view :src="src()">
<cover-view class="collect" v-if="isButtonShow">
<!-- 收藏 -->
<cover-view class="btn-collect" :class="{btnActive:isCollect=='1'}" @click="onCollect">
<cover-image v-show="isCollect==='1'" class="star" src="@/static/img/follow-fill1.png" />
<cover-image v-show="isCollect==='2'" class="star" src="@/static/img/follow1.png" />
<cover-view class="conllectTxt">{{isCollect==='1'?'已收藏':'收藏'}}</cover-view>
</cover-view>
<!-- 下载 -->
<cover-view class="btn-collect" @click="onDownload">
<cover-image class="ico-download" src="@/static/img/ico-download.png" />
<cover-view class="conllectTxt">下载</cover-view>
</cover-view>
</cover-view>
</web-view>
</view>
</template>
<script>
import {
getReportUserInfo,
getSysFileInfo
} from '@/api/login/login.js'
import {
getExamineContentInfo
} from '@/api/detection/index.js'
import {
getLawContentInfo,
editUserCollectInfo
} from '@/api/law/index.js'
import {
getInstitutionalContentInfo,
} from '@/api/institution/index.js'
import {
getEducationContentInfo,
} from '@/api/education.js' //教育培训接口
export default {
data() {
return {
pageUrl: 'https://*****gkeji.com.cn/', //线上地址
//列表带过来的咨询id
id: '',
//通过类型区分不同详细页
classify: '',
onBack: '',
//详细
detailedInfo: {},
//用于判断当前用户是否手机号验证
isPhoneLogin: '',
isCollect: '', //1 收藏 2 取消
isButtonShow: false,
token: '',
}
},
onLoad(e) {
this.id = e.id;
this.classify = e.classify;
this.getDetailed();
},
onShow() {
this.getPhoneLogin();
},
mounted() {
setTimeout(() => {
this.isButtonShow = true;
}, 1500); // 1.5后显示按钮
},
methods: {
//获取数据
getDetailed() {
//1行业资讯 2法律法规 3审批手续 4教育培训 5制度建设 6应急资源
if (this.classify === '2') {
this.getLawContentInfos()
} else if (this.classify === '3') {
this.getExamineContentInfos()
} else if (this.classify === '5') {
this.getInstitutional()
} else if (this.classify === '4') {
this.getEducationInfo()
}
},
//判断用户是否授权手机号
getPhoneLogin() {
getReportUserInfo().then(res => {
let phone = res.data.phone
if (phone != null) {
this.isPhoneLogin = true
} else {
this.isPhoneLogin = false
}
})
},
// 教育培训
async getEducationInfo() {
const {
data
} = await getEducationContentInfo({
id: this.id
})
this.detailedInfo = data
this.isCollect = data.isCollect //收藏
this.detailedInfo.isPhoneLogin = this.isPhoneLogin //是否登录
},
// 获取详细信息--制度建设
getInstitutional() {
const data = {
id: this.id,
}
getInstitutionalContentInfo(data).then(res => {
this.detailedInfo = res.data
this.isCollect = res.data.isCollect //收藏
this.detailedInfo.isPhoneLogin = this.isPhoneLogin //是否登录
this.detailedInfo.token = uni.getStorageSync('token') //token
})
},
// 获取详细信息--审批手续
getExamineContentInfos() {
const data = {
id: this.id,
}
getExamineContentInfo(data).then(res => {
this.detailedInfo = res.data
this.isCollect = res.data.isCollect //收藏
this.detailedInfo.isPhoneLogin = this.isPhoneLogin //是否登录
this.detailedInfo.token = uni.getStorageSync('token') //token
})
},
// 获取详细信息--法律法规
getLawContentInfos() {
const data = {
id: this.id,
}
getLawContentInfo(data).then(res => {
this.detailedInfo = res.data
this.isCollect = res.data.isCollect //收藏
this.detailedInfo.isPhoneLogin = this.isPhoneLogin //是否登录
this.detailedInfo.token = uni.getStorageSync('token') //token
})
},
//登录成功后回调的收藏方法
onLoginCollect(e) {
this.detailedInfo.onBack = e
this.editUserCollectInfos();
},
//点击下载
onDownload() {
if (this.isPhoneLogin === true) {
this.downloadMethod();
} else {
let isDownloadDet = 1
uni.navigateTo({
url: '/pages/login/login?isDownloadDet=' + isDownloadDet
})
}
},
//下载方法
downloadMethod() {
uni.showLoading({
title: '正在下载...'
});
const data = {
contentId: this.id,
fileType: this.detailedInfo.collectType,
}
getSysFileInfo(data).then(res => {
uni.hideLoading()
uni.showLoading({
title: '下载并打开文档...'
});
let downloadUrl = res.data.filePath
uni.downloadFile({
url: downloadUrl, // 文件的本身url
success: (data) => {
if (data.statusCode === 200) {
//文件保存到本地
uni.getFileSystemManager().saveFile({
tempFilePath: data.tempFilePath, //临时路径
filePath: uni.env.USER_DATA_PATH + '/' + this.detailedInfo
.oldFileName, //自定义文件名
success: function(res) {
setTimeout(() => {
//打开文档查看
uni.openDocument({
filePath: res
.savedFilePath,
showMenu: true, //是否可以分享
success: function(res) {
uni.hideLoading()
},
fail: (e) => {
uni.showToast({
title: '打开失败',
icon: "error"
})
}
});
}, 3000)
}
});
}
},
fail: (err) => {
uni.showToast({
icon: 'none',
mask: true,
title: '失败请重新下载',
});
},
});
}).catch(err => {
uni.hideLoading()
})
},
//收藏方法
onCollect() {
if (this.isPhoneLogin === true) {
this.editUserCollectInfos();
} else {
let isConllectDet = 1
uni.navigateTo({
url: '/pages/login/login?isConllectDet=' + isConllectDet
})
}
},
//收藏接口
editUserCollectInfos() {
let data = {
collectType: this.detailedInfo.collectType,
collectContentId: this.id,
doType: this.isCollect === "2" ? '1' : '2'
}
editUserCollectInfo(data).then(res => {
uni.showToast({
title: res.msg,
icon: 'success',
duration: 500,
});
this.isCollect = res.data.doType
})
},
src() {
return `${this.pageUrl}?detailedInfo=${encodeURIComponent(JSON.stringify(this.detailedInfo))}`
},
}
}
</script>
<style scoped lang="scss">
.collect {
position: fixed;
right: 40rpx;
z-index: 999;
bottom: 20%;
.btn-collect {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 200rpx;
color: #222;
background-color: #fff;
border: 1px solid #d6d6d6;
width: 116rpx;
font-size: 22rpx;
height: 116rpx;
opacity: 0.8;
margin-bottom: 20rpx;
}
.btnActive {
background-color: #3278fc;
border-color: #3278fc;
color: #fff;
}
.conllectTxt {
margin-top: 2px;
letter-spacing: 1px;
}
.star,
.ico-download {
width: 50rpx;
height: 45rpx;
z-index: 99999;
}
}
</style>
8019

被折叠的 条评论
为什么被折叠?



