【Vue】Pdf转图片功能+多张图片拼接封装

devtools/2024/12/5 7:19:05/

Pdf转图片功能+多张图片拼接封装

      • HTML页面
      • tools.js文件

HTML页面

<template><div class="main-marge"><div class="box"><van-uploader accept="image/*,.pdf" :before-read="(file) => beforeRead(file, '-1')"><div class="upImg"><img src="./assets/upload.png" /><span>请上传文件第{{ imglength }}</span></div></van-uploader><div class="upImgtip">单文件大小不超过2MB,格式仅限PNGJPGJPEGPDF</div><div class="imglist"><div class="title">上传材料</div><div class="listform" v-if="filelist.length"><div class="listbar" v-for="(item, index) in filelist" :key="index"><div class="before"><img :src="item.url" /><div class="cont">{{ item.name }}</div></div><div class="bargroup"><span @click="delImg(index)">删除</span><van-uploader accept="image/*,.pdf" :before-read="(file) => beforeRead(file, index)"><span>重新上传</span></van-uploader></div></div></div><div class="nolist" v-else><img src="./assets/nolist.png" /><span>暂未上传材料,请上传</span></div></div></div><div class="btngroup"><div class="cancle" @click="cancle">取消</div><div class="confirm" @click="mergeImg">确定</div></div><!-- pdf绘制区域 --><div class="canvasPDF"><div><canvasid="canvas":style="{border: '1px solid #eeeeee' }"></canvas></div><div class="pdfbar" v-for="(item, i) in imgFiles" :key="i"><canvas :id="`pdf_canvas_${item}`" style="border: 1px solid #eeeeee"></canvas></div></div><!-- 图片绘制合并区域 --><div class="canvasPDF"><canvasid="myCanvas":style="{ border: '1px solid #eeeeee' }"></canvas></div></div>
</template><script>
import { getPdfnum, PdfToImg, MeargeImg } from '../../utils/tools.js';
export default {data() {return {// pdfnewPrototype: [],newPrototypeValue: [],imgFiles: [], //pdf页数列表filelist: [], //列表//imgrealWidth: 720,dpr: '',loading: false,};},computed: {imglength() {return this.filelist.length + 1;},},created() {//此功能是为了pdf.js内部,有时候会报for....in的错误,原因是原型方法被其他地方改变,这里需要改回来for (let key in Array.prototype) {if (!Array.prototype.hasOwnProperty(key)) continue;this.newPrototype.push(key);}// 存放原始键 原始方法this.newPrototypeValue = this.newPrototype.map((v) => ({ [v]: Array.prototype[v] }));// 删除直接属性this.newPrototype.forEach((v) => delete Array.prototype[v]);},beforeDestroy() {if (Array.isArray(this.newPrototypeValue) && this.newPrototypeValue.length > 0) {for (const key in this.newPrototypeValue) {const method = this.newPrototypeValue[key];// 确保该属性是函数(即方法)if (typeof method === 'function') {// 将方法重新赋值到Array.prototype上Array.prototype[key] = method;}}}},methods: {//pdf转图片async beforeRead(file, fileindex) {if (!file) {return false;}let loading = this.$Toast.loading({message: '加载中...',forbidClick: true,duration: 0,});try {this.imgFiles = await getPdfnum(file);const res = await PdfToImg(file);if (fileindex === '-1') {this.filelist.push(res);} else {this.filelist.splice(fileindex, 1, res);}loading.clear();} catch (error) {loading.clear();this.$confirm({title: '提示',message: error,showCancelButton: false,closeOnClickModal: false,});}},//合并图片async mergeImg() {let loading = this.$Toast.loading({message: '加载中...',forbidClick: true,duration: 0,});try {const res = await MeargeImg(this.filelist, this.realWidth);this.$emit('confirm-Merge', res.data);loading.clear();} catch (error) {loading.clear();this.$confirm({title: '提示',message: error,showCancelButton: false,closeOnClickModal: false,});}},//删除所选照片delImg(index) {this.filelist.splice(index, 1);},//关闭弹窗cancle() {this.$emit('hidden-cancle');},},
};
</script><style scoped lang="scss">
.main-marge {position: fixed;top: 0;left: 0;z-index: 99;height: 100vh;width: 100%;background: #fff;.tip {display: flex;padding: 9px 9px 9px 12px;background: rgba(253, 171, 77, 0.1);img {margin-right: 8px;width: 16px;height: 16px;}p {flex: 1;color: #fc7a43;font-family: 'PingFang SC';font-size: 12px;font-style: normal;font-weight: 400;line-height: 22px; /* 157.143% */}}.box {padding: 0 16px;}.upImg {margin-top: 16px;display: flex;padding: 12px 0px;flex-direction: column;align-items: center;border-radius: 8px;border: 1px dashed rgba(203, 180, 134, 0.3);background: rgba(203, 180, 134, 0.06);color: #b8926b;text-align: center;font-family: 'PingFang SC';font-size: 14px;font-style: normal;font-weight: 400;line-height: 22px; /* 157.143% */span {margin-top: 8px;}}.upImgtip {margin-top: 8px;color: rgba(0, 0, 0, 0.4);font-family: 'PingFang SC';font-size: 12px;font-style: normal;font-weight: 400;line-height: 20px; /* 166.667% */}.imglist {margin-top: 28px;.title {color: #000;font-family: 'PingFang SC';font-size: 18px;font-style: normal;font-weight: 500;line-height: 26px; /* 144.444% */}.listform {.listbar {display: flex;align-items: center;justify-content: space-between;border-bottom: 1px solid #e5e5e5;padding: 12px 0;.before {display: flex;align-items: center;img {margin-right: 20px;width: 48px;height: 48px;border-radius: 6px;}.cont {width: 175px;color: rgba(0, 0, 0, 0.8);font-family: 'PingFang SC';font-size: 16px;font-style: normal;font-weight: 500;line-height: 24px; /* 150% */}}.bargroup {width: 100px;color: #cbb486;text-align: center;font-family: 'PingFang SC';font-size: 14px;font-style: normal;font-weight: 400;line-height: 22px; /* 157.143% */}&:last-child {border-bottom: none;}}}.nolist {margin-top: 24px;display: flex;flex-direction: column;align-items: center;img {width: 160px;height: 160px;}span {margin-top: 16px;color: rgba(0, 0, 0, 0.6);text-align: center;font-family: 'PingFang SC';font-size: 14px;font-style: normal;font-weight: 400;line-height: normal;}}}.btngroup {position: fixed;z-index: 2;bottom: 0;left: 0;width: 100%;padding: 12px 0;display: flex;align-items: center;justify-content: space-around;background: #fff;box-shadow: 0px -2px 8px 0px rgba(191, 191, 191, 0.15), 0px -2px 8px 0px rgba(191, 191, 191, 0.15);.cancle {width: 160px;padding: 11px 16px;border-radius: 8px;border: 1px solid #e5e5e5;background: #fff;color: rgba(0, 0, 0, 0.6);font-family: 'PingFang SC';font-size: 16px;font-style: normal;font-weight: 400;line-height: 26px; /* 144.444% */text-align: center;}.confirm {width: 160px;padding: 11px 16px;border-radius: 8px;background: linear-gradient(135deg, #e4c995 0%, #b9916a 100%);color: #fff;font-family: 'PingFang SC';font-size: 16px;font-style: normal;font-weight: 400;line-height: 26px; /* 144.444% */text-align: center;}}.canvasPDF {position: fixed;top: 0;left: -9999px;display: flex;flex-direction: column;align-items: center;justify-content: center;.pdfbar {position: relative;margin-top: 10px;z-index: 1;}}
}
</style>

tools.js文件

import * as pdfjs from 'pdfjs-dist';
import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
/*** pdf转图片获取pdf页数,用于渲染页面* @param {file} file - 文件*/
export function getPdfnum(file) {return new Promise((resolve, reject) => {const imgFiles = [];if (file.type === 'application/pdf') {let reader = new FileReader();reader.readAsDataURL(file); //将文件读取为 DataURLreader.onload = function () {//文件读取成功完成时触发const loadingTask = pdfjs.getDocument(reader.result);loadingTask.promise.then(async (pdf) => {const pageNum = pdf.numPages;if (pageNum > 10) {reject('此pdf页数过多,请线下合并');}//准备图片for (let i = 1; i <= pageNum; i++) {imgFiles.push(i);}resolve(imgFiles);});};reader.onerror = function () {// 如果文件读取失败,拒绝Promisereject(new Error('Failed to read the file'));};} else {resolve([]);}});
}
//pdf转图片
export function PdfToImg(file) {return new Promise((resolve, reject) => {const newimgList = [];const fileName = file.name.substring(0, file.name.lastIndexOf('.'));if (file.type === 'application/pdf') {let reader = new FileReader();reader.readAsDataURL(file); //将文件读取为 DataURLreader.onload = function () {//文件读取成功完成时触发const loadingTask = pdfjs.getDocument(reader.result);loadingTask.promise.then(async (pdf) => {let pageNum = pdf.numPages;// 处理for (let i = 1; i <= pageNum; i++) {let canvasItem = '';pdf.getPage(i).then(async (page) => {const canvas = document.getElementById('pdf_canvas_' + i);const ctx = canvas.getContext('2d');const viewport = page.getViewport({ scale: 4 });canvas.height = viewport.height;canvas.width = viewport.width;const destWidth = 298;const destheight = destWidth * (viewport.height / viewport.width);canvas.style.width = destWidth + 'px';canvas.style.height = destWidth * (viewport.height / viewport.width) + 'px';newimgList.push(canvas);await page.render({ canvasContext: ctx, viewport });// 使用file对象进行后续操作if (i === pageNum) {setTimeout(async () => {const res = await savePdfImage(newimgList, destWidth, destheight, fileName);resolve(res);}, 500);}});}});};reader.onerror = function () {// 如果文件读取失败,拒绝Promisereject(new Error('Failed to read the file'));};} else {const reader = new FileReader();reader.onload = function (e) {let pngData = e.target.result;let obj = {url: pngData,name: fileName,};resolve(obj);};// 开始读取文件reader.readAsDataURL(file);}});
}
/*** pdf保存图片* @param {Array} newimgList - canvas列表* @param {Number} x - 页面展示宽* @param {Number} y - 页面展示高* @param {String}fileName - 文件名称*/
function savePdfImage(newimgList, x, y, fileName) {return new Promise((resolve, reject) => {let allcanvas = document.getElementById('canvas');let allctx = allcanvas.getContext('2d');allcanvas.style.width = x;allcanvas.style.height = y * newimgList.length;const subCanvasWidth = newimgList[0].width;const subCanvasHeight = newimgList[0].height;allcanvas.width = subCanvasWidth;allcanvas.height = subCanvasHeight * newimgList.length;newimgList.forEach((subCanvas, index) => {// 计算当前图片在Canvas中的垂直位置const yPosition = index * subCanvasHeight;// 绘制图片// 使用drawImage的四个参数版本来指定绘制的源图像区域和目标区域allctx.drawImage(subCanvas, 0, yPosition, subCanvasWidth, subCanvasHeight);if (index === newimgList.length - 1) {let pngData = allcanvas.toDataURL('image/png');let obj = {url: pngData,name: fileName,};resolve(obj);}});});
}
/*** 合并图片* @param {file} filelist - 文件列表* @param {Number} realWidth - 合并后的文件宽度**/
export function MeargeImg(filelist, realWidth) {return new Promise(async (resolve, reject) => {if (!filelist.length) {reject('材料不能为空,请检查');}let canvas = document.getElementById('myCanvas');const ctx = canvas.getContext('2d');let x = 0;let y = 0;//获取图片信息const dpr = window.devicePixelRatio;const res = await getImginfo(filelist, realWidth);//展示高度canvas.style.width = realWidth;canvas.style.height = res;canvas.width = realWidth * dpr;canvas.height = res * dpr;for (let i = 0; i < filelist.length; i++) {const img = new Image();img.src = filelist[i].url;img.crossOrigin = 'anonymous';img.onload = async () => {let { imgWidth, imgHeight } = await setPosition(x, y, img, realWidth);//获取比例ctx.drawImage(img, x, y, imgWidth, imgHeight);y += imgHeight;if (i === filelist.length - 1) {// 下载图片let pngData = canvas.toDataURL('image/png');let filename = filelist[0].name + '.png' || '合并图片.png';let fileimg = await base64ToFile(pngData, filename);let obj = {target: {files: [fileimg],},};resolve({ data: obj });}};}ctx.scale(dpr, dpr);});
}
//获取实际位置,宽高
function setPosition(x, y, img, realWidth) {return new Promise((resolve, reject) => {const scaledpr = img.width / realWidth;let imgWidth = img.width;let imgHeight = img.height;if (scaledpr > 1) {imgWidth = img.width / scaledpr;imgHeight = img.height / scaledpr;}resolve({imgWidth: imgWidth,imgHeight: imgHeight,});});
}
//获取所有图片的总高度
function getImginfo(filelist, realWidth) {return new Promise((resolve, reject) => {let allHeight = 0;let loadedCount = 0; // 用于追踪已加载图片数量let totalImages = filelist.length; // 图片总数for (let i = 0; i < totalImages; i++) {const img = new Image();img.crossOrigin = 'anonymous';img.src = filelist[i].url;img.onload = () => {const scaledpr = img.width / realWidth;let imgHeight = img.height;if (scaledpr > 1) {imgHeight = img.height / scaledpr;}allHeight += imgHeight;loadedCount++; // 增加已加载图片计数if (loadedCount === totalImages) {resolve(allHeight);}};img.onerror = (error) => {reject(error);};}});
}
//base64转file文件
function base64ToFile(base64, filename) {filename = filename || String(new Date().getTime());let arr = base64.split(',');let mime = arr[0].match(/:(.*?);/)[1];let bstr = atob(arr[1]);let n = bstr.length;let u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new File([u8arr], filename, { type: mime });
}

http://www.ppmy.cn/devtools/23864.html

相关文章

Linux基本指令(3)

目录 时间相关的指令&#xff1a; 1.在显示方面&#xff0c;使用者可以设定欲显示的格式&#xff0c;格式设定为一个加好后接数个标记&#xff0c;其中常用的标记列表如下&#xff1a; 2.在设定时间方面&#xff1a; 3.时间戳&#xff1a; Cal指令&#xff1a; find指令&a…

php7.4在foreach中对使用数据使用无法??[]判读,无法使用引用传递

代码如下图&#xff1a;这样子在foreach中是无法修改class_history的。正确的应该是去掉??[]判断。 public function actionY(){$array [name>aaa,class_history>[[class_name>一班,class_num>1],[class_name>二班,class_num>2]]];foreach ($array[class_…

解析vue.config.js文件

一、用途 创建 Vue 项目时&#xff0c;默认情况下是没有 vue.config.js 文件的。Vue CLI 会提供一组默认的配置&#xff0c;用于构建和开发项目&#xff0c;这些配置在内部被封装好了&#xff0c;并不需要用户手动创建 vue.config.js 文件来进行配置。通过在项目根目录下创建 …

Stable Diffusion 常用放大算法详解

常用放大算法 图像放大算法大致有两种: 传统图像放大算法(Lantent、Lanczos、Nearest)AI图像放大算法(4x-UltraSharp、BSRGAN、ESRGAN等)传统图像放大算法是基于插值算法,计算出图像放大后新位置的像素值。AI图像放大算法,比一般的传统图像放大算法效果更好。 推荐放大…

数之寻软件怎么样?

数之寻软件是一款功能强大的数据恢复和备份软件&#xff0c;以下是对其特点和功能的详细评价&#xff1a; 一、数据恢复方面&#xff1a; 高效的数据恢复能力&#xff1a;数之寻软件采用了先进的算法和数据恢复技术&#xff0c;能够快速有效地恢复丢失或损坏的数据。无论是文…

Docker深入探索:网络与资源控制、数据管理与容器互联以及镜像生成

目录 一、 Docker网络 &#xff08;一&#xff09;Docker网络实现原理 &#xff08;二&#xff09;Docker网络模式 1. Bridge网络&#xff08;默认&#xff09; 2. Host网络 3. None网络 4. Container网络 5. 自定义网络 二、资源控制 &#xff08;一&#xff09;cgr…

机器人技术概述_3.机器人的分类

由于机器人的用途广泛&#xff0c;有许多种分类。行业不同&#xff0c;机器人的应用场景不一样&#xff0c;由于要求的不同&#xff0c;机器人的控制方式也存在许多差异&#xff0c;这里简要描述两种分类。 1.按控制方式分类 如果按照要求的控制方式分类&#xff0c;机器人可分…

树的层序遍历(详解)

下面以一道力扣题为例&#xff1a; 代码和解释如下&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(…