vue3.0 根据富文本html页面生成压缩包(含视频在线地址、图片在线地址、前端截图、前端文档)

vue3.0生成压缩包(含在线地址、前端截图、前端文档)

  • 需求描述
  • 效果
  • 开始
    • 下载插件包
    • 基本代码构造
  • 点击下载按钮
    • 1.截图content元素,并转化为pdf
      • canvas putImageData、getImageData
        • getImageData 获取指定矩形区域的像素信息
        • putImageData 将这些数据放回画布,从而实现对画布像素的编辑
    • 2.提取富文本视频
      • 正则 str.match(regex) regex.exec(str)知识补充
    • 3.base64和在线地址转blob
    • 4.下载成压缩包代码
  • 全部代码

需求描述

  • 内容区为富文本html渲染的内容
  • 要求点击下载后 需要有以下文件
  • 1.当前内容的页面,即渲染内容截图,且需要将截图转化成pdf
  • 2.提取html内容区的视频,单独下载
  • 3.后端返回的附件地址,下载附件文档
  • 4.再将以上文件总结成压缩包
    在这里插入图片描述

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开始

下载插件包

  • html2canvas 截图
npm install html2canvas --save
//或
yarn add html2canvas
  • jspd 生成pdf插件
npm install jspdf --save
//或
yarn add jspdf
  • jszip压缩文件
npm install jszip --save
//或
yarn add jszip
  • 保存文件的JavaScript库
npm install file-saver --save
//或
yarn add file-saver
  • 一起安装
npm i file-saver jszip html2canvas jspdf --save

基本代码构造

  <div v-html="contentValue" ></div> //用于展示<div ref="content" v-html="contentValue" id="content"></div> //用于截图并转化成pdf 我们调整使他不在可视范围内(不需要视频 同时把视频隐藏)-----// js
import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";
import JSZip from "jszip";
import FileSaver from "file-saver";
-----const content = ref<any>(null); //ref实例const contentValue= ref<string>(""); //contentValue富文本html数据const downloadFileUrl = ref<string[]>([]); //压缩包下载数组const pdfValue=ref<string>("")// 获取详情const getDetail = async (id: string) => {const res = await 你的详情api({id,});if (res) {contentValue.value = res;// 添加视频链接downloadFileUrl.value = getVideo(res.content);if (res?.annexList?.length) {// 添加附件链接downloadFileUrl.value.push(res.annexList[0].url);}}};----- //less#content {position: absolute;left: 100000px;top: 0;/deep/video {display: none;}
}

点击下载按钮

1.截图content元素,并转化为pdf

  const exportToPDF = () => {const dom = content.value;html2canvas(dom, {useCORS: true, //解决网络图片跨域问题width: dom.width,height: dom.height,windowWidth: dom.scrollWidth,dpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍scale: 4, // 按比例增加分辨率backgroundColor: "#fff", // 背景}).then((canvas) => {const pdf = new jsPDF("p", "mm", "a4"); // A4纸,纵向const ctx = canvas.getContext("2d");const a4w = 170;const a4h = 250; // A4大小,210mm x 297mm,四边各保留20mm的边距,显示区域170x257const imgHeight = Math.floor((a4h * canvas.width) / a4w); // 按A4显示比例换算一页图像的像素高度let renderedHeight = 0;while (renderedHeight < canvas.height) {const page = document.createElement("canvas");page.width = canvas.width;page.height = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页// 用getImageData剪裁指定区域,并画到前面创建的canvas对象中page.getContext("2d").putImageData(ctx.getImageData(0,renderedHeight,canvas.width,Math.min(imgHeight, canvas.height - renderedHeight)),0,0);pdf.addImage(page.toDataURL("image/jpeg", 1.0),"JPEG",20,20,a4w,Math.min(a4h, (a4w * page.height) / page.width)); // 添加图像到页面,保留10mm边距renderedHeight += imgHeight;if (renderedHeight < canvas.height) {pdf.addPage(); // 如果后面还有内容,添加一个空页}}pdfValue.value = pdf.output("datauristring"); // 获取base64Pdf});

canvas putImageData、getImageData

getImageData 获取指定矩形区域的像素信息

ctx.getImageData(x,y,width,height)

属性描述
x开始复制的左上角位置的 x 坐标(以像素计)。
y开始复制的左上角位置的 y 坐标(以像素计)。
width要复制的矩形区域的宽度。
height要复制的矩形区域的高度。
putImageData 将这些数据放回画布,从而实现对画布像素的编辑

ctx.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)

属性描述
imgData规定要放回画布的 ImageData 对象 ;
xImageData 对象左上角的 x 坐标,以像素计;
yImageData 对象左上角的 y 坐标,以像素计;
dirtyX可选。水平值(x),以像素计,在画布上放置图像的位置;
dirtyY可选。水平值(y),以像素计,在画布上放置图像的位置;
dirtyWidth可选。在画布上绘制图像所使用的宽度;
dirtyHeight可选。在画布上绘制图像所使用的高度
  • ImageData
    结构:每个ImageData对象包含三个属性:
    width:图像数据的宽度(以像素为单位)。
    height:图像数据的高度(以像素为单位)。
    data:一个一维数组,包含图像数据的RGBA值。每个像素由四个连续的数组元素表示,分别对应红、绿、蓝和透明度(alpha)通道。每个通道的值都是一个0到255之间的整数。

2.提取富文本视频

  //单独提取富文本视频链接const getVideo = (str: string) => {const regex = /<video.*?src=["']([^"']+)["']/g;const videoTags = str.match(regex);//  console.log(videoTags) arr[0]代表第一个匹配项,arr[1]代表第二个匹配项...,数组length代表有几个匹配项//  ["<video poster="" controls="true" width="auto" height="auto"><source src="你的地址""]const videoUrls = [];if (videoTags) {for (let i = 0; i < videoTags.length; i++) {const match = regex.exec(videoTags[i]);//  console.log(match) [0]代表匹配项,[≥1]代表捕获的group。index是匹配的第一个字符索引,input代表str字符串//  0: "<video poster=\"\" controls=\"true\" width=\"auto\" height=\"auto\"><source src=\"你的地址\""//  1: "你的地址"//  index: 0//input: "<video poster=\"\" controls=\"true\" width=\"auto\" height=\"auto\"><source src=\"你的地址\""if (match) {videoUrls.push(match[1]); // match[1] 匹配到的视频地址}}}return videoUrls;};//ps 单独提取文字正则 str.replace(/<[^>]+>/g, "")

正则 str.match(regex) regex.exec(str)知识补充

在这里插入图片描述

3.base64和在线地址转blob

const dataURLtoFile = (dataurl: string, type: string) => {return new Promise((resolve, reject) => {if (type === "http") {//通过请求获取文件blob格式let xmlhttp = new XMLHttpRequest();xmlhttp.open("GET", url, true);xmlhttp.responseType = "blob";xmlhttp.onload = function () {if (xmlhttp.status == 200) {resolve(xmlhttp.response);} else {reject(xmlhttp.response);}};xmlhttp.send();} else {let arr = dataurl.split(",");let bstr = atob(arr[1]);let n = bstr.length;let u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}resolve(u8arr);}});};

4.下载成压缩包代码

  // 下载全部附件const downloadFile = async () => {var blogTitle = `附件批量下载`; // 下载后压缩包的名称var zip = new JSZip();var promises = [];for (let item of downloadFileUrl.value) {if (item) {// 在线地址转blob 添加至进程const promise = dataURLtoFile(item, "http").then((data) => {// 下载文件, 并存成ArrayBuffer对象(blob)let fileName = getFileName(item); //文件名 这里可以自己命名 不用调这个方法 博主需求是截取地址后面的zip.file(fileName, data, { binary: true });});promises.push(promise);} else {// answer地址不存在时提示alert(`附件地址错误,下载失败`);}}// 单独加富文本pdf blobif (pdfUrl.value) {const contentPromise = dataURLtoFile(pdfUrl.value, "base64").then((data) => {zip.file("content.pdf", data, { binary: true });});promises.push(contentPromise);}Promise.all(promises).then(() => {zip.generateAsync({type: "blob",}).then((content) => {// 生成二进制流FileSaver.saveAs(content, blogTitle); // 利用file-saver保存文件  blogTitle:自定义文件名});}).catch((res) => {alert("文件压缩失败");});};// 获取文件名const getFileName = (filePath: string) => {var startIndex = filePath.lastIndexOf("/");if (startIndex != -1)return filePath.substring(startIndex + 1, filePath.length).toLowerCase();else return "";};

全部代码

<template><div><div><div><p@click="downloadAllFile()"><a-icon type="icon-xiazai"></w-icon> 下载</p></div></div><divclass="text-content"v-html="detaileInfo.content"></div><divclass="text-content"ref="content"id="content"><div v-html="detaileInfo.content"></div></div></div>
</template><script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";
import JSZip from "jszip";
import FileSaver from "file-saver";export default defineComponent({name: "announcementDetail",setup() {const detaileInfo = ref<any>({});const content = ref<any>(null);const downloadFileUrl = ref<string[]>([]);const pdfUrl = ref<string>("");// 获取详情const getDetail = async () => {const res = await 你的api({id: "你的id",});if (res) {detaileInfo.value = res;// 添加视频链接downloadFileUrl.value = getVideo(res.content);if (res?.annexList?.length) {// 添加附件链接downloadFileUrl.value.push(res.annexList[0].url);}}};//单独提取富文本视频链接const getVideo = (str: string) => {const regex = /<video.*?src=["']([^"']+)["']/g;const videoTags = str.match(regex);const videoUrls = [];if (videoTags) {for (let i = 0; i < videoTags.length; i++) {const match = regex.exec(videoTags[i]);if (match) {videoUrls.push(match[1]); // match[1] 匹配到的视频地址}}}return videoUrls;};const downloadAllFile = () => {exportToPDF();};const exportToPDF = () => {const dom = content.value;html2canvas(dom, {useCORS: true, //解决网络图片跨域问题width: dom.width,height: dom.height,windowWidth: dom.scrollWidth,dpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍scale: 4, // 按比例增加分辨率backgroundColor: "#fff", // 背景}).then((canvas) => {// eslint-disable-next-line new-capconst pdf = new jsPDF("p", "mm", "a4"); // A4纸,纵向const ctx = canvas.getContext("2d");const a4w = 170;const a4h = 250; // A4大小,210mm x 297mm,四边各保留20mm的边距,显示区域170x257const imgHeight = Math.floor((a4h * canvas.width) / a4w); // 按A4显示比例换算一页图像的像素高度let renderedHeight = 0;while (renderedHeight < canvas.height) {const page = document.createElement("canvas");page.width = canvas.width;page.height = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页// 用getImageData剪裁指定区域,并画到前面创建的canvas对象中page.getContext("2d").putImageData(ctx.getImageData(0,renderedHeight,canvas.width,Math.min(imgHeight, canvas.height - renderedHeight)),0,0);pdf.addImage(page.toDataURL("image/jpeg", 1.0),"JPEG",20,20,a4w,Math.min(a4h, (a4w * page.height) / page.width)); // 添加图像到页面,保留10mm边距renderedHeight += imgHeight;if (renderedHeight < canvas.height) {pdf.addPage(); // 如果后面还有内容,添加一个空页}}pdfUrl.value = pdf.output("datauristring"); // 获取base64PdfdownloadFile();});};//返回blob值 在线地址和前端生成的base64编码const dataURLtoFile = (dataurl: string, type: string) => {return new Promise((resolve, reject) => {if (type === "http") {//通过请求获取文件blob格式let xmlhttp = new XMLHttpRequest();xmlhttp.open("GET", url, true);xmlhttp.responseType = "blob";xmlhttp.onload = function () {if (xmlhttp.status == 200) {resolve(xmlhttp.response);} else {reject(xmlhttp.response);}};xmlhttp.send();} else {let arr = dataurl.split(",");let bstr = atob(arr[1]);let n = bstr.length;let u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}resolve(u8arr);}});};// 下载全部附件const downloadFile = async () => {var blogTitle = `附件批量下载`; // 下载后压缩包的名称var zip = new JSZip();var promises = [];for (let item of downloadFileUrl.value) {if (item) {// 在线地址转blob 添加至进程const promise = dataURLtoFile(item, "http").then((data) => {// 下载文件, 并存成ArrayBuffer对象(blob)let fileName = getFileName(item); //文件名zip.file(fileName, data, { binary: true });});promises.push(promise);} else {alert(`附件地址错误,下载失败`);}}// 单独加富文本blobif (pdfUrl.value) {const contentPromise = dataURLtoFile(pdfUrl.value, "base64").then((data) => {zip.file("content.pdf", data, { binary: true });});promises.push(contentPromise);}Promise.all(promises).then(() => {zip.generateAsync({type: "blob",}).then((content) => {// 生成二进制流FileSaver.saveAs(content, blogTitle); // 利用file-saver保存文件  blogTitle:自定义文件名});}).catch((res) => {alert("文件压缩失败");});};// 获取文件名const getFileName = (filePath: string) => {var startIndex = filePath.lastIndexOf("/");if (startIndex != -1)return filePath.substring(startIndex + 1, filePath.length).toLowerCase();else return "";};onMounted(() => {getDetail();});return {content,detaileInfo,downloadAllFile,};},
});
</script><style lang="less" scoped>
.text-content {font-family: "PingFang SC";font-weight: 400;font-size: 15px;letter-spacing: 0.06px;line-height: 30px;text-align: left;color: #666;width: 100%;/deep/video,/deep/img {width: 100%;}
}
#content {position: absolute;left: 100000px;top: 0;.label {display: inline-block;}/deep/video {display: none;}
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/62520.shtml

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

相关文章

图像小波去噪与总变分去噪详解与Python实现

目录 图像小波去噪与总变分去噪详解与实现1. 基础概念1.1 噪声类型及去噪问题定义1.2 小波去噪算法基础1.3 总变分去噪算法基础2. 小波去噪算法2.1 理论介绍2.2 Python实现及代码详解2.3 案例分析3. 总变分去噪算法3.1 理论介绍3.2 Python实现及代码详解3.3 案例分析4. 两种算法…

单细胞细胞通讯全流程分析教程,代做分析和辅导

0. 分析参数文件和细胞通讯的演示数据 0.1 细胞通讯分析总的参数文件&#xff0c;后面部分细胞通讯分析模块会用到 分析参数文件 参数文件名称&#xff1a;total_analysis_params_demo.xlsx &#xff0c;很多分析模块都是这个总的参数文件&#xff0c;我的这个总的参数文件如…

auto与decltype

auto: 1.定义&#xff1a; 在C中&#xff0c; auto 是一个类型说明符&#xff0c;它让编译器在编译阶段自动推导变量的类型&#xff0c;其类型取决于初始化表达式的类型。auto 在声明变量时使用&#xff0c;编译器会根据变量初始化表达式自动推断类型。 #include<iostrea…

[Code]R2U-Net中的眼部血管分割

DenseUnet.py import torch import torch.nn as nn import torch.nn.functional as F# 定义一个名为Single_level_densenet的类,继承自nn.Module,它构建了一个单层级的DenseNet结构 class Single_level_densenet(nn.Module):def __init__(self, filters, num_conv=4):super…

Java中的“封装“详解

封装&#xff08;Encapsulation&#xff09;是面向对象编程&#xff08;OOP&#xff09;的四大基本特性之一。它通过将数据和操作数据的方法绑定在一起&#xff0c;并隐藏对象的内部实现细节&#xff0c;只提供有限的访问接口来实现。这种机制不仅提高了代码的安全性&#xff0…

深度学习常用方法(一)

1. Dropout 的原理 Dropout 是一种防止神经网络过拟合&#xff08;学习得过于复杂&#xff0c;导致泛化能力差&#xff09;的方法。 原理&#xff1a;在每次训练时&#xff0c;随机“丢弃”一部分神经元&#xff08;即暂时让它们失效&#xff0c;设置为零&#xff09;&#x…

C++趣味编程:基于树莓派Pico的模拟沙漏-倾斜开关与LED的互动实现

沙漏,作为一种古老的计时工具,利用重力让沙子通过狭小通道,形成了计时效果。在现代,我们可以通过电子元件模拟沙漏的工作原理。本项目利用树莓派Pico、倾斜开关和LED,实现了一个电子沙漏。以下是项目的详细技术解析与C++代码实现。 一、项目概述 1. 项目目标 通过倾斜开关…

【Oracle】个人收集整理的Oracle常用SQL及命令

【建表】 create table emp( id number(12), name nvarchar2(20), primary key(id) ); 【充值一】 insert into emp select rownum,dbms_random.string(*,dbms_random.value(6,20)) from dual connect by level<101; 【充值二】 begin for i in 1..100 loop inser…

RAG (Retrieval Augmented Generation) 检索增强和生成

1 RAG技术简介 1.1 RAG技术概述 RAG&#xff08;Retrieval Augmented Generation&#xff09; 是一种结合了检索&#xff08;Retrieval&#xff09;和生成&#xff08;Generation&#xff09;的技术&#xff0c;旨在通过利用外部知识库来增强大型语言模型&#xff08;LLMs&am…

Android 编译和使用libheif

项目中需要使用libheif,libde265,libyuv。一下是相应的cmakelist.txt。这里直接使用了静态库。 里面涉及到c包的链接&#xff0c;需要stdc。 ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/liblibde265.a这个路径由于操作过程中copy出现问题&#xff0c;多了一层路径&…

【无标题】多模态对齐

第一章 绪论 1.1 研究背景和意义 在现实生活中,人类往往是以事件为单位来认识和理解世界的,一个事件包 含了时间、地点、参与者等多个静态因素。将这些事件信息从非结构化数据中提 取出来,已逐渐成为信息抽取领域的关键任务之一。事件通常并非孤立存在,而 是彼此关联,它…

DIY-Tomcat part 3 实现对动态资源的请求

实现ServletRequest package connector;import javax.servlet.RequestDispatcher; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.i…

黑马程序员Java笔记整理(day05)

1.面向对象编程 2.用法 3.对象是什么 4.对象在计算机中是啥 5.无参与有参构造器 小结: 6.this的作用 7.小结 8.封装 9.小结 10.实体类 11.小结 12.static 13.小结 14.static修饰方法 15.static应用前景 16.几个注意事项 17.java中可以直接用类的名字创建数组&#xff0c;如: M…

Perforce SAST专家详解:自动驾驶汽车的安全与技术挑战,Klocwork、Helix QAC等静态代码分析成必备合规性工具

自动驾驶汽车安全吗&#xff1f;现代汽车的软件包含1亿多行代码&#xff0c;支持许多不同的功能&#xff0c;如巡航控制、速度辅助和泊车摄像头。而且&#xff0c;这些嵌入式系统中的代码只会越来越复杂。 随着未来汽车的互联程度越来越高&#xff0c;这一趋势还将继续。汽车越…

《Python基础》之数据加密模块hashlib的用法

目录 一、简介 二、用法 步骤一、导入hashlib库 步骤二、创建哈希对象 步骤三、往哈希对象中传值 1、可以在创建对象的时候传值 2、使用updata传值 步骤四、获取经过哈希对象加密后的值 三、注意事项 1、编码问题 2、安全性 3、多次传值 四、总结 一、简介 hashli…

QT实战--qt各种按钮实现

本篇介绍qt一些按钮的实现&#xff0c;包括正常按钮&#xff1b;带有下拉箭头的按钮的各种实现&#xff1b;按钮和箭头两部分分别响应&#xff1b;图片和按钮大小一致&#xff1b;图片和按钮大小不一致的处理&#xff1b;文字和图片位置的按钮 效果图如下&#xff1a; 详细实现…

恶补英语初级第12天,《描述进行中的动作》

对话 Where’s Sally, Jack? She’s in the garden, Jane. What’s she doing? she’s sitting under the tree. Is Tim in the garden, too? Yes, he is. He’s climbing the tree. I beg your pardon? Who’s climbing the tree? Tim is. What about the dog? The dog’…

联表查询,外键

联表查询 使用多表可以减少冗余&#xff0c;便于增删改操作&#xff0c;查询操作复杂 表与表之间通过主键和外键关联 外键 如果一张表的某个列指向另一个表的主键列 &#xff0c;就称之为外键列 -- 添加外键 alter table t_emp add constraint 外键名 foreign key (depart)…

【OJ】前K个高频单词和单词识别和两个数组的交集

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 692. 前K个高频单词1.1 分析1.2 代码 2. KY264 单词识别2.1 分析2.2 代码 3. 349. 两个数组的交集3.1 分析3.2 代码 1. 692. 前K个高频单词 1.1 分析 先试用map来统计每个单词出现的次数&#xff1a; map<string,i…

分布式协同 - 分布式锁一二事儿

文章目录 导图Pre概述概述1. 分布式互斥和临界资源的协调2. 分布式锁的基本原理3. 分布式锁的实现方式a. 基于数据库实现的分布式锁b. 基于Redis实现的分布式锁c. 基于Zookeeper实现的分布式锁 4. 高并发场景下的分布式锁优化a. 分段锁&#xff08;Sharded Locks&#xff09;b.…