当前位置: 首页 > news >正文

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.lryc.cn/news/496664.html

相关文章:

  • WPF+LibVLC开发播放器-LibVLC在C#中的使用
  • 消息中间件-Kafka1-实现原理
  • 2023年华数杯数学建模B题不透明制品最优配色方案设计解题全过程文档及程序
  • Mysql事务常见面试题 -- 事务的特性 ,并发事务问题 , undo_log和redo_log , 分布式事务
  • 【数据库系列】Spring Boot如何配置Flyway的回调函数
  • 分布式推理框架 xDit
  • DR.KNOWS:医疗图谱UMLS + 图神经网络 + LLM 模拟医生的诊断推理过程, 从症状出发找到可能的诊断结果
  • 缓存雪崩 详解
  • 使用 Vite 创建 Vue3+TS 项目并整合 ElementPlus、Axios、Pinia、Less、Vue-router 等组件或插件
  • Flink随笔 20241203 Flink重点内容
  • shell脚本实战
  • 【机器学习】分类任务: 二分类与多分类
  • FreeSWITCH mod_conference 的按键会控
  • 串口工作方式
  • 统计Nginx的客户端IP,可以通过分析Nginx的访问日志文件来实现
  • Apache Airflow 快速入门教程
  • 42 基于单片机的智能浇花系统
  • 乐橙云小程序插件接入HbuilderX
  • VoCo-LLaMA: Towards Vision Compression with Large Language Models
  • Vue+vite 组件开发的环境准备
  • 基于社区发现的GraphRAG思路
  • react学习记录
  • Day2——需求分析与设计
  • VScode离线下载扩展安装
  • 【机器学习】机器学习的基本分类-监督学习-决策树(Decision Tree)
  • 【第 1 章 初识 C 语言】1.8 使用 C 语言的 7 个步骤
  • Docker 使用 Dockerfile 文件打包部署前端项目
  • HTML-全
  • 高效流程图绘制:开发设计流程图利器
  • 数据仓库的概念