vue下载表格并插入charts图表

image.png

image.png

页面代码:

        <ExportExcel
          v-if="enterpriseType.table"
          :data="enterpriseType.table"
          :fields="enterpriseTypeFields"
          :filename="enterpriseTypeName"
          :header="'企业注册类型分类统计'"
          title="企业注册类型分类统计"
          :chartOptions="this.enterpriseType.chart"
          style="margin-left: 20px;"
        >
        </ExportExcel>
        <ExportExcel
          v-if="typeIdCountData.table"
          :data="typeIdCountData.table"
          :fields="typeIdCountFields"
          :filename="typeIdCountName"
          :header="'岗位数量'"
          title="岗位数量"
          :chartOptions="this.typeIdCountData.chart"
          style="margin-left: 20px;"
        >
        </ExportExcel>

//引入下载组件
import ExportExcel from "@/components/ExportExcel";

创建components/ExportExcel/index文件
安装依赖:

npm install file-saver
npm install html2canvas --save
npm install exceljs --save

引入
import ExcelJS from 'exceljs'
import { saveAs } from 'file-saver'
import html2canvas from 'html2canvas'

代码:

<template>
  <div>
    <el-button
      type="primary"
      size="mini"
      @click="handleExport"
      :loading="loading"
      style="height: 35px;margin-top: 10px;"
    >
      导出{{title}}
    </el-button>
    <dv-border-box-2
      :color="['#37a2da', 'white']"
      backgroundColor="white"
      style="width: 800px;height: 600px;position: absolute;left: -99999px;"
      ref="chartRef"
    >
      <dv-charts :option="chartOptions" />
    </dv-border-box-2>

  </div>
</template>
<script>
import ExcelJS from 'exceljs'
// import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
import html2canvas from 'html2canvas'



export default {
  props: {
    data: {
      type: Array,
      required: true
    },
    fields: {
      type: Object,
      required: true
    },
    filename: {
      type: String,
      default: 'export.xlsx'
    },
    title: {
      type: String,
      required: true
    },
    chartOptions: {
      type: Object,
      default: () => ({}),
      validator: (value) => {
        return typeof value === 'object' && value !== null
      }
    }
  },
  data() {
    return {
      loading: false
    }
  },
  methods: {
    async handleExport() {
      this.loading = true

      try {
        const mappedData = this.data.map(item => {
          const newItem = {}
          Object.keys(this.fields).forEach(key => {
            newItem[key] = item[this.fields[key]]
          })
          return newItem
        })

        // 使用 exceljs 创建 workbook
        const workbook = new ExcelJS.Workbook()
        workbook.creator = 'QLM'
        workbook.lastModifiedBy = 'QLM'
        workbook.created = new Date()
        workbook.modified = new Date()
        const worksheet = workbook.addWorksheet('Sheet1')

        // 添加表头
        const headers = Object.keys(this.fields)
        worksheet.addRow(headers)

        // 添加数据
        mappedData.forEach(item => {
          const row = headers.map(header => item[header])
          worksheet.addRow(row)
        })

        // 添加图表
        // if (this.chartData && this.chartData.length > 0 && this.$refs.chartRef) {
        //   // 格式化图表数据
        //   const formattedData = this.chartData.map(item => {
        //     return {
        //       ...item,
        //       // 确保数值字段是数字类型
        //       value: Number(item.value) || 0
        //     }
        //   })

          // 格式化图表选项
          // const formattedOptions = {
          //   ...this.chartOptions,
          //   // 确保必要的选项存在
          //   xAxis: this.chartOptions.xAxis || { type: 'category' },
          //   yAxis: this.chartOptions.yAxis || { type: 'value' },
          //   series: this.chartOptions.series || []
          // }
          // // 确保图表元素有有效尺寸
          // // 更新图表数据
          // this.chartData = formattedData
          // this.chartOptions = formattedOptions
          await this.$nextTick() // 等待视图更新

          const chartEl = this.$refs.chartRef.$el
          chartEl.style.display = 'block'
          chartEl.style.width = '800px'
          chartEl.style.height = '600px'
          chartEl.style.position = 'absolute'
          chartEl.style.left = '-9999px'

          // 等待渲染完成
          await new Promise(resolve => setTimeout(resolve, 500))
          await this.$nextTick()

          // 检查元素尺寸
          if (chartEl.offsetWidth === 0 || chartEl.offsetHeight === 0) {
            console.error('Chart element has invalid dimensions')
            this.$message.error('图表渲染失败:元素尺寸无效')
            return
          }

          // 捕获图表
          let canvas;
          try {
            // 添加更多调试信息
            console.log('Chart element dimensions:', {
              width: chartEl.offsetWidth,
              height: chartEl.offsetHeight
            });

            // 改进html2canvas配置
            canvas = await html2canvas(chartEl, {
              useCORS: true,
              logging: true,
              scale: 2,
              allowTaint: true,
              width: chartEl.offsetWidth,
              height: chartEl.offsetHeight,
              backgroundColor: '#FFFFFF', // 确保背景为白色
              onclone: async (clonedDoc) => {
                // 确保克隆的元素也应用了样式
                const clonedChart = clonedDoc.querySelector('.dv-charts');
                if (clonedChart) {
                  clonedChart.style.display = 'block';
                  clonedChart.style.width = '800px';
                  clonedChart.style.height = '600px';
                  // 强制重绘以确保内容渲染
                  clonedChart.offsetHeight;
                  // 添加动画帧等待
                  await new Promise(resolve => requestAnimationFrame(resolve));
                }
              },
              // 增加渲染等待时间
              async: true,
              timeout: 10000, // 进一步增加超时时间
              ignoreElements: (element) => {
                // 忽略可能干扰渲染的元素
                return element.tagName === 'IFRAME';
              }
            });

            // 添加额外的渲染检查
            await new Promise(resolve => setTimeout(resolve, 1000));
            await this.$nextTick();

            // 验证canvas是否有效
            if (!canvas || canvas.width === 0 || canvas.height === 0) {
              throw new Error('Invalid canvas dimensions');
            }

            // 测试canvas渲染
            document.body.appendChild(canvas);
            setTimeout(() => {
              document.body.removeChild(canvas);
            }, 3000);

          } catch (err) {
            console.error('html2canvas error:', err);
            this.$message.error(`图表截图失败: ${err.message}`);
            return null;
          }

          this.$refs.chartRef.$el.style.display = 'none';
          if (canvas) {
            // 验证canvas内容
            const ctx = canvas.getContext('2d');
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const isEmpty = imageData.data.every(channel => channel === 0);

            if (isEmpty) {
              console.error('Canvas is empty');
              this.$message.error('图表内容为空');
              return;
            }

            // 获取完整的data URL
            const imgData = canvas.toDataURL('image/png', 1.0);

            // 调试信息
            console.log('Canvas dimensions:', canvas.width, canvas.height);
            console.log('Canvas content:', imageData);
            console.log('Data URL length:', imgData.length);
            console.log('Data URL sample:', imgData.substring(0, 100) + '...');

            if (!imgData || imgData.length < 100) {
              console.error('Invalid image data');
              this.$message.error('图片数据无效');
              return;
            }

            try {
              const imageId = workbook.addImage({
                base64: imgData,
                extension: 'png'
              });

              console.log('Image successfully added to workbook');
              // 将图表插入到数据行之后
              worksheet.addImage(imageId, {
                tl: { col: 0, row: mappedData.length + 2 }, // 从第0列开始,数据行数+2行
                ext: { width: 500, height: 300 } // 设置图像大小
              });
            } catch (err) {
              console.error('Failed to add image to workbook:', err);
              this.$message.error('添加图片到Excel失败');
              return;
            }
          }
        // }

        // 保存文件
        const buffer = await workbook.xlsx.writeBuffer()
        const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
        saveAs(blob, this.filename)
      } catch (error) {
        console.error('Export error:', error)
        this.$message.error('Export failed')
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容