最近uniapp开发遇到一个问题 在微信小程序里实现预览和导出PDF
首先小程序和浏览器是两个JS开发环境 传统web库是无法正常使用的
要么使用微信小程序内置功能 要么内嵌webview
思路:宏观视角是站在数据处理、数据展示、数据导出角度看问题
-
数据处理方面
后端可以提供pdf链接、pdf接口、图片 base64
前端可以使用pdf链接 图片 dataurl arraybuffer blob
-
数据展示
微信小程序内置canvas和openDocument
webview canvas和html
-
数据导出
微信小程序内置saveFileSync和openDocument 跳转外部浏览器或其他app打开
实践:方法很多种自己看着处理
预览
-
微信canvas
如果是链接转接口 如果是接口转成 arraybuffer再转成 base64 canvas上下绘制pdf图片
uni.request-> uni.arraybuffer2base64 -> canvasContext.drawImage
-
微信openDucument
同时处理调出预览和导出问题 使用微信自带pdf预览器 如需自定义pdf采取别的方式
(uni.downloadFile|uni.saveFile) ->uni.openDocument
-
微信webview
可使用web传统方案 比如pdfjs jspdf html2canvas vue-pdf react-pdf 转canvas html image dataURL形式渲染
导出
-
微信openDocument 右上角分享
-
跳转浏览器 本身无法直接跳转 qrcodejs生成链接二维码 文本链接保存剪贴板setClipboardData
-
微信saveFile 存储手机文件系统 这个需求估计不会过关
个人实现 -- 其他方式自行探索
-
pdfjs预览和wx.opendocument形式导出
为了分页和保持清晰度决定pdfjs渲染然后wx.opendocument分享导出
后端提供个pdf接口 content-type application/pdf形式
webview页面 可以是html文件 uniapp项目 其他脚手架项目
这里使用uniapp打包的 自己嵌自己 懒的起个新项目 还能使用uni封装api 即uniapp打包的小程序导入uniapp打包的webview
准备3个page
小程序-page pdf-wrapper pdf-export webview-pdge pdf-preview
pdf-wrapper 负责渲染webview 传递业务参数 小程序与webview父子通信
pdf-preview webview会覆盖全屏 第一次加载预览pdf 小程序与webview父子通信 定制其他业务行为
pdf-export负责导出 第一次加载预览pdf loading wx.opendocument
核心代码如下
<template>
<web-view :src="src" ></web-view>
</template>
```
<script setup>
import {ref,} from 'vue';
import {onLoad,} from '@dcloudio/uni-app'
const src = ref();
onLoad(async(options)=>{
//自行拼接业务参数
const href = 'xxxx'
src.value = href
})
</script>
```pdf-preview.vue
<template>
<uni-load-more status="isLoading" />
<view class="pdf-preview"></view>
<button @click="exportPDF >导出pdf</button>
</template>
<script setup>
import {ref} from "vue"
import {onLoad,} from '@dcloudio/uni-app'
import * as pdfjsLib from 'pdfjs-dist';
//uniapp自带静态资源处理
const workerSrc = `/static/js/pdf.worker-4.9.155.min.js`
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(workerSrc, import.meta.url).href;
const params = ref(null)
const isLoading = ref(true)
onLoad(async (options) => {
params.value = options
//hbuilderx和WXIDE 不支持 Promise.withResolvers API
if (typeof Promise.withResolvers === 'undefined') {
if (window)
// @ts-expect-error This does not exist outside of polyfill which this is doing
window.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
uni.request({
url:"pdf路径",
method: 'GET',
responseType: 'arraybuffer',
header: {},
success: async (res) => {
const url = 'data:application/pdf;base64,' + uni.arrayBufferToBase64(res.data)
const pdf = await pdfjsLib.getDocument(url).promise
const container = document.querySelector(".pdf-preview")
const containerWidth = container.clientWidth;
new Array(pdf.numPages).fill(1).forEach(()=>{
pdf.getPage(1).then((page) => {
//适配pdf宽高
const viewport = page.getViewport({ scale: 1 });
const scale = containerWidth / viewport.width;
const scaledViewport = page.getViewport({ scale, rotation: 0 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
const renderContext = {
canvasContext: context,
viewport: scaledViewport
};
page.render(renderContext);
container.appendChild(canvas);
isLoading.value=false
})
})
},
});
})
const exportPDF = ()=>{
//通知小程序跳转
webUni.navigateTo({url: "/pages/inner/pdf-export/pdf-export"+"?xxx"})
}
</script>
<template>
//loading 懒的写 主要是进入微信预览页面
</tempalte>
<script>
import {ref,} from 'vue';
import {onLoad,} from '@dcloudio/uni-app'
const params = ref(null)
onLoad(async(options)=>{
params.value = options
downloadPDF()
})
const downloadPDF = ()=>{
uni.showLoading({
title:"导出PDF中...",
mask:true,
})
const {type,report_no} = params.value
uni.downloadFile({
url:"pdf地址",
header: {},
success: function (res) {
uni.hideLoading({
title:"导出PDF中...",
mask:true,
})
const filePath = res.tempFilePath;
uni.openDocument({
filePath,
fileType: 'pdf',
showMenu: true,
success: function (res) {
uni.navigateBack()
},
});
}
});
}
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
//微信小程序与webview通信
<script type="text/javascript" src="/static/js/jweixin-1.4.0.min.js" ></script>
<script type="text/javascript" src="/static/js/uni.webview-1.5.6.min.js" ></script>
//方便手机端调试
<script type="text/javascript" src="/static/js/vconsole-3.15.1.min.js" ></script>
<script>
const vConsole = new VConsole();
</script>
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
踩坑
-
pdfjs报错
不支持Promise.withResolvers
直接添加 if (typeof Promise.withResolvers === 'undefined') { if (window) // @ts-expect-error This does not exist outside of polyfill which this is doing window.Promise.withResolvers = function () { let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; } 对pdfjs 语法降级 vite打包过程中使用babel swc 工具 降低pdfjs版本至2.x
动态导入报错 微信IDE和hbuilderx内置浏览器 版本低 直接手机webview真机调试渲染功能 降级pdfjs版本至2.x
-
uni通信报错
jweixin uni.webview 必须在index.js加载 uni.webview文件和 uniapp内置uni命名冲突 存在小程序uni h5 uni webview uni 3个全局对象 修改uni.webview里面uni 改成 webUni
!function (e, n) { "object" == typeof exports && "undefined" != typeof module ? module.exports = n() : "function" == typeof define && define.amd ? define(n) : (e = e || self).webUni = n() } -
静态资源正式打包加载不一致
如果h5公共路径 会导致jweixin和uni.webview加载不出来
测试需要 /static/js/jweixin-1.4.0.min.js uniapp会自动拼接 打包需要 base_path/static/js/jweixin-1.4.0.min.js
-
标题异常
webview页面标题和小程序页面标题并存 或者 标题会切换多次 两个页面配置相同标题 标题切换不清楚如何处理