番茄酱
主 题
前言
之前业务上有用pdf文件进行合同协议签署的需求;即用户浏览完pdf合同文件,唤起签字面板进行手写签名;签完名字之后把名字和合同文件合并再一起,然后上传给后端,后端调用E签宝相关API进行盖章;
主要实现
经查找相关,确定了以下三个相关库:
- PDF.JS 用于pdf文件的展示已经对pdf文件编辑完之后重新加载展示;
- PDF-LIB 可以对pdf文件进行编辑,追加图片和 文字(追加中文时需要加载中文相关字体,比较麻烦);
- Smooth-Signature 签字面板,用户手写签字之后,生成图片追加到合同pdf文件中;
缩放实现
通过设置pdf外容器的css变量--scale-factor
,然后css中动态计算容器的宽和高,从而实现放的效果;
.pdf-wrap {
min-height: 100vh;
width: calc(var(--scale-factor) * 612px);
height: calc(var(--scale-factor) * 792px);
transition: all 0.5s ease-in;
}
主要js代码
import { ref, onMounted } from 'vue'
import SmoothSignature from 'smooth-signature'
import dayjs from 'dayjs'
import sealLogo from '@/assets/images/logo/person/jiang.png'
const emits = defineEmits(['success'])
const props = defineProps({
pdfSrc: {
type: String,
default: '',
},
second: {
type: Number,
default: 10,
},
info: {
type: Object,
default: () => ({}),
},
})
const smoothSignatureCanvas = ref('')
const signature = ref('')
const signaturePng = ref('')
const showSmoothSignatureWrap = ref(false)
const localSecond = ref(props.second)
const loading = ref(true)
onMounted(() => {
const timer = setInterval(() => {
localSecond.value--
if (!localSecond.value) {
clearInterval(timer)
}
}, 1000)
const { width, height, } = scrollContainer.value.getBoundingClientRect()
minScale.value = width / 612
// console.log(width, height)
const options = {
width: width - 24,
height: height / 3 - 50,
minWidth: 3,
maxWidth: 10,
color: '#333333',
bgColor: 'transparent',
// bgColor: '#f6f6f6',
}
scaleFactor.value = width / 612
signature.value = new SmoothSignature(smoothSignatureCanvas.value, options)
document.querySelector('.scroll-container').addEventListener('scroll', (e) => {
const scrollTop = e.target.scrollTop
// console.log(scrollTop, pageHeight.value, Math.ceil((scrollTop + 306) / pageHeight.value))
currentPage.value = Math.ceil(
((scrollTop + 100) * window.devicePixelRatio) / pageHeight.value
)
if (currentPage.value >= totalPageCount.value) {
currentPage.value = totalPageCount
}
})
reloadPdf()
})
const handleClear = () => {
signature.value.clear()
}
const handleUndo = () => {
signature.value.undo()
}
const handleFinish = () => {
// 旋转
// const canvas = signature.value.getRotateCanvas(-90)// 不用反转
const canvas = signature.value
const pngUrl = canvas.toDataURL()
signaturePng.value = pngUrl
editPdf()
showSmoothSignatureWrap.value = false
}
const handleClose = () => {
console.log('关闭')
handleClear()
showSmoothSignatureWrap.value = false
}
const showSmoothSignatureWrapHandle = () => {
handleClear()
showSmoothSignatureWrap.value = true
}
const scrollContainer = ref('')
const pdfContainer = ref('')
const totalPageCount = ref(1)
// 页数和缩放相关
const currentPage = ref(1)
const pageWidth = ref(0)
const pageHeight = ref(0)
const scaleFactor = ref(1)
const minScale = ref(1)
const changeScale = (t) => {
let num = scaleFactor.value
if (t === '-') {
num = num - num * 0.1
if (num <= 1) {
num = minScale.value
}
} else if (t === '+') {
num = num + num * 0.1
if (num >= 4) {
num = minScale.value * 4
}
}
scaleFactor.value = num
}
const reloadPdf = async (pdfData = props.pdfSrc) => {
loading.value = true
const pdfDocument = await pdfjsLib.getDocument(pdfData).promise
// console.log(pdfDocument)
pdfContainer.value.innerHTML = '' // 清空PDF容器
totalPageCount.value = pdfDocument.numPages
for (let pageIndex = 1; pageIndex <= pdfDocument.numPages; pageIndex++) {
const page = await pdfDocument.getPage(pageIndex)
const viewport = page.getViewport({ scale: 2, })
pageWidth.value = viewport.width / 2
pageHeight.value = viewport.height / 2
// console.log(page)
const canvas = document.createElement('canvas')
pdfContainer.value.appendChild(canvas)
const context = canvas.getContext('2d')
canvas.width = viewport.width
canvas.height = viewport.height
const renderContext = {
canvasContext: context,
viewport,
}
// console.log(page)
await page.render(renderContext)
loading.value = false
}
}
const editPdf = async () => {
const PDFDocument = PDFLib.PDFDocument
// showToast.loading('加载中')
// const fontBytes = await fetch('https://jiang-xia.top/x-blog/api/v1/static/uploads/2023-12/ga0hqzh5lek2ntyxtzebx0-华文中宋.ttf').then((res) => res.arrayBuffer())
const pdfBuffer = await fetch(props.pdfSrc).then(res => res.arrayBuffer())
const pdfDoc = await PDFDocument.load(pdfBuffer)
// pdfDoc.registerFontkit(fontkit)
// const customFont = await pdfDoc.embedFont(fontBytes,{subset:true})
const pages = pdfDoc.getPages()
console.log('签名设置————————开始')
for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
console.log('pageIndex', pageIndex)
const width = pages[pageIndex].getWidth()
const height = pages[pageIndex].getHeight()
if (pageIndex === pages.length - 1) {
const emblemImageBytes = await fetch(signaturePng.value).then(res => res.arrayBuffer())
const img = await pdfDoc.embedPng(emblemImageBytes)
// pdf(0,0)再左上角
const x = width - 260
const y = height / 2 - 100
// 印章图片
const sealImageBytes = await fetch(sealLogo).then(res => res.arrayBuffer())
const sealImg = await pdfDoc.embedPng(sealImageBytes)
pages[pageIndex].drawImage(sealImg, {
x,
y: y - 40,
width: 140,
height: 140,
opacity: 1, // 设置图片透明度
})
// 签名图片
pages[pageIndex].drawImage(img, {
x,
y,
width: 160,
height: 60,
})
// 签名日期
const dateText = dayjs().format('YYYY MM DD')
console.log(dateText)
pages[pageIndex].drawText(dateText, {
x: x + 20,
y: y - 20,
size: 18,
// font: customFont,
})
const pdfBytes = await pdfDoc.save()
// console.log(pdfBytes)
const myFile = new File([pdfBytes], 'generated.pdf')
reloadPdf(pdfBytes)
mSignatureSuccess(myFile)
// showToast.hide()
}
}
}
// pdf
// all canvas to pdf
const pdfWrap = ref()
// 签名组件成功
const mSignatureSuccess = (res) => {
emits('success', res)
全部评论(0)