|
@@ -1,27 +1,186 @@
|
|
|
<template>
|
|
|
<div class="pdf-viewer">
|
|
|
- <iframe v-if="pdfUrl" :src="pdfUrl" ref="iframe" class="iframe-pdf"></iframe>
|
|
|
+ <div class="pdf-viewer-box">
|
|
|
+ <div class="preview-box">
|
|
|
+ <div class="thumbnail-item" v-for="(thumb, index) in thumbnails" :key="index"
|
|
|
+ :class="{ active: currentPage === index + 1 }" @click="changePage(index + 1)">
|
|
|
+ <img class="image" :src="thumb" alt="thumbnail" />
|
|
|
+ <div class="text">{{index+1}}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="operation-box">
|
|
|
+ <canvas ref="mainCanvas"></canvas>
|
|
|
+ <div ref="fabricCanvas" class="fabric-canvas">
|
|
|
+ <canvas id="fabricCanvas"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="operation-button" v-if="type === 'edit'">
|
|
|
+ <div class="button-item">
|
|
|
+ <el-button type="primary" size="mini">签字</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="button-item">
|
|
|
+ <el-button type="primary" size="mini" @click="insertSeal">盖章</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="hui-dialog-submit" v-if="type === 'edit'">
|
|
|
+ <el-button size="small" @click="$emit('callback')">取 消</el-button>
|
|
|
+ <el-button size="small" type="primary" @click="submit" :loading="loading">提 交</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+ import pdf from 'pdfvuer';
|
|
|
+ import * as fabric from 'fabric';
|
|
|
export default {
|
|
|
props: ['list', 'type'],
|
|
|
data() {
|
|
|
return {
|
|
|
- pdfUrl: ''
|
|
|
+ pdfDoc: null,
|
|
|
+ currentPage: 1,
|
|
|
+ totalPages: 0,
|
|
|
+ thumbnails: [], // 存储缩略图 Base64
|
|
|
+ renderTask: null,
|
|
|
+ fabricCanvas: null,
|
|
|
+ sealObj: null,
|
|
|
+ loading: false
|
|
|
}
|
|
|
},
|
|
|
- mounted() {
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.fabricCanvas) {
|
|
|
+ document.removeEventListener('keydown', this.initDelete);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async mounted() {
|
|
|
if (this.list.length > 0) {
|
|
|
- this.selectPdf();
|
|
|
+ await this.loadPdf(this.list[0].url);
|
|
|
+ await this.generateThumbnails();
|
|
|
+ await this.changePage(1);
|
|
|
+ }
|
|
|
+ if (this.type === 'edit') {
|
|
|
+ document.addEventListener('keydown', this.initDelete);
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
- selectPdf() {
|
|
|
- this.pdfUrl = '';
|
|
|
- this.pdfUrl = '/pdf/web/viewer.html?file=' + this.list[0].url + '&type=' + this.type;
|
|
|
+ //加载pdf
|
|
|
+ async loadPdf(url) {
|
|
|
+ const loadingTask = pdf.createLoadingTask({
|
|
|
+ url
|
|
|
+ });
|
|
|
+ this.pdfDoc = await loadingTask;
|
|
|
+ this.totalPages = this.pdfDoc._pdfInfo.numPages;
|
|
|
+ },
|
|
|
+ // 生成所有缩略图
|
|
|
+ async generateThumbnails() {
|
|
|
+ for (let pageNum = 1; pageNum <= this.totalPages; pageNum++) {
|
|
|
+ const page = await this.pdfDoc.getPage(pageNum);
|
|
|
+ const thumbnail = await this.renderPageToImage(page); // 缩略图缩放比例
|
|
|
+ this.thumbnails.push(thumbnail);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 将单页渲染为图片(Base64)
|
|
|
+ async renderPageToImage(page) {
|
|
|
+ const viewport = page.getViewport({
|
|
|
+ scale: 1
|
|
|
+ });
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
+ const context = canvas.getContext('2d');
|
|
|
+ const outputScale = window.devicePixelRatio || 1;
|
|
|
+ this.setCanvasSize(canvas, viewport, outputScale);
|
|
|
+ const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
|
|
|
+ await page.render({
|
|
|
+ canvasContext: context,
|
|
|
+ viewport,
|
|
|
+ transform
|
|
|
+ }).promise;
|
|
|
+ // 转换为 Base64
|
|
|
+ return canvas.toDataURL('image/jpeg', 1);
|
|
|
+ },
|
|
|
+ // 切换页面
|
|
|
+ async changePage(pageNum) {
|
|
|
+ this.currentPage = pageNum;
|
|
|
+ const page = await this.pdfDoc.getPage(pageNum);
|
|
|
+ await this.renderMainPage(page);
|
|
|
+ },
|
|
|
+ // 渲染主视图(高清大图)
|
|
|
+ async renderMainPage(page) {
|
|
|
+ const viewport = page.getViewport({
|
|
|
+ scale: 1
|
|
|
+ });
|
|
|
+ const canvas = this.$refs.mainCanvas;
|
|
|
+ const context = canvas.getContext('2d');
|
|
|
+ const outputScale = window.devicePixelRatio || 1;
|
|
|
+ this.setCanvasSize(canvas, viewport, outputScale);
|
|
|
+ const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
|
|
|
+ // 取消之前的渲染任务(避免冲突)
|
|
|
+ if (this.renderTask) this.renderTask.cancel();
|
|
|
+ // 渲染高清大图
|
|
|
+ this.renderTask = page.render({
|
|
|
+ canvasContext: context,
|
|
|
+ viewport,
|
|
|
+ transform
|
|
|
+ });
|
|
|
+ await this.renderTask.promise;
|
|
|
+ },
|
|
|
+ setCanvasSize(canvas, viewport, outputScale) {
|
|
|
+ canvas.width = Math.floor(viewport.width * outputScale);
|
|
|
+ canvas.height = Math.floor(viewport.height * outputScale);
|
|
|
+ canvas.style.width = Math.floor(viewport.width) + "px";
|
|
|
+ canvas.style.height = Math.floor(viewport.height) + "px";
|
|
|
+ if (this.fabricCanvas) this.fabricCanvas.dispose();
|
|
|
+ if (this.type === 'edit') {
|
|
|
+ this.$refs.fabricCanvas.style.width = Math.floor(viewport.width) + "px";
|
|
|
+ this.$refs.fabricCanvas.style.height = Math.floor(viewport.height) + "px";
|
|
|
+ this.fabricCanvas = new fabric.Canvas('fabricCanvas', {
|
|
|
+ width: Math.floor(viewport.width), // 设置画布宽度
|
|
|
+ height: Math.floor(viewport.height), // 设置画布高度
|
|
|
+ });
|
|
|
+ }
|
|
|
},
|
|
|
+ insertSeal() {
|
|
|
+ if (!this.fabricCanvas) return;
|
|
|
+ fabric.Image.fromURL('./assets/wechat_code.jpg').then(img => {
|
|
|
+ let scale = 100 / img.width;
|
|
|
+ img.scale(scale);
|
|
|
+ img.set({
|
|
|
+ top: 0,
|
|
|
+ left: 0
|
|
|
+ })
|
|
|
+ this.sealObj = img;
|
|
|
+ // 将文字添加到画布
|
|
|
+ this.fabricCanvas.add(img);
|
|
|
+ this.fabricCanvas.renderAll();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ sureSeal() {
|
|
|
+ this.fabricCanvas.discardActiveObject();
|
|
|
+ this.sealObj.set({
|
|
|
+ selectable: false
|
|
|
+ });
|
|
|
+ },
|
|
|
+ initDelete(event) {
|
|
|
+ if (!this.fabricCanvas) return;
|
|
|
+ if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
|
+ var activeObject = this.fabricCanvas.getActiveObject(); // 获取当前激活的对象
|
|
|
+ if (activeObject) {
|
|
|
+ this.fabricCanvas.remove(activeObject); // 删除选中的对象
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ submit() {
|
|
|
+ const allObjects = this.fabricCanvas.getObjects();
|
|
|
+ for (let i = 0; i < allObjects.length; i++) {
|
|
|
+ let item = allObjects[i];
|
|
|
+ console.log({
|
|
|
+ left: item.left,
|
|
|
+ top: item.top,
|
|
|
+ width: item.width * item.scaleX,
|
|
|
+ height: item.height * item.scaleY
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
}
|
|
|
</script>
|
|
@@ -30,11 +189,82 @@
|
|
|
height: 100%;
|
|
|
width: 100%;
|
|
|
overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .hui-dialog-submit {
|
|
|
+ border-top: 1px solid $--border-color-light;
|
|
|
+ text-align: right;
|
|
|
+ padding: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .pdf-viewer-box {
|
|
|
+ flex: 1;
|
|
|
+ height: 0;
|
|
|
+ display: flex;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
|
|
|
- .iframe-pdf {
|
|
|
- border: none;
|
|
|
- height: 100%;
|
|
|
- width: 100%;
|
|
|
+ .operation-button {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 20px;
|
|
|
+ right: 20px;
|
|
|
+
|
|
|
+ .button-item {
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .preview-box {
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .thumbnail-item {
|
|
|
+ width: 100px;
|
|
|
+ padding: 10px 10px 0 10px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .image {
|
|
|
+ border: 1px solid $--border-color-light;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .text {
|
|
|
+ text-align: center;
|
|
|
+ padding-top: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ padding-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ .image {
|
|
|
+ border-color: $--color-primary;
|
|
|
+ }
|
|
|
+
|
|
|
+ .text {
|
|
|
+ color: $--color-primary;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .operation-box {
|
|
|
+ flex: 1;
|
|
|
+ width: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ background: $--background-color-base;
|
|
|
+ text-align: center;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .fabric-canvas {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ margin: auto;
|
|
|
+ z-index: 9;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
</style>
|