项目总结-认证平台
遇到的问题及解决
PDF展示
怎样实现
pdf-vue
使用很简单,直接看教程就可以了,但在使用的过程中遇到了几个问题:
1:pdf存在多页的加载
<pdf-vue
v-for="i in pageCount"
:key="i"
:src="src"
:page="i"
style="display: inline-block; width: 25%"
>
</pdf>
通过for循环来加载多页,虽然可以解决多页展示的问题,但是如果你的pdf存N页的话,就会发送N个请求,等所有请求结束后才会展示pdf内容,网速不好、pdf过大….都会导致加载慢,用户体验不好。其实并不是一开始就加载所有的pdf,看多少就加载多少,于是就有了下面的代码
<pdf-vue
v-for="i in copyPageCount"
:key="i"
:page="i"
:src="src"
style="display: inline-block; width: 25%"
>
</pdf-vue>
就改了一个变量就这么神奇吗?当然不是,你以为我就只?少侠看下面
listenScroll () {
this.dialogBody = document.querySelector('.dialog-confirm .el-dialog__wrapper .el-dialog .el-dialog__body')
this.dialogBody.scrollTop = 0
if (this.dialogBody) {
this.dialogBody.addEventListener('scroll', () => {
if (!this.loading && (this.dialogBody.scrollHeight - this.dialogBody.scrollTop - this.dialogBody.clientHeight < 10)) {
this.loading = true
this.copyPageCount = this.copyPageCount + 1 > this.pageCount ? this.pageCount : this.copyPageCount + 1
}
})
}
},
原理是这样的,copyPageCount
是一个假象,它并不是所有的pdf页数,初始值为1,当用户向下查看的是的,由于监听了滚动,会自动增加copyPageCount
的值,copyPageCount
变化了,pdf也会相应的加载。首次加载只加载了第一页,速度就快多了。
2:IE上的兼容
当在IE上打开页面的时候,发现是白屏,并且报错语法不兼容(来自pdfjs-dist),虽然在vue.config.js
里面加了 transpileDependencies: ['pdfjs-dist'],
,虽然可以正常的打开页面,但是不能正常显示pdf,而且打包变得巨慢,所以需要在IE上使用的话,建议放弃。
说了这么多坏话,我们来夸一下它:
1:使用方便(相对于pdfjs)
2:展示效果更好,页面的缩放里面的内容也会相应的变化
pdfjs
在使用之前最好看一下在线的demo看看能不能满足你的需求,首先需要安装:npm install pdfjs-dist
,如果有莫名其奥妙的报错,建议安装不一样的版本在尝试(2.2.228 or 2.3.200)。使用的话,需要先建一个pdf.vue
的文件封装
在需要展示pdf的地方引用就好。但在使用的过程中还是遇到的问题,上图:
两侧的留白太多,不美观。机智的我在官方的demo上看到了这个:
如果选择适合页宽不就完美了吗?于是去找了下源码,发现了惊天大秘密,其实就是改变scale
缩放来实现需要的效果。
效果如下:
但还是存在些许优化,浏览器缩放的时候不能实现内容的缩放,相应的方法是监听窗口变化,改变scale
值,相信你一定会写~。pdf.js
相对pdf-vue
在多页pdf上使用比较友好,不需要自己额外写方法实现,IE上也可以使用,综合体验更胜一筹。
跨域
如果存在pdf跨域展示的问题,需要后端的配合,允许跨域。
总结
如果仅仅是展示pdf,为了获取更好的体验,可以考虑将pdf转成图片返回给前端进行展示,这样兼容和体验都不错。
文件下载
Vue.directive('down', {
update: (el, binding) => {
el.style.cssText = 'cursor: pointer;color:write;'
el.addEventListener('click', () => {
const link = document.createElement('a')
const url = binding.value
if (!url) return
// 这里是将url转成blob地址,
fetch(url).then(res => res.blob()).then(blob => { // 将链接地址字符内容转变成blob地址
link.href = URL.createObjectURL(blob)
link.download = ''
document.body.appendChild(link)
link.click()
})
})
}
})
最开始是通过vue directive实现的但是IE不兼容,会直接打开需要下载的pdf,于是写了两套逻辑。
非IE,直接调用downloadFile
这个方法就可以了,例如:downloadFile(this.pdfUrl, '授权书.pdf')
function downloadBlob (url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("GET", url)
xhr.responseType = "blob"
xhr.onload = function () {
resolve(xhr.response)
}
xhr.onerror = function () {
reject(new Error("Download failed."))
}
xhr.send()
})
}
export function downloadFile (url, fileName = "") {
return downloadBlob(url, fileName)
.then(resp => {
if (resp.blob) {
return resp.blob()
}
return new Blob([resp])
})
.then(blob => URL.createObjectURL(blob))
// eslint-disable-next-line no-shadow
.then(url => {
// eslint-disable-next-line no-use-before-define
downloadURL(url, fileName)
URL.revokeObjectURL(url)
})
.catch(err => {
throw new Error(err.message)
})
}
export function downloadURL (url, name = "") {
const link = document.createElement("a")
link.download = name
link.href = url
if ("download" in document.createElement("a")) {
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
} else {
// 对不支持download进行兼容
// eslint-disable-next-line no-use-before-define
click(link, (link.target = "_blank"))
}
}
function click (node) {
try {
node.dispatchEvent(new MouseEvent("click"))
} catch (e) {
const evt = document.createEvent("MouseEvents")
evt.initMouseEvent(
"click",
true,
true,
window,
0,
0,
0,
80,
20,
false,
false,
false,
false,
0,
null
)
node.dispatchEvent(evt)
}
}
IE版本,没错就这么简单,点击下载会询问你打开/保存
<a :href="pdfUrl" class="down-a" v-else>下载《授权书》</a>
只有为什么没有用vue directive的原因,我也忘了。。。。
文件上传
没错还是IE的问题,使用的是vue directive,但是在IE上没有上传文件成功。
Vue.directive('uploadImg', {
inserted: (el, binding) => {
// eslint-disable-next-line no-param-reassign
el.style.cssText = 'cursor: pointer;color:write;'
el.addEventListener('click', () => {
const upload = document.createElement('input')
const uploadResult = binding.value
upload.style.display = 'none'
upload.type = 'file'
upload.accept = 'image/png, image/jpeg'
upload.click()
if (isIE()) {
setTimeout(() => {
if (upload.files[0] !== null) {
toBase64(upload.files[0]).then(res => {
uploadResult(res.replace(/^data:image\/\w+;base64,/, ""), upload.files[0].type.replace("image/", ""))
})
}
}, 0)
} else {
upload.addEventListener('change', () => {
toBase64(upload.files[0]).then(res => {
uploadResult(res.replace(/^data:image\/\w+;base64,/, ""), upload.files[0].type.replace("image/", ""))
})
})
}
})
}
})
这段代码是为了兼容IE设计的,解决change事件不响应,利用的是IE的一个特点:IE在关闭对话框之前都会挂起一个timeout
setTimeout(() => {
if (upload.files[0] !== null) {
toBase64(upload.files[0]).then(res => {
uploadResult(res.replace(/^data:image\/\w+;base64,/, ""), upload.files[0].type.replace("image/", ""))
})
}
}, 0)
前端视频压缩
使用的是ffmpeg,DEMO代码如下:
遇到的问题是在使用的过程中还需要下载20多兆的资源,导致体验不好,当然你也可以把这些资源放到本地or自己的服务器。依赖资源
Vue代理
开发使用的Vue版本是2.6.11,需要代理的话,需要在vue.config.js
里面配置:
configureWebpack: {
devServer: {
proxy: {
'/apis': { // 将www.example.com映射为/apis
target: 'xxx', // 接口域名
secure: true, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 是否跨域
pathRewrite: {
'^/apis': '' // 需要rewrite的,
}
}
}
},
},
后面就直接用/apis
就可以直接代替target
流水线处理
用户的认证分为好几个步骤,每个步骤里面还包含子步骤,每个子步骤之间还可以相互跳转,用户可以从上次中断步骤开始操作,我可以从接口获取用户当前操作步骤以及整个需要经历的步骤,最开始我是想通过接口返回给我的参数把完整的流水线算出来,但是步骤间的相互跳转不好处理,然后想到我只算出主流水线,其他的细支小流我不处理,细支小流的处理放到每个主流水线的步骤中处理。
代码健壮性
这次写项目中,对于异常操作的处理不好,下次需要加强。
设计模式
发布订阅模式
有一个功能是通过Iframe(一个对话框)嵌入到系统的,有一个功能点需要用到接口轮询查询状态,但是用户直接关闭弹框的话,轮询还是会一直存在的,对于一个有着代码洁癖的我来说,肯定是不可以接受的,于是想到通过发布订阅模式来解决这个问题。对于轮询的操作发布一个取消轮询的时间,对于那些无操作的操作订阅这个事件,这样就解决了这个问题,在Vue中需要将这些事件挂载到同一个对象中, 。
单例模式
需求需要需要写一个toast提示,但是最初的一版,存在一个问题,如果提示过于频繁会叠加在一起,就像下面的效果
于是我动用我灵泛的大脑各种搜索,用单例模式解决了这个问题,下图是演示效果 :
贴心的我已经我大家准备好了NPM包,一起欢快的使用吧!
思考
组件封装
这次写代码自我感觉技术上提升了,但是对于数据与视图之间的关系把握不好,组件之间的高聚合 低耦合
处理不太好,设计模式需要加强练习。
接口
用户之前的操作会保存一些信息下来,以备后续的操作,但是在项目中,所有的基础信息全部放到一个接口中返回,个人认为不太友好,最好是按模块、按功能返回这些信息。
不足
正则表达式:需要加强学习,现在基本上不会写正则,全靠搜索引擎。