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

news2024/11/30 5:40:04

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的边距,显示区域170x257
        const 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 blob
      if (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>
    <div
      class="text-content"
      v-html="detaileInfo.content"
    ></div>
    <div
      class="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-cap
        const pdf = new jsPDF("p", "mm", "a4"); // A4纸,纵向
        const ctx = canvas.getContext("2d");
        const a4w = 170;
        const a4h = 250; // A4大小,210mm x 297mm,四边各保留20mm的边距,显示区域170x257
        const 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"); // 获取base64Pdf
        downloadFile();
      });
    };
    //返回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(`附件地址错误,下载失败`);
        }
      }
      // 单独加富文本blob
      if (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.coloradmin.cn/o/2250226.html

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

相关文章

由于导包而引发的错误

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

黑马2024AI+JavaWeb开发入门Day04-SpringBootWeb入门-HTTP协议-分层解耦-IOCDI飞书作业

视频地址&#xff1a;哔哩哔哩 讲义作业飞书地址&#xff1a;day04作业&#xff08;IOC&DI&#xff09; 作业很简单&#xff0c;主要是练习拆分为三层架构controller、service、dao&#xff0c;并基于IOC & DI进行解耦。 1、结构&#xff1a; 2、代码 网盘链接&…

【LeetCode: 145. 二叉树的后序遍历 + 栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

哈希表算法题

目录 题目一——1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 1.1.暴力解法1 1.2.暴力解法2 1.2.哈希表解法 题目二——面试题 01.02. 判定是否互为字符重排 - 力扣&#xff08;LeetCode&#xff09; 题目三——217. 存在重复元素 - 力扣&#xff08;LeetCode&…

Galaxy预测比特币期权活跃交易将持续至2027年,特朗普执政中期

随着比特币及其相关产品的交易量不断增加&#xff0c;加密货币市场的期权交易也迎来了前所未有的活跃。Galaxy Digital的交易团队指出&#xff0c;贝莱德IBIT ETF期权在美国股市的交易量已经创下了新纪录。首日交易量高达353,716份合约&#xff0c;几乎与2012年Facebook期权上线…

Java-GUI(登录界面示例)

简述&#xff1a; 步骤&#xff1a; (1)构造界面(将组件对象加入容器对象,注意&#xff1a;应设定对容器对象的布局策略&#xff09; (2)为界面加入事件响应处理(如单击按钮&#xff09; 实现&#xff1a; 两种方式实现&#xff0c;只有用户名为"admin"且密码为…

屏幕分辨率|尺寸|颜色深度指纹

一、前端通过window.screen接口获取屏幕分辨率 尺寸 颜色深度&#xff0c;横屏竖屏信息。 二、window.screen c接口实现&#xff1a; 1、third_party\blink\renderer\core\frame\screen.idl // https://drafts.csswg.org/cssom-view/#the-screen-interface[ExposedWindow ] …

【论文阅读】三平面相关与变体

文章目录 1. 【CVPR2023】Tri-Perspective View for Vision-Based 3D Semantic Occupancy Prediction动机可视化方法Pipeline 2. 【2023/08/31】PointOcc: Cylindrical Tri-Perspective View for Point-based 3D Semantic Occupancy Prediction动机&#xff08;针对雷达点云、与…

Java - JSR223规范解读_在JVM上实现多语言支持

文章目录 1. 概述2. 核心目标3. 支持的脚本语言4. 主要接口5. 脚本引擎的使用执行JavaScript脚本执行groovy脚本1. Groovy简介2. Groovy脚本示例3. 如何在Java中集成 Groovy4. 集成注意事项 6. 与Java集成7. 常见应用场景8. 优缺点9. 总结 1. 概述 JSR223&#xff08;Java Spe…

定时/延时任务-ScheduledThreadPoolExecutor的使用

文章目录 1. 概要2. 固定速率和固定延时2.1 固定速率2.2 固定延时 3. API 解释3.1 schedule3.2 固定延时 - scheduleWithFixedDelay3.2 固定速率 - scheduleWithFixedDelay 4. 小结 1. 概要 前三篇文章的地址&#xff1a; 定时/延时任务-自己实现一个简单的定时器定时/延时任…

什么是sfp,onu,​为什么PON(​俗称“光猫”​)模块使用SC光纤接口

在现代网络设备中&#xff0c;我们经常会看到SFP或SFP接口的身影&#xff0c;这些接口有时被简称为光口&#xff0c;但这个称呼并不严谨。有些厂商则称之为多功能口或多用途口&#xff0c;然而这对于不了解的人来说可能还是一头雾水。SFP&#xff0c;即Small Form-Factor Plugg…

005 MATLAB符号微积分

前言&#xff1a; 在MATLAB中&#xff0c;数值与符号的主要区别在于它们的处理方式和应用场景 数值计算适用于实际的数值计算问题&#xff0c;如矩阵运算、数据分析等。符号计算适用于符号推导、公式化简和符号解析&#xff0c;如理论物理和工程计算。 01 符号对象 1.基本符…

深入实践:从零开始掌握GPT的应用开发

1. 为什么选择GPT&#xff1f; GPT&#xff08;Generative Pre-trained Transformer&#xff09;是当下最具影响力的语言生成模型之一&#xff0c;适用于生成文本、分析语言情感、翻译、多任务对话等多种场景。相比传统算法和模型&#xff0c;GPT有以下显著优势&#xff1a; …

WRF-Chem模式安装、环境配置、原理、调试、运行方法;数据准备及相关参数设置方法

大气污染是工农业生产、生活、交通、城市化等方面人为活动的综合结果&#xff0c;同时气象因素是控制大气污染的关键自然因素。大气污染问题既是局部、当地的&#xff0c;也是区域的&#xff0c;甚至是全球的。本地的污染物排放除了对当地造成严重影响外&#xff0c;同时还会在…

开源项目:纯Python构建的中后台管理系统

来源&#xff1a;Python大数据分析 费弗里 大家好我是费老师&#xff0c;目前市面上有很多开源的「中后台管理系统」解决方案&#xff0c;复杂如「若依」那种前端基于Vue&#xff0c;后端基于Java的框架&#xff0c;虽然其提供了较为完善的一整套前后端分离权限管理系统解决方…

汽车免拆诊断案例 | 2017款捷豹F-PACE车发动机偶尔怠速不稳

故障现象  一辆2017款捷豹F-PACE车&#xff0c;搭载2.0 L GTDi发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;车辆组合仪表上发动机故障灯点亮&#xff08;图1&#xff09;&#xff0c;且发动机偶尔怠速不稳。 图1 发动机故障灯点亮 故障诊断 接车后试车…

SQL进阶技巧:非等值连接--单向近距离匹配

目录 0 场景描述 1 数据准备 2 问题分析 ​编辑 ​编辑 3 小结 数字化建设通关指南 0 场景描述 表 t_1 和表 t_2 通过 a 和 b 关联时&#xff0c;有相等的取相等的值匹配&#xff0c;不相等时每一 个 a 的值在 b 中找差值最小的来匹。 表 t_1&#xff1a;a 中无重复值…

泷羽sec-云技术

基础之云技术 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec…

element ui select绑定的值是对象的属性时,显示异常.

需要声明 value-key"value",如果还不行可能是数据类型不一致数字0和字符串0是不一致的. el-select v-model"value" clearable placeholder"Select" value-key"value" style"width: 240px"><!-- <el-option v-for&…

【ChatGPT大模型开发调用】如何获得 OpenAl API Key?

如何获取 OpenAI API Key 获取 OpenAI API Key 主要有以下三种途径&#xff1a; OpenAI 官方平台 (推荐): 开发者用户可以直接在 OpenAI 官方网站 (platform.openai.com) 注册并申请 API Key。 通常&#xff0c;您可以在账户设置或开发者平台的相关页面找到申请入口。 Azure…