前端React实现pdf在线阅读和电子合同
前端React实现pdf在线阅读和电子合同
介绍
前段时间看到网上有个趋势,今后合同也开始变成无纸化,如果要签约,只需要在网上签名,不需要本人去现场。之前没有做过,决定试试看效果。
1. 搭建页面
因为是我自建的脚手架,所以暂时先搭建个骨架用来做测试页面的开发。
提供一个简单的搭建步骤:
npx create-react-app [name] --template typescript
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEGRKtws-1644143802262)(./ReadMe.assets/image-20220206175120080.png)]
搭建好页面骨架后,我在前端周报上面翻了翻,找到了2个不错的库: js-pdf
和react-pdf
jsPdf
: 前端生成pdf
文件。支持直接下载、转成data url
。react-pdf
: 实时展示pdf
内容。支持打开文件和打开链接。- 注意: 如果使用
webpack5
的话,不要使用国内的npm
源,一定要用国外最新版本的,否则会出现dev-server
崩溃栈溢出的问题。
- 注意: 如果使用
思路
如果要实现pdf
版本的合同,我个人分为4步:
- 读取、展示合同内容。
- 制作合同。
- 制作合同的同时,能实时看到内容。
- 下载文件。
后期扩展:
5. 线上签名。
6. 盖章留戳。
1. 读取、展示合同内容
利用react-pdf
的展示和读取功能,实现pdf
文件的展示。首先,先不考虑文件的来源,我们以本地的测试pdf
为准。
在开发页面引入react-pdf
,下面是实现源码
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf/dist/esm/entry.webpack';
// 引入样式,修复不对齐的问题
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import sample from './test.pdf';
const PDFDisplay = () => {
const [totalPages, setTotalPages] = useState<number>(0)
const [pageNumber, setPageNumber] = useState<number>(1)
const [file, setFile] = useState(sample)
return (
<>
<Card style = {{
margin: 8,
}}>
<Title level={3}>
React-pdf 查看/显示 pdf内容
</Title>
<div style = {{
margin: 8,
}}>
<div style = {{
margin: 6,
display: 'flex',
flexDirection: 'row-reverse',
}}>
<div className="Example__container__load">
<input onChange={onFileChange} type="file" />
</div>
<Button
style={{
margin: 4,
}}
type='primary'
onClick={turnHeadPages}
>查看首页</Button>
<Button
style={{
margin: 4,
}}
type='primary'
onClick={turnTailPages}
>查看尾页</Button>
<Button
style={{
margin: 4,
}}
type='primary'
onClick={handleReducePages}
>上一页</Button>
<Button
style={{
margin: 4,
}}
type='primary'
onClick={handleTurnPages}
>下一页</Button>
</div>
<div style={{
display: 'flex',
justifyContent: 'center',
}}>
<Document file={file}
onLoadSuccess={(values: any) => setTotalPages(values?.numPages)} options={options} >
<Page pageNumber={pageNumber} renderMode='svg' />
</Document>
</div>
</div>
<div style={{
margin: 6,
display: 'flex',
flexDirection: 'row-reverse',
}}>第{pageNumber}页, 共{totalPages}页</div>
</Card>
</>
)
}
export default PDFDisplay
可以看到文件已经被读取出来:
因为react-pdf
生成了一个隐藏的dom
,所以在复制文字的时候,能看到和展示的内容又区别,甚至出现了一些位置上的偏移。
为了解决这种不对齐的问题,需要引入样式,最大限度的避免这种样式偏移:
// 引入样式,修复不对齐的问题
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
因为pdf
是逐页读取的,我并没有采用官方doc的做法把所有的内容全部展示出来,因为需要写上4个按钮,实现灵活操作。
这个是按钮的方法:
// 翻下页
const handleTurnPages = () => {
if (pageNumber < totalPages) setPageNumber(pageNumber + 1)
else message.error('已经到最后一页!')
}
// 翻上页
const handleReducePages = () => {
if (pageNumber > 1) setPageNumber(pageNumber - 1)
else message.error('已经到首页!')
}
// 回到首页
const turnHeadPages = () => {
if (pageNumber === 1) message.error('已经是首页')
else setPageNumber(1)
}
// 回到尾页
const turnTailPages = () => {
if (pageNumber === totalPages) message.error('已经是尾页')
else setPageNumber(totalPages)
}
// 上传文件
const onFileChange = (event: any) => {
const file = event?.target.files[0]
const isPDF = file?.type === 'application/pdf'
if(!isPDF) {
message.error(`${file.name}不是一个pdf文件!`)
return
}
console.log('event:', event.target.files);
setFile(file)
}
在展示过程中,如果我们的主要内容是汉字,会出现乱码情况。而汉字属于utf-8
,我们需要额外引入一些东西,并且对webpack.config
进行修改:
// 非拉丁语的格式问题修复
// 此处需要修改webpack.config, CopyWebpackPlugin
const options = {
cMapUrl: 'cmaps/',
cMapPacked: true,
};
安装新的plugin
:
npm i copy-webpack-plugin -S
打开webpack.config
的配置文件,写入以下代码:
const CopyWebpackPlugin = require('copy-webpack-plugin');
new CopyWebpackPlugin({
patterns: [
{
from: path.join(path.dirname(require.resolve('pdfjs-dist/package.json')), 'cmaps'),
to: 'cmaps/'
},
],
}),
解决了汉字显示问题:
附录
1. jsPDF
- 文档说明比较少,需要仔细阅读源码
- 推荐阅读
stackoverflow
的常见QA
: jspdf-stackoverflow
- 推荐阅读
- 暂时不支持
utf-8
- 如果要保存为图片,需要使用
html2canvas
来使用, html2canvas - 更换为支持
utf-8
的pdfmake
或者pdfkit
- 如果要保存为图片,需要使用
实现
1. 下载pdf
文件
- 指定内容下载
pdf.save()
pdf.output()
- 用
canvas
展示效果,并转换下载
2. 显示pdf
文件
- 本地文件展示
- 实时下载结果展示