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

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.mzph.cn/bicheng/5008.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库|TiDB-Server API的高效应用指南

一、API介绍 1.Status 显示TiDB 连接数、版本和git_hash 信息 tidb-server_ip:status_port/status { "connections": 0, "version": "5.7.25-TiDB-v6.1.1", "git_hash": "5263a0abda61f102122735049fd0dfadc7b7f822" } 2.St…

mysql-sql-练习题-4-标记

标记 连续登录2-7天用户建表排名找规律 最大连胜次数建表只输出连胜结果输出所有连续结果 连续登录2-7天用户 建表 create table continuous_login(user_id1 integer comment 用户id,date_login date comment 登陆日期 ) comment 用户登录表;insert into continuous_login val…

LT2611UX四端口 LVDS转 HDMI2.0,带音频

描述LT2611UX 是一款面向机顶盒、DVD 应用的高性能 LVDS 至 HDMI2.0 转换器。LVDS输入可配置为单端口、双端口或四端口&#xff0c;具有1个高速时钟通道和3~4个高速数据通道&#xff0c;工作速率最高为1.2Gbps/通道&#xff0c;可支持高达19.2Gbps的总带宽。LT2611UX 支持灵活的…

002 springCloudAlibaba Sentinel流控-关联

当与A关联的资源B达到阀值后&#xff0c;就限流A自己 文章目录 FlowLimitController.javaSentinelServerApplication.javaServletInitializer.javaapplication.yamlpom.xmlpom.xml 启动Sentinel8080 - java -jar sentinel-dashboard-1.7.0.jar 启动微服务8401 启动8401微服务…

一款非常不错的音频录制播放Python库

声音无处不在&#xff0c;在计算机应用中也是。Python拥有丰富的库来处理声音&#xff0c;sounddevice就是其中一个非常实用的库。本文将介绍sounddevice库的特点、工作原理以及使用方式&#xff0c;帮助你大家理解和运用这个库。 简介 sounddevice是一个Python库&#xff0c…

72、栈-每日温度

思路&#xff1a; 第一种方法&#xff0c;双循环&#xff0c;第一层循环拿出一个元素&#xff0c;第二层循环寻找最近比当前大的元素位置。 第二种方法&#xff1a;使用栈来实现。 初始化&#xff1a; int[] ans 用来存储每一天之后多少天温度会升高。Stack<Integer> 用…

软件重构的要点及注意事项

重构是软件开发过程中改进现有代码结构和设计而不改变其外在行为的过程。良好的重构实践可以提高代码质量、可读性、可维护性&#xff0c;并促进后续的开发工作。以下是重构的基本步骤、要点及注意事项&#xff1a; 重构的基本步骤 明确重构目的&#xff1a;确定重构的动机&a…

Java | AI+编程 | 如何使用通义灵码提升开发效率

大家好&#xff0c;我是程序员影子 | 全网同名 一名致力于帮助更多朋友快速入门编程的程序猿 今天&#xff0c;我将以小白入门的视角带着大家学会如何在Idea上使用通义灵码&#xff0c;提高开发效率&#xff0c;减少重复工作&#xff1b;话不多说&#xff0c;我们直接进入正题…

点云和去噪

1. 什么是去噪 在人工智能领域中&#xff0c;去噪指的是利用算法和模型来消除或减少数据中的噪声&#xff0c;以提高数据的质量和可用性。噪声是指数据中不希望的随机扰动或干扰&#xff0c;可能由于采集过程中的各种因素引入&#xff0c;例如传感器误差、环境干扰、信号衰减等…

golang beego结合wire依赖注入及自动路由

1 安装wire 1.1 通过命令直接安装 go install github.com/google/wire/cmd/wirelatest 1.2 通过go get方式安装 go get github.com/google/wire/cmd/wire进入目录编译 cd C:\Users\leell\go\pkg\mod\github.com\google\wirev0.6.0\cmd\wire go build 然后将wire.exe移动到…

广交会烹饪机器人用上大模型 支付宝小程序云提供技术支持

近日&#xff0c;第135届广交会正在火热进行&#xff0c;记者获悉&#xff0c;支付宝小程序云助力合作伙伴田螺云厨&#xff0c;在烹饪机器人上开始用上大模型技术。各类智能产品的亮相&#xff0c;从中国制造迈向中国创造&#xff0c;也成为广交会的一个亮点。 &#xff08;图…

鲲鹏华为云--OBS

文章目录 1.创建桶2.上传对象3.下载对象4.分享对象5. 删除对象6.删除桶 1.创建桶 创建桶 2.上传对象 点击创建的桶–“上传对象” 拖拽本地文件或文件夹至“上传对象”区域框内添加待上传的文件。 也可以通过单击“上传对象”区域框内的“添加文件”&#xff0c;选择本地…

给vue配置路径别名@

使用vite构建的项目 在vite.config.js中进行别名的配置 import { defineConfig } from vite import vue from vitejs/plugin-vueexport default defineConfig({plugins: [vue()],resolve: {alias: {: /src}} }) //这样&#xff0c;你就可以使用作为别名来引用/src目录下的文件…

C语言函数指针的使用、函数指针数组及使用、指向函数指针数组的指针,指针进阶版的冒泡排序等介绍

文章目录 前言一、函数指针的使用1. 加减乘除计算器普通实现2. 加减乘除计算机函数指针实现 二、函数指针数组1. 函数指针数组的书写2. 两个有趣的代码3. 函数指针数组的使用 三、指向函数指针数组的指针四、指针进阶_冒泡排序1.整型冒泡排序2. C语言qsort函数3. 仿写C语言qsor…

ChatGLM2-6B的部署步骤_A3

ChatGLM2-6B 下载地址 一、VisualGLM-6B环境安装 1、硬件配置 操作系统&#xff1a;Ubuntu_64&#xff08;ubuntu22.04.3&#xff09; GPU&#xff1a;4050 显存&#xff1a;16G 2、配置环境 建议最好自己新建一个conda环境 conda create -n chatglm2 python3.8pip …

【Java】HOT100 贪心算法

目录 理论基础 一、简单贪心 LeetCode455&#xff1a;分发饼干 二、中等贪心 2.1 序列问题 LeetCode376&#xff1a;摆动序列 2.2 贪心股票问题 LeetCode121&#xff1a;买卖股票的最佳时机 LeetCode121&#xff1a;买卖股票的最佳时机ii 2.3 两个维度权衡问题 LeetCode135&…

KVM安装Ubuntu24.04简要坑点以及优点

本机环境是ubuntu22.04的环境&#xff0c;然后是8核16线程 ssd是500的 目前对于虚拟机的选择&#xff0c;感觉kvm确实会更加流畅&#xff0c;最重要的一点是简洁&#xff0c;然后实际安装效果也比较的好&#xff0c;如果对于速度方面希望快一点&#xff0c;并且流畅一点的话这…

数据集市与数据仓库

一、概念 数据仓库&#xff08;Data Warehouse&#xff09;和数据集市&#xff08;Data Mart&#xff09;是企业中用于存储和管理数据的两种常见架构。它们在设计和应用上有一些区别&#xff0c;下面我简要介绍一下&#xff1a; 数据仓库&#xff08;Data Warehouse&#xff0…

UE Snap03 启动参数设置

UE Snap03 启动参数设置 UE打包后传入自定义参数及解析。 void UGameInstance::StartGameInstance() {Super::StartGameInstance();UE_LOG(LogTemp, Warning, TEXT("--StartGameInstance--"));FString param;FParse::Value(FCommandLine::Get(), TEXT("-UserN…

Linux testparm命令教程:检查Samba配置文件的内部正确性(附案例详解和注意事项)

Linux testparm命令介绍 testparm&#xff08;test parameter&#xff09;命令是Samba套件的一部分&#xff0c;用于检查smbd配置文件&#xff08;通常是smb.conf&#xff09;的内部正确性。如果testparm命令的语法检查成功&#xff0c;那么可以确保Samba服务能够正确地加载配…