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,一经查实,立即删除!

相关文章

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

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

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

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

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…

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; 详细实现…

【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.…

Vue-01

Vue框架 Vue官网&#xff1a; Vue.js 框架 数据模型和view的通信就是依靠viewmodel的关键。 目前主流版本仍然是vue2版本。 Vue快速入门 1.新建一个HTML文件&#xff0c;引入Vue.js文件。Vue.js文件是官方引入的一个文件&#xff0c;我们如果要使用Vue就必须引入这个文件。…

【微服务】Nacos

一、安装 1、官网地址&#xff1a;https://nacos.io/download/nacos-server/ 2、启动&#xff1a;找到bin目录下的startup.cmd双击启动&#xff0c;或者打开一个命令窗口输入&#xff1a; startup.cmd -m standalone双击启动后如下&#xff1a;可以访问控制台地址 访问后的…

2025 - AIDD - python的autodock vina 批量分子对接改进版本2.0-全自动对接,完全全自动对接

2025 - AIDD - python的autodock vina 批量分子对接改进版本2.0-全自动对接&#xff0c;完全全自动对接 import warnings from pathlib import Path import subprocess from itertools import product import numpy as np import pandas as pd from MDAnalysis import Univers…

【uniapp】轮播图

前言 Uniapp的swiper组件是一个滑块视图容器组件&#xff0c;可以在其中放置多个轮播图或滑动卡片。它是基于微信小程序的swiper组件进行封装&#xff0c;可以在不同的平台上使用&#xff0c;如微信小程序、H5、App等。 效果图 前端代码 swiper组件 <template><vi…

由于导包而引发的错误

今天在调试时发现删除功能无论如何都无法实现&#xff0c;于是调试找到了mapper层的错误但不知道为什么报错。以下是报错信息。 Caused by: org.apache.ibatis.binding.BindingException: Parameter userIds not found. Available parameters are [arg0, collection, list]at o…

STM32 ADC --- 知识点总结

STM32 ADC — 知识点总结 文章目录 STM32 ADC --- 知识点总结cubeMX中配置注解单次转换模式、连续转换模式、扫描模式单通道采样的情况单次转换模式&#xff1a;连续转换模式&#xff1a; 多通道采样的情况禁止扫描模式&#xff08;单次转换模式或连续转换模式&#xff09;单次…

Web day02 Js Vue Ajax

目录 1.javascript: 1.js的引入方式&#xff1a; 2.js变量 & 数据类型 & 输出语句&#xff1a; 模板字符串&#xff1a; 3.函数 & 自定义对象&#xff1a; 4. json 字符串 & DOM操作&#xff1a; 5. js事件监听&#xff1a; 6.js的模块化导入或者导出&a…

Kylin Server V10 下 RocketMQ 主备自动切换模式部署

一、NameServer简介 NameServer 是一个注册中心,提供服务注册和服务发现的功能。NameServer 可以集群部署,集群中每个节点都是对等的关系,节点之间互不通信。 服务注册 Broker 启动的时候会向所有的 NameServer 节点进行注册,注意这里是向集群中所有的 NameServer 节点注册…

芯片测试-RF中的S参数,return loss, VSWR,反射系数,插入损耗,隔离度等

RF中的S参数&#xff0c;return loss, VSWR&#xff0c;反射系数&#xff0c;插入损耗&#xff0c;隔离度 &#x1f4a2;S参数&#x1f4a2;&#x1f4a2;S11与return loss&#xff0c;VSWR&#xff0c;反射系数&#x1f4a2;&#x1f4a2;S21&#xff0c;插入损耗和增益&#…

一个开源轻量级的服务器资源监控平台,支持告警推送

大家好&#xff0c;今天给大家分享一款开源的轻量级服务器资源监控工具Beszel&#xff0c;提供历史数据记录、Docker容器统计信息监控以及多种警报功能&#xff0c;用于监控服务器资源。 项目介绍 Beszel由hub&#xff08;中心服务器端应用&#xff0c;基于PocketBase构建&…