初尝vue+html5plus+hbuilder

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 图片预览展示

作者

Catooilg

发布于

2021-04-16

更新于

2025-02-10

许可协议

评论