html5plus 是 vuejs 原生拓展插件,其具体特性可以在官方文档中查询。
以调用 camera 摄像头以及 io 读取 pdf 文件展示 html5+原生能力
IO 流
IO 模块管理本地文件系统,用于对文件系统的目录浏览、文件的读取、文件的写入等操作。通过 plus.io 可获取文件系统管理对象。
实现方法为 plus.io -> document -> 使用 pdf 插件在线预览、缩放和翻页/使用第三方应用(WPS\office)打开
初始化
安装库
cnpm i vue-awesome-mui -S
引入
1
| import Mui from 'vue-awesome-mui' Vue.config.productionTip = false Vue.use(Mui)
|
plus.openFile
调用第三方程序打开 pdf 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <script> openFile() { let files = []; // 获取目标路径下的所有文件以及子目录,此处以PRIVATE_WWW为例 plus.io.requestFileSystem(plus.io.PRIVATE_WWW,entry=>{ // 同理删除操作为 plus.io.resolveLocalFileSystemURL let directoryReader = entry.root.createReader(); directoryReader.readEntries(fs=>{ for(let i=0;i<fs.length;i++){ if(fs[i].isFile){ files.push({ name:fs[i].name, fullPath:fs[i].fullPath, url:fs[i].toURL() }) } } plus.runtime.openFile(files[0].url); },e=>{ Dialog({ message: '调用失败: ' + e.message }) }) },e=>{ Dialog({ message: '调用失败: ' + e.message }) }) } </script>
|
使用第三方成熟可靠的应用,可以大大节省我们的开发的时间,不过通常我们会遇到 ios 手机不能下载文件的问题,那是因为苹果手机的拦截机制,这时我们只能通过别的方法来解决问题。
vue-pdf 插件
官方文档
初始化
npm install --save vue-pdf
引入
import pdf from ‘vue-pdf’
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
| <template> <div class="pdf"> <div class="pdf-tab"> <div class="btn-def btn-pre" @click.stop="prePage">上一页</div> <div class="btn-def btn-next" @click.stop="nextPage">下一页</div> </div> <div>{{ pageNum }}/{{ pageTotalNum }}</div> <pdf id="pdfPreview" ref="pdf" :src="pdfUrl" :page="pageNum" :rotate="pageRotate" @progress="loadedRatio = $event" @page-loaded="pageLoaded($event)" @num-pages="pageTotalNum = $event" @error="pdfError($event)" @link-clicked="page = $event" > </pdf> </div> </template>
<script> import pdf from "vue-pdf";
export default { name: "demo1", data() { return { leader_code: this.$route.query.act, pdfUrl: "", // pdf文件地址 pageNum: 1, pageRotate: 0, loadedRatio: 0, curPageNum: 0, }; }, components: { pdf, }, created() { // 扩展API是否准备好,监听“plusready"事件 if (window.plus) { this.plusReady(); } else { document.addEventListener("plusready", this.plusReady, false); } }, mounted() { this.getFile(); }, methods: { onClickLeft() { this.$router.go(-1); }, // 扩展API准备完成后要执行的操作 plusReady() { var ws = plus.webview.currentWebview(); // 可输出plus.webview console.log("plus is ready"); }, getFile() { let files = []; plus.io.requestFileSystem( plus.io.PRIVATE_DOC, (entry) => { let directoryReader = entry.root.createReader(); directoryReader.readEntries( (fs) => { for (let i = 0; i < fs.length; i++) { if (fs[i].isFile) { files.push({ name: fs[i].name, fullPath: fs[i].fullPath, url: fs[i].toURL(), }); } } this.pdfUrl = pdf.createLoadingTask(files[0].fullPath); }, (e) => { Dialog({ message: "调用失败: " + e.message }); } ); }, (e) => { Dialog({ message: "调用失败: " + e.message }); } ); }, pageLoaded(e) { this.curPageNum = e; }, prePage() { let p = this.pageNum; p = p > 1 ? p - 1 : this.pageTotalNum; this.pageNum = p; }, nextPage() { let p = this.pageNum; p = p < this.pageTotalNum ? p + 1 : 1; this.pageNum = p; }, pdfError(error) { console.log(error); }, }, }; </script>
<style scoped> .pdf-tab { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; padding: 0 0.4rem; -ms-flex-pack: justify; justify-content: space-between; }
.pdf-tab .btn-def { border-radius: 0.2rem; font-size: 0.98rem; height: 1.93333rem; width: 6.4rem; text-align: center; line-height: 1.93333rem; background: #409eff; color: #fff; margin-bottom: 1.26667rem; } </style>
|
进一步可以改造升级,使用AlloyFinger 手势库,让 pdf 实现手势缩放和移动
1 2
| import AlloyFinger from 'alloyfinger' import AlloyFingerPlugin from 'alloyfinger/vue/alloy_finger_vue' Vue.use(AlloyFingerPlugin,{ AlloyFinger })
|
单页配置,具体查看官方 demo详情
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <div class="any-scroll-view"> <div ref="body" :style="bodyStyle"> <!-- 滚动区域 --> </div> </div> </template> ... ... ... <script> import AlloyFinger from "@/assets/js/alloyfinger.js"; import transform from "@/assets/js/transform.js"; import To from "@/assets/js/to.js"; </script>
|
pdfjs 插件
如果说 vue-pdf 比较繁杂,不能滚动翻页,只能手动触发翻页,可以考虑由火狐浏览器的 pdfjs
它的优点体现在,更好的跨域解决方案,自带手势库
缺点,经常更新,容易不兼容
官方文档 1
官方文档 2
初始化
<font style="color:rgb(64, 64, 64);">npm install pdfjs-dist --save</font>
这里安装版本号为 v2.7.570
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
| <template> <div class="container"> <div class="pdf_wrap"> <div class="pdf_down"> <div class="pdf_set_left" @click="scaleD()">放大</div> <div class="pdf_set_middle" @click="scaleX()">缩小</div> </div> <div :style="{ width: pdf_div_width, margin: '0 auto' }"> <canvas v-for="page in pdf_pages" :id="'the-canvas' + page" :key="page" ></canvas> </div> </div> </div> </template>
<script> import PDFJS from "pdfjs-dist"; PDFJS.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.js";
export default { name: "demo2", data() { return { leader_code: this.$route.query.act, pdf_scale: 1.0, //放大系数 pdf_pages: [], pdf_div_width: "", pdf_src: null, }; }, created() { if (window.plus) { this.plusReady(); } else { document.addEventListener("plusready", this.plusReady, false); } }, mounted() { this.getFile(); }, methods: { onClickLeft() { this.$router.go(-1); }, plusReady() { var ws = plus.webview.currentWebview(); console.log("hello plus"); }, getFile() { let files = []; plus.io.requestFileSystem( plus.io.PRIVATE_WWW, (entry) => { let directoryReader = entry.root.createReader(); directoryReader.readEntries( (fs) => { for (let i = 0; i < fs.length; i++) { if (fs[i].isFile) { files.push({ name: fs[i].name, fullPath: fs[i].fullPath, url: fs[i].toURL(), }); } } this.pdf_src = files[0].fullPath; this._loadFile(this.pdf_src); }, (e) => { Dialog({ message: "调用失败: " + e.message }); } ); }, (e) => { Dialog({ message: "调用失败: " + e.message }); } ); }, scaleD() { //放大 let max = 0; if (window.screen.width > 1440) { max = 1.4; } else { max = 1.2; } if (this.pdf_scale >= max) { return; } this.pdf_scale = this.pdf_scale + 0.1; this._loadFile(this.pdf_src); }, scaleX() { //缩小 let min = 1.0; if (this.pdf_scale <= min) { return; } this.pdf_scale = this.pdf_scale - 0.1; this._loadFile(this.pdf_src); }, _loadFile(url) { //初始化pdf let loadingTask = PDFJS.getDocument(url); loadingTask.promise.then((pdf) => { this.pdfDoc = pdf; this.pdf_pages = this.pdfDoc.numPages; //debugger this.$nextTick(() => { this._renderPage(1); }); }); }, _renderPage(num) { //渲染pdf页 const that = this; this.pdfDoc.getPage(num).then((page) => { let canvas = document.getElementById("the_canvas" + num); let ctx = canvas.getContext("2d"); let dpr = window.devicePixelRatio || 1; let bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; let ratio = dpr / bsr; let viewport = page.getViewport({ scale: this.pdf_scale }); canvas.width = viewport.width * ratio; canvas.height = viewport.height * ratio; canvas.style.width = viewport.width + "px"; that.pdf_div_width = viewport.width + "px"; canvas.style.height = viewport.height + "px"; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); let renderContext = { canvasContext: ctx, viewport: viewport, }; page.render(renderContext); if (this.pdf_pages > num) { this._renderPage(num + 1); } }); }, }, }; </script>
<style lang="scss" scoped> .pdf_wrap { width: 100%; height: 100%; .pdf_down { position: fixed; display: flex; z-index: 20; right: 26px; bottom: 7%; .pdf_set_left { width: 30px; height: 40px; color: #408fff; font-size: 11px; padding-top: 25px; text-align: center; margin-right: 5px; cursor: pointer; } .pdf_set_middle { width: 30px; height: 40px; color: #408fff; font-size: 11px; padding-top: 25px; text-align: center; margin-right: 5px; cursor: pointer; } } } </style>
|
可以观察到,其实pdfjs 是把 pdf 文件渲染成 canvas,所以 vue 加载完 dom 后,要用 nexttick 调用渲染 pdf 函数,值得注意的是 vue-pdf 和 pdfjs 同时 import 会引起冲突问题。
pdfh5 插件
其优点体现在:
1.需要展示的 pdf 文件内容需要每一页上下排列;
2.需要展示用户跟第三方进行电子签名后的合同时,vue-pdf 会屏蔽签名图层,导致合同缺失第三方的电子签名;
3.需要更加轻量化
官方文档
初始化
<font style="color:rgb(77, 77, 77);">cnpm install pdfh5</font>
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| <template> <div class="container"> <div class="outBox"> <div id="demo"></div> </div> </div> </template>
<script> import { Dialog } from "vant"; import Pdfh5 from "pdfh5"; import "pdfh5/css/pdfh5.css";
export default { name: "demo3", data() { return { leader_code: this.$route.query.act, pdfh5: null, }; }, beforeRouteLeave(to, from, next) { to.meta.keepAlive = true; next(); }, created() { if (window.plus) { this.plusReady(); } else { document.addEventListener("plusready", this.plusReady, false); } }, mounted() { this.getFile(); }, methods: { onClickLeft() { this.$router.go(-1); }, plusReady() { var ws = plus.webview.currentWebview(); console.log("hello plus"); }, getFile() { let files = []; plus.io.requestFileSystem( plus.io.PRIVATE_DOC, (entry) => { let directoryReader = entry.root.createReader(); directoryReader.readEntries( (fs) => { for (let i = 0; i < fs.length; i++) { if (fs[i].isFile) { files.push({ name: fs[i].name, fullPath: fs[i].fullPath, url: fs[i].toURL(), }); } } this.pdfh5 = new Pdfh5("#demo", { // 加载进度条 loadingBar: false, pdfurl: files[0].fullPath, }); //监听完成事件 this.pdfh5.on("complete", function (status, msg, time) { console.log( "状态:" + status + ",信息:" + msg + ",耗时:" + time ); }); //监听失败事件 this.pdfh5.on("error", function () { Dialog({ message: "查无文档" }); }); }, (e) => { //失败操作 Dialog({ message: "调用失败: " + e.message }); } ); }, (e) => { Dialog({ message: "调用失败: " + e.message }); } ); }, }, }; </script>
<style> * { padding: 0; margin: 0; }
.outBox { margin-top: 3.5rem; touch-action: none; width: 100%; height: 100%; } </style>
|
打包
npm run build
后使用 Hbuilder 打包应用即完成。
Camera 流
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <ul class="mui-table-view"> <li class="mui-table-view-cell" id="device.html" @click="getImage"> <a class="mui-navigate-right"> 调用拍照 </a> </li> </ul> ... ... ... <script> export default { name: "demo4", methods: { plusReady() { var ws = plus.webview.currentWebview(); console.log("hello plus"); }, getImage() { let cmr = plus.camera.getCamera(); // 获取摄像头对象 let res = cmr.supportedImageResolutions[0]; // 字符串数组,摄像头支持的拍照分辨率 let fmt = cmr.supportedImageFormats[0]; // 字符串数组,摄像头支持的拍照文件格式 console.log("Resolution :" + res + ", Format: " + fmt); cmr.captureImage( (path) => { alert("调用成功: " + path); }, (error) => { // 拍照操作失败的回调函数 alert("调用失败: " + error.message); }, { resolution: res, format: fmt } ); // 摄像头拍照参数 }, }, created() { // 扩展API是否准备好,如果没有则监听“plusready"事件 if (window.plus) { this.plusReady(); } else { document.addEventListener("plusready", this.plusReady, false); } }, }; </script>
|
尔后npm run build
将 dist 丢进 hbuilder 打包 App 即可。
注意事项
1、调试报错
plus is not defined
只有 HBuilder 真机运行、打包后、或流应用环境下才能运行 plus api。
在普通浏览器里运行时 plus api 时控制台必然会输出 plus is not defined 错误提示
2、出现白屏
将 Vue 项目中 config 文件夹里的 index.js 文件中 assetsPublicPath 由’/‘改为 ‘./’ 同时删除路由模式或者改为 hash
3、缩放卡顿或无法缩放
检查是否有诸如引入 zepto.js 引起的冲突
4、文件打印乱码
确认 pdf 是否用了第三方库字体
5、引入文件后 pdf 不显示
需要逐一排查,一般地是属于路径问题,观察报错信息,如果 error 为 pdf.js annotationstorage: annotationstorage?.getall() 一般是 pdf 文档存在图章引起的问题。
6、如何使用 blob 文件流传输
vue-pdf/pdfh5 均可以实现 blob 文件对接
参考文档
html5plus 规范
vue 使用 vue-pdf 实现 pdf 在线预览
Vue PDF 文件预览 vue-pdf
2021 pdfjs 在 vue 中的使用
vue 项目 pdf 预览插件 pdfh5
vue 项目文件流数据格式转 blob 图片预览展示