<template>
|
<div class="up-pdf-stamp">
|
<div v-if="isUpFile" class="work" style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;">
|
<input ref="fileInput" type="file" accept="application/pdf" @change="handleFileUpload" />
|
</div>
|
<div v-if="canvasNumPages > 0" style="color: red;font-size: 12px;margin: 16px 0;">提示:在文件范围内,单击鼠标盖章,双击鼠标已盖好章处可删除当前章</div>
|
<canvas
|
v-for="(item, index) in canvasNumPages"
|
:key="index"
|
:ref="el => setCanvasRef(el, index)"
|
style="border: 1px solid #000;"
|
@click="e => handleCanvasClick(e, index)"
|
@dblclick="e => removeStamp(e, index)"
|
></canvas>
|
</div>
|
</template>
|
|
<script>
|
import jsPDF from 'jspdf'
|
|
// 原实现用 require("@/assets/stamps/" + name + ".png"),Vite 里没有 require;
|
// 用 import.meta.glob 把印章图打成一张 名字→URL 的表,效果等价。
|
const STAMP_MODULES = import.meta.glob('@/assets/stamps/*.png', { eager: true, query: '?url', import: 'default' })
|
|
export default {
|
props: ['isUpFile'],
|
data() {
|
return {
|
pdfDoc: null, // 存储上传的 PDF 数据
|
stamps: [], // 记录盖章的位置
|
contextList: [], //canvas列表
|
canvasEls: [], // canvas 元素列表(Vue3 的 v-for + ref 不再自动收集成数组,改函数 ref 收集)
|
canvasNumPages: 0, // 存储 PDF 总页数
|
stampWidth: 120, // 盖章宽度
|
stampHeight: 80, // 盖章高度
|
stampsName: '',
|
stampsList: ['主任', '质量负责人', '技术负责人'],
|
fileName: '文件名'
|
}
|
},
|
methods: {
|
// Vue3 里 v-for 上的 ref 必须用函数形式,否则 $refs.pdfCanvas 不会是数组
|
setCanvasRef(el, index) {
|
// markRaw:canvas 是 DOM 节点,进了响应式系统会变成 Proxy,
|
// 之后 canvas.getContext() 会抛 "Illegal invocation"
|
this.canvasEls[index] = el ? markRaw(el) : null
|
},
|
handleFileUpload(event) {
|
const file = event.target.files[0]
|
if (file.size > 20 * 1024 * 1024) {
|
this.$refs.fileInput.value = '' // 清空文件输入框内容
|
return this.$message.error('文件大小不能超过20M')
|
}
|
this.lookFile(file)
|
},
|
lookFile(file, currentStamp) {
|
this.fileName = file.name
|
if (currentStamp) {
|
this.stampsName = currentStamp
|
} else {
|
// 原实现写的是 indexOf(回调),数组的 indexOf 不做函数匹配,结果恒为 -1,
|
// 于是这里永远落到“综合室”。按作者本意改为 findIndex,按文件名匹配印章。
|
const index = this.stampsList.findIndex(m => file.name.includes(m))
|
if (index > -1) {
|
this.stampsName = this.stampsList[index]
|
} else {
|
this.stampsName = '综合室'
|
}
|
}
|
if (file && file.type === 'application/pdf') {
|
const reader = new FileReader()
|
reader.onload = e => {
|
const typedArray = new Uint8Array(e.target.result)
|
this.loadPDF(typedArray)
|
}
|
reader.readAsArrayBuffer(file)
|
} else {
|
this.$message.error('请选择 PDF 文件')
|
}
|
},
|
loadPDF(typedArray) {
|
pdfjsLib.getDocument(typedArray).promise.then(pdfDoc_ => {
|
this.pdfDoc = pdfDoc_
|
this.canvasNumPages = this.pdfDoc._pdfInfo.numPages
|
this.stamps = []
|
this.contextList = []
|
for (let i = 1; i <= this.canvasNumPages; i++) {
|
this.$nextTick(() => {
|
this.renderPage(i) // 渲染页面
|
})
|
this.stamps.push([])
|
}
|
})
|
},
|
// 渲染指定页面
|
renderPage(pageNum) {
|
this.pdfDoc.getPage(pageNum).then(page => {
|
const canvas = this.canvasEls[pageNum - 1]
|
if (!canvas) return
|
// 原实现是 push,位置取决于各页 getPage 的 Promise 完成顺序,
|
// 而其它方法一律按 [pageNum-1] 取值;改成按下标写,顺序就无关了。
|
this.contextList[pageNum - 1] = canvas.getContext('2d')
|
const viewport = page.getViewport({ scale: 1.5 })
|
|
canvas.width = viewport.width
|
canvas.height = viewport.height
|
|
page
|
.render({
|
canvasContext: this.contextList[pageNum - 1],
|
viewport: viewport
|
})
|
.promise.then(() => {
|
this.stamps[pageNum - 1].forEach(m => {
|
this.drawStamps(m.x, m.y, pageNum - 1)
|
})
|
})
|
})
|
},
|
// 单击--添加章
|
handleCanvasClick(event, i) {
|
const x = event.offsetX
|
const y = event.offsetY
|
const index = this.stamps[i].findIndex(stamp => {
|
const x0 = x - stamp.x
|
const y0 = y - stamp.y
|
return x0 > 0 && x0 < this.stampWidth && y0 > 0 && y0 < this.stampHeight
|
})
|
if (index > -1) return
|
this.drawStamps(x, y, i)
|
this.stamps[i].push({ x, y })
|
},
|
// 双击--删除盖章
|
removeStamp(event, i) {
|
const x = event.offsetX
|
const y = event.offsetY
|
// 查找被双击的盖章
|
const index = this.stamps[i].findIndex(stamp => {
|
const x0 = x - stamp.x
|
const y0 = y - stamp.y
|
return x0 > 0 && x0 < this.stampWidth && y0 > 0 && y0 < this.stampHeight
|
})
|
if (index === -1) return
|
this.stamps[i].splice(index, 1) // 删除指定的盖章
|
this.contextList[i].clearRect(0, 0, this.contextList[i].width, this.contextList[i].height)
|
this.renderPage(i + 1)
|
},
|
// 渲染章
|
drawStamps(x, y, index) {
|
const src = this.stampUrl(this.stampsName)
|
if (!src) {
|
console.warn(`[UpPdfStamp] 未找到印章图片:${this.stampsName}.png`)
|
return
|
}
|
const img = new Image()
|
img.src = src
|
const that = this
|
img.onload = function () {
|
// 图片加载完成后,将图片绘制到canvas上
|
that.contextList[index].drawImage(img, x, y, that.stampWidth, that.stampHeight)
|
}
|
},
|
stampUrl(name) {
|
const suffix = `/${name}.png`
|
const key = Object.keys(STAMP_MODULES).find(k => k.endsWith(suffix))
|
return key ? STAMP_MODULES[key] : ''
|
},
|
// 生成 PDF 的函数
|
async generatePDF() {
|
if (this.contextList.length === 0) {
|
this.$message({ message: '请先上传PDF文件', type: 'error' })
|
this.$emit('uploadPDFErr')
|
return false
|
}
|
const pdf = new jsPDF('p', 'mm', 'a4')
|
for (let i = 0; i < this.contextList.length; i++) {
|
const imgData = this.canvasEls[i].toDataURL('image/jpeg', 0.7)
|
const pdfWidth = pdf.internal.pageSize.getWidth()
|
const pdfHeight = (this.canvasEls[i].height * pdfWidth) / this.canvasEls[i].width
|
pdf.addImage(imgData, 'JPEG', 0, 0, pdfWidth, pdfHeight) // 将图片添加到 PDF
|
if (i !== this.contextList.length - 1) {
|
pdf.addPage() // 添加新的一页
|
}
|
}
|
|
// 将 PDF 文件保存或上传
|
const pdfOutput = pdf.output('blob') // 获取 PDF 文件的 Blob 对象
|
|
// 上传到后端
|
return this.$emit('uploadPDF', pdfOutput, this.fileName)
|
}
|
}
|
}
|
</script>
|
|
<style scoped></style>
|