uniapp实现微信小程序预览和导出PDF

983 阅读4分钟

最近uniapp开发遇到一个问题 在微信小程序里实现预览和导出PDF

首先小程序和浏览器是两个JS开发环境 传统web库是无法正常使用的
要么使用微信小程序内置功能 要么内嵌webview

思路:宏观视角是站在数据处理、数据展示、数据导出角度看问题
  1. 数据处理方面

    后端可以提供pdf链接、pdf接口、图片 base64

    前端可以使用pdf链接 图片 dataurl arraybuffer blob

  2. 数据展示

    微信小程序内置canvas和openDocument

    webview canvas和html

  3. 数据导出

    微信小程序内置saveFileSync和openDocument 跳转外部浏览器或其他app打开

实践:方法很多种自己看着处理
预览
  1. 微信canvas

    如果是链接转接口 如果是接口转成 arraybuffer再转成 base64 canvas上下绘制pdf图片

    uni.request-> uni.arraybuffer2base64 -> canvasContext.drawImage

  2. 微信openDucument

    同时处理调出预览和导出问题 使用微信自带pdf预览器 如需自定义pdf采取别的方式

    (uni.downloadFile|uni.saveFile) ->uni.openDocument

  3. 微信webview

    可使用web传统方案 比如pdfjs jspdf html2canvas vue-pdf react-pdf 转canvas html image dataURL形式渲染

导出
  1. 微信openDocument 右上角分享

  2. 跳转浏览器 本身无法直接跳转 qrcodejs生成链接二维码 文本链接保存剪贴板setClipboardData

  3. 微信saveFile 存储手机文件系统 这个需求估计不会过关

个人实现 -- 其他方式自行探索
  1. 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页面标题和小程序页面标题并存 或者 标题会切换多次 两个页面配置相同标题 标题切换不清楚如何处理