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>