当前位置:首页 » 《休闲阅读》 » 正文

vue 前端导出页面图表保存为Html格式文档

13 人参与  2024年03月30日 08:50  分类 : 《休闲阅读》  评论

点击全文阅读


问题描述:

遇到一个需求,需要前端将页面上所有的图表导出,比如有echarts的图表(折线图、饼图、柱状图)、表格、文本、图片、标签(也是一段文字)等类型,保存成一个html格式的文档,在浏览器中直接打开这个html文档可以看到跟之前页面上一样的展示效果。

在这里插入图片描述图一

解决思路:

在研究的过程中,像echarts图表和图片,都需要保存为base64图片格式来使用,图片转成base64是为了保证导出到html文档中之后,在不是内网的环境中打开html文档,图片可以正常显示,echarts是需要请求数据,导出的文档中再打开之后,肯定不可能再去请求数据了,所以也需要转成base64处理。

想要达到要求的效果,网上找了很久,最后记录下2种方案:
方案一、使用html2canvas组件,html2canvas的作用就是允许我们直接在用户浏览器上拍摄网页或某一部分的截图。它的屏幕截图是基于DOM元素的,实际上它不会生成实际的屏幕截图,而是基于页面上可用的信息构建屏幕截图。

  exportReport (fileName) {    //需要的dom元素,需要自己定位到拿得到      let dom = this.$refs.exportTemplate.$parent.$parent.$parent.$refs.exportDiv.$refs.realDom      // 给的dom元素必须是原生的dom元素,不能是elementUI在浏览器中生成的,不然html2canvas会报错: Element is not attached to a Document      html2canvas(dom, {        backgroundColor: null,        useCORS: true // 配置图片可跨域      }).then(canvas => {        // 转成图片,生成图片地址        let imgUrl = canvas.toDataURL('image/png') // 可将 canvas 转为 base64 格式        // 创建HTML内容        const htmlContent = `                          <!DOCTYPE html>                          <html>                            <head>                              <title>导出的HTML文件</title>                            </head>                            <body>                              <h2 style="text-align:center">                               这是一个导出的文档                              </h2>                              <div style="width:100%;text-align:center">                                <img src="${imgUrl}" alt="导出内容">                              </div>                            </body>                          </html>                        `        // 创建Blob对象        const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })        // 创建下载链接        const downloadLink = document.createElement('a')        downloadLink.href = URL.createObjectURL(blob)        downloadLink.download = `${fileName}.html`        // 模拟点击下载链接        downloadLink.click()        // 释放URL对象        URL.revokeObjectURL(downloadLink.href)      })    }

注意
1、使用html2Canvas只能截取当前页面中显示的内容,如果当前页面中存在滚动条,html2canvas方法第一个参数dom就要给整个包含所有的元素长度的最外层元素才能将滚动的内容都截取下来。
2、这种方法实现的效果,整个页面就像一个pdf,所有的交互都不存在了,如果页面上还存在操作(比如点击展示,点击收起)是无法实现的。

方案二、需要根据页面中已存在的内容先生成一个html模板,然后获取页面的数据,循环生成相应的dom结构,就是用原生的html元素再实现一遍图一;表格、echarts可以直接用base64图片。生成这些内容后,外面套个html模板,最后再导出。

 createHtml (fileName, templateName) {      console.log(this.json.components) //所有的数据来源      let str = ''      this.json.components.forEach(data=> {        if (data.componentType == 'Picture') {          str += `<div>            <img class="image-class" src="${data.base64}" alt="图片">          </div>`        } else if (data.componentType == 'Label') {          str += `<div style="font-size:${data.attributes.fontSize};color:${data.attributes.fontColor}">            <span>${JSON.parse(data.translatedData)[data.bindData]}</span>          </div>`        } else if (['Histogram', 'Line-Chart', 'Pie-Chart'].includes(data.componentType)) {          str += `<div class="report-div">            <div class="title"><span>${data.componentName}</span></div>            <div class="desc"><span>${data.attributes.showDesc ? data.attributes.desc : ''}</span></div>            <div class="chart-height"> <img src="${data.base64}" alt="图表"></div>          </div>`        } else if (data.componentType == 'Rich-Text') {          str += `<div class="report-div">            <div class="title"><span>${data.componentName}</span></div>            <div class="desc"><span>${data.attributes.showDesc ? data.attributes.desc : ''}</span></div>            <div class="chart-div">              <div>                <textarea autocomplete="off" rows="${data.attributes.rowsNum}" class="el-textarea__inner" style="resize: none; min-height: 30px;">${JSON.parse(data.translatedData)[data.bindData]}</textarea>               </div>            </div>          </div>`        } else if (data.componentType == 'Form') {          // 表单          // 数据源:data.showList          let formHtml = ''          data.showList.forEach(itemm => {            formHtml += `              <div class="${data.attributes.columns == '1' ? 'one-col' : 'two-col'}">                <div class="title-div">                  <span class="con-span">${itemm.name}</span>                </div>                <div class="desc-div">`            if (itemm.style && itemm.style.location && itemm.style.location == 1) {              formHtml += `                      <img class="icon-img" src="${itemm.icon_base64}" style="height:${itemm.style.height}px;width:${itemm.style.width}px;">                      <span                        class="con-span"                        style="color:${itemm.advanced.fontColor}"                      >                        ${itemm.value}                      </span>                    </div>                  </div>`            } else if (itemm.style && itemm.style.location && itemm.style.location == 2) {              formHtml += `                      <span                        class="con-span"                        style="color:${itemm.advanced.fontColor}"                      >                        ${itemm.value}                      </span>                      <img class="icon-img" src="${itemm.icon_base64}" style="height:${itemm.style.height}px;width:${itemm.style.width}px;">                    </div>                  </div>`            } else {              formHtml += `                      <span                        class="con-span"                        style="color:${itemm.advanced.fontColor}"                      >                        ${itemm.value}                      </span>                    </div>                  </div>`            }          })          str += `<div class="report-div">            <div class="title"><span>${data.componentName}</span></div>            <div class="desc"><span>${data.attributes.showDesc ? data.attributes.desc : ''}</span></div>            <div class="chart-div form-content">              ${formHtml}            </div>          </div>`        } else if (data.componentType == 'Standard-Form') {          // mainTable 表头数据  subTable  displayName-表头数值  field对应字段   tableData表格数值          let table = '<table border="1" style="border-collapse: collapse;width:100%;">\n'          table += '<thead>'          if (data.attributes.tableNumber) {            table += '<th>序号</th>'          }          let tableHead = data.mainTable          tableHead.forEach(obj => {            table += `<th>${obj.displayName}</th>\n`          })          table += '</thead>'          let tableData = data.tableData          let subTable = data.subTable          // row 表格一行数据          tableData.forEach((row, index) => {            debugger            // 创建主表内容            table += `<tr>\n` + (data.attributes.tableNumber ? `<td>${index + 1}</td>` : '')            tableHead.forEach(headRow => {              // table += `<td>${row[headRow.field]}</td>\n`              table += `<td>`              let param = row[headRow.field + '_style']              let originParam = row[headRow.field + '1']              if (param && param.iconUrl && param.location == 1 && (originParam ? originParam == param.condition : true)) {                table += `                      <img class="icon-img" src="${row[headRow.field + '_style_base64']}" style="height:${param.height}px;width:${param.width}px;">                      <span                        class="con-span"                        style="color:${headRow.advanced.fontColor}"                      >                        ${row[headRow.field]}                      </span>                    </td>`              } else if (param && param.iconUrl && param.location == 2 && (originParam ? originParam == param.condition : true)) {                table += `                      <span                        class="con-span"                        style="color:${headRow.advanced.fontColor}"                      >                        ${row[headRow.field]}                      </span>                      <img class="icon-img" src="${row[headRow.field + '_style_base64']}" style="height:${param.height}px;width:${param.width}px;">                    </td>`              } else {                table += `                      <span                        class="con-span"                        style="color:${headRow.advanced.fontColor}"                      >                        ${row[headRow.field]}                      </span>                    </td>`              }            })            table += `</tr>\n`            if (data.attributes.subTableIsShow) {              // 子表内容 colspan需要合并单元格才能一行空间都有              table += `<tr><td colspan=${data.attributes.tableNumber ? data.mainTable.length + 1 : data.mainTable.length}><div class="sub-div-content">\n`              subTable.forEach(item => {                table += `<div class="sub-div">                  <div class="title-div sub">                    <span class="con-span">${item.displayName}</span>                  </div>                  <div class="desc-div sub">`                let param = row[item.field + '_style']                let originParam = row[item.field + '1']                if (param && param.iconUrl && param.location == 1 && (originParam ? originParam == param.condition : true)) {                  table += `                      <img class="icon-img" src="${row[item.field + '_style_base64']}" style="height:${param.height}px;width:${param.width}px;">                      <span                        class="con-span"                        style="color:${item.advanced.fontColor}"                      >                        ${row[item.field]}                      </span>                    </div>                  </div>`                } else if (param && param.iconUrl && param.location == 2 && (originParam ? originParam == param.condition : true)) {                  table += `                      <span                        class="con-span"                        style="color:${item.advanced.fontColor}"                      >                        ${row[item.field]}                      </span>                      <img class="icon-img" src="${row[item.field + '_style_base64']}" style="height:${param.height}px;width:${param.width}px;">                    </div>                  </div>`                } else {                  table += `                      <span                        class="con-span"                        style="color:${item.advanced.fontColor}"                      >                        ${row[item.field]}                      </span>                    </div>                  </div>`                }              })              table += `</div></td></tr>\n`            }          })          table += '</table>'          str += `<div class="report-div">            <div class="title"><span>${data.componentName}</span></div>            <div class="desc"><span>${data.attributes.showDesc ? data.attributes.desc : ''}</span></div>            <div class="chart-div">              <div>                ${table}              </div>            </div>          </div>`        } else if (data.componentType == 'Dynamic-Table') {          // 动态表格          let translated = JSON.parse(data.translatedData)          let table = '<table border="1" style="border-collapse: collapse;width:100%;">\n'          table += '<thead>'          let tableHead = translated[data.fieldNameList]          tableHead.forEach(name => {            table += `<th>${name}</th>\n`          })          table += '</thead>'          // 数据源 translatedData.dataList  fieldWidthList 宽度  fieldList字段列表          let realData = translated[data.dataList]          realData.forEach(dataItem => {            table += `<tr>\n`            translated[data.fieldList].forEach(columnsItem => {              table += `<td>${dataItem[columnsItem]}</td>\n`            })            table += `</tr>\n`          })          table += '</table>'          str += `<div class="report-div">            <div class="title"><span>${data.componentName}</span></div>            <div class="desc"><span>${data.attributes.showDesc ? data.attributes.desc : ''}</span></div>            <div class="chart-div">              ${table}            </div>          </div>`        }      })      let html =        `<!DOCTYPE html>          <html lang="en">          <head>            <meta charset="UTF-8">            <meta http-equiv="X-UA-Compatible" content="IE=edge">            <meta name="viewport" content="width=device-width, initial-scale=1.0">            <title>文件导出</title>            <style type="text/css">              body {                line-height: 1.5;                font-size: 14px;              }              thead tr{                background: #F5F7F9;              }              tr {                height: 36px;                page-break-inside: avoid !important;              }              th, td {                text-align: center;                border-top: none;                border-left: none;                border-right: none;              }              .image-class{                width:100%;              }              .content{                width: 80%;                margin: 0 auto;              }              .report-div{                margin-bottom: 15px;                min-height: 350px;                position: relative;                border: 1px solid #cacaca;              }              .title {                height: 50px;                line-height: 50px;                font-size: 16px;                font-weight: bold;                padding: 0 20px;                background: #F7F7F7;              }              .desc {                  height: 20px;                  font-size: 14px;                  margin: 5px 20px;              }              .chart-height{                height:calc(100% - 80px);                text-align: center;              }              .chart-div{                height:calc(100% - 90px);                overflow-y:auto;                margin: 5px 15px 10px;              }              .form-content{                display: flex;                flex-wrap: wrap;                align-content:flex-start;              }              .el-textarea__inner {                display: block;                resize: vertical;                padding: 5px 15px;                line-height: 1.5;                box-sizing: border-box;                width: 100%;                font-size: inherit;                color: #606266;                background-color: #FFF;                border: 1px solid #DCDFE6;                border-radius: 4px;                transition: border-color .2s cubic-bezier(.645,.045,.355,1);              }              .title-div,.desc-div{                line-height: 30px;                padding: 0 10px;                position: relative;              }              .title-div{                border-right: 1px solid #dadada;              }              .one-col{                width: 100%;                display: flex;                border-top: 1px solid #dadada;                border-left: 1px solid #dadada;                border-right: 1px solid #dadada;              }              .two-col{                width: 49.8%;                display: flex;                border-bottom: 1px solid #dadada;                border-left: 1px solid #dadada;                border-right: 1px solid #dadada;                height: 36px;                line-height: 36px;              }              .one-col:last-child{                border-bottom: 1px solid #dadada;              }              .one-col>div:first-child{                  width: 30%;                  background-color: #F7F7F7;              }              .one-col>div:last-child{                width: 70%;              }              .two-col>div:nth-child(2n+1){                  width: 30%;                  background-color: #F7F7F7;              }              .two-col>div:nth-child(2n){                  width: 70%;              }              .two-col:first-child{                border-top: 1px solid #dadada;              }              .two-col:nth-child(2){                border-top: 1px solid #dadada;              }              .two-col:nth-child(2n){                border-left: none;              }              .icon-img{                height:16px;                width:16px;                vertical-align: middle              }              .sub-div-content{                margin: 10px 20px;              }              .sub-div-content>:first-child {                  border: 1px solid #c9c9c9;              }              .sub-div-content>:not(:first-child){                border-left: 1px solid #c9c9c9;                border-right: 1px solid #c9c9c9;                border-bottom: 1px solid #c9c9c9;              }              .sub-div{                display: flex;              }              .title-div{                width:20%;                background-color: #ededed;                text-align: right;                border-right: 1px solid #c9c9c9;              }              .desc-div{                width:80%;                text-align:left              }              .sub{                line-height: 30px;                padding: 0 15px;                position: relative;              }            </style>          </head>          <body>            <div style="">              <h1 style="text-align:center">                ${templateName}              </h1>              <div class="content">                ${str}              </div>            </div>          </body>        </html>`      console.log(html)      // 创建Blob对象      const blob = new Blob([html], { type: 'text/html;charset=utf-8' })      // 创建下载链接      const downloadLink = document.createElement('a')      downloadLink.href = URL.createObjectURL(blob)      downloadLink.download = `${fileName}.html`      // 模拟点击下载链接      downloadLink.click()      // 释放URL对象      URL.revokeObjectURL(downloadLink.href)    }

导出效果:
在这里插入图片描述

注意:
这种自己写的html导出的方式,效果基本可以达到和页面展示的一样,如果还有事件交互,就需要在html中添加事件,本记录中就没有实现了,需要的可自行继续研究。


点击全文阅读


本文链接:http://zhangshiyu.com/post/87934.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1