Vue + ECharts 实现图表导出为图片功能详解
前言
在数据可视化项目中,经常需要将图表导出为图片供用户下载或分享。本文将详细介绍如何在 Vue 项目中使用 ECharts 的 getDataURL() 方法实现图表导出功能。
一、实现原理
1.1 核心 API
ECharts 提供了 getDataURL() 方法,可以将图表转换为 Base64 编码的图片数据:
javascript
const imageUrl = chart.getDataURL({
type: 'png', // 图片格式:'png' 或 'jpeg'
pixelRatio: 2, // 像素比,2 表示 2 倍分辨率(更清晰)
backgroundColor: '#fff' // 背景色
})
1.2 下载机制
利用 HTML5 的 标签的 download 属性触发浏览器下载:
javascript
const link = document.createElement('a')
link.href = imageUrl
link.download = '图表.png'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
二、基础实现
2.1 保存单个图表
javascript
<template>
<div>
<div id="myChart" style="width: 600px; height: 400px;"></div>
<el-button @click="saveChartAsImage">保存图片</el-button>
</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById('myChart'))
const option = {
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150, 80, 70], type: 'line' }]
}
this.chart.setOption(option)
},
saveChartAsImage() {
if (!this.chart) {
this.$message.warning('图表未初始化')
return
}
try {
// 将图表转换为图片 URL
const imageUrl = this.chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
// 创建下载链接
const link = document.createElement('a')
link.href = imageUrl
link.download = `图表_${Date.now()}.png`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$message.success('图片保存成功')
} catch (error) {
this.$message.error('保存失败: ' + error.message)
}
}
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
}
}
}
</script>
2.2 保存多个图表
javascript
<template>
<div>
<div id="chart1" style="width: 600px; height: 400px;"></div>
<div id="chart2" style="width: 600px; height: 400px;"></div>
<el-button @click="saveAllCharts" :loading="savingImages">保存所有图表</el-button>
</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
data() {
return {
chart1: null,
chart2: null,
savingImages: false
}
},
mounted() {
this.initCharts()
},
methods: {
initCharts() {
// 初始化图表1
this.chart1 = echarts.init(document.getElementById('chart1'))
this.chart1.setOption({
title: { text: '趋势图' },
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'line' }]
})
// 初始化图表2
this.chart2 = echarts.init(document.getElementById('chart2'))
this.chart2.setOption({
title: { text: '柱状图' },
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ data: [100, 150, 200], type: 'bar' }]
})
},
async saveAllCharts() {
this.savingImages = true
try {
const images = []
// 收集所有图表
if (this.chart1) {
images.push({
name: '趋势图',
url: this.chart1.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
if (this.chart2) {
images.push({
name: '柱状图',
url: this.chart2.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
if (images.length === 0) {
this.$message.warning('没有可保存的图表')
return
}
// 批量下载(添加延迟避免浏览器阻止)
images.forEach((img, index) => {
setTimeout(() => {
const link = document.createElement('a')
link.href = img.url
link.download = `${img.name}_${Date.now()}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}, index * 100)
})
this.$message.success(`成功保存 ${images.length} 张图片`)
} catch (error) {
this.$message.error('保存失败: ' + error.message)
} finally {
this.savingImages = false
}
}
},
beforeDestroy() {
if (this.chart1) this.chart1.dispose()
if (this.chart2) this.chart2.dispose()
}
}
</script>
三、进阶技巧
3.1 通过 ref 访问子组件图表
父组件:
javascript
<template>
<div>
<ChartComponent ref="chartComponent" />
<el-button @click="saveChart">保存图片</el-button>
</div>
</template>
<script>
export default {
methods: {
saveChart() {
// 通过 ref 访问子组件
const chartComponent = this.$refs.chartComponent
// 访问子组件的图表实例
const chart = chartComponent.chart
if (chart) {
const imageUrl = chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
const link = document.createElement('a')
link.href = imageUrl
link.download = `图表_${Date.now()}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$message.success('保存成功')
}
}
}
}
</script>
子组件(ChartComponent.vue):
javascript
<template>
<div id="chart" style="width: 600px; height: 400px;"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
data() {
return {
chart: null // 暴露给父组件访问
}
},
mounted() {
this.chart = echarts.init(document.getElementById('chart'))
this.chart.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'line' }]
})
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
}
}
}
</script>
3.2 自定义文件名
javascript
// 方式1:使用时间戳
link.download = `图表_${Date.now()}.png`
// 结果:图表_1703567890123.png
// 方式2:使用日期格式
const now = new Date()
const dateStr = `${now.getFullYear()}${(now.getMonth()+1).toString().padStart(2,'0')}${now.getDate().toString().padStart(2,'0')}`
link.download = `图表_${dateStr}.png`
// 结果:图表_20231226.png
// 方式3:组合多个信息
const patientName = '张三'
const chartType = '趋势分析'
const timestamp = Date.now()
link.download = `${patientName}_${chartType}_${timestamp}.png`
// 结果:张三_趋势分析_1703567890123.png
3.3 调整图片质量
javascript
// PNG 格式(无损压缩,文件较大,适合需要高质量的场景)
const imageUrl = chart.getDataURL({
type: 'png',
pixelRatio: 2, // 1=标准, 2=高清, 3=超高清
backgroundColor: '#fff'
})
// JPEG 格式(有损压缩,文件较小,适合对文件大小有要求的场景)
const imageUrl = chart.getDataURL({
type: 'jpeg',
pixelRatio: 2,
backgroundColor: '#fff',
excludeComponents: ['toolbox'] // 排除工具栏等组件
})
3.4 添加 Loading 状态
javascript
<template>
<div>
<div id="chart" style="width: 600px; height: 400px;"></div>
<el-button @click="saveChart" :loading="savingImage">
{{ savingImage ? '保存中...' : '保存图片' }}
</el-button>
</div>
</template>
<script>
export default {
data() {
return {
chart: null,
savingImage: false
}
},
methods: {
async saveChart() {
this.savingImage = true
try {
const imageUrl = this.chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
const link = document.createElement('a')
link.href = imageUrl
link.download = `图表_${Date.now()}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$message.success('保存成功')
} catch (error) {
this.$message.error('保存失败: ' + error.message)
} finally {
this.savingImage = false
}
}
}
}
</script>
四、实战案例:综合报告多图表导出
在实际项目中,我们可能需要同时导出多个不同类型的图表。以下是一个完整的实战案例:
javascript
<template>
<div class="comprehensive-report">
<!-- 选择要导出的图表 -->
<div class="report-selection">
<el-checkbox-group v-model="selectedTypes">
<el-checkbox :label="1">趋势分析</el-checkbox>
<el-checkbox :label="2">小时平均</el-checkbox>
<el-checkbox :label="3">研究比较</el-checkbox>
<el-checkbox :label="4">散点图</el-checkbox>
</el-checkbox-group>
<el-button
type="success"
@click="saveChartsAsImages"
:disabled="!reportData"
:loading="savingImages">
保存图片
</el-button>
</div>
<!-- 报告内容 -->
<div v-if="reportData" class="report-content">
<!-- 趋势分析 -->
<TrendChart
v-if="selectedTypes.includes(1)"
ref="trendChart"
:data="reportData.trendData" />
<!-- 小时平均 -->
<HourlyChart
v-if="selectedTypes.includes(2)"
ref="hourlyChart"
:data="reportData.hourlyData" />
<!-- 研究比较(包含2个图表)-->
<ResearchChart
v-if="selectedTypes.includes(3)"
ref="researchChart"
:data="reportData.researchData" />
<!-- 散点图(包含4个图表)-->
<ScatterChart
v-if="selectedTypes.includes(4)"
ref="scatterChart"
:data="reportData.scatterData" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
selectedTypes: [1, 2, 3, 4],
reportData: null,
savingImages: false
}
},
methods: {
async saveChartsAsImages() {
if (!this.reportData) {
this.$message.warning('请先生成报告')
return
}
this.savingImages = true
try {
const images = []
// 1. 保存趋势分析图表
if (this.selectedTypes.includes(1) && this.$refs.trendChart) {
const chart = this.$refs.trendChart.chart
if (chart) {
images.push({
name: '趋势分析',
url: chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
}
// 2. 保存小时平均图表
if (this.selectedTypes.includes(2) && this.$refs.hourlyChart) {
const chart = this.$refs.hourlyChart.chart
if (chart) {
images.push({
name: '小时平均',
url: chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
}
// 3. 保存研究比较图表(2个图表)
if (this.selectedTypes.includes(3) && this.$refs.researchChart) {
const researchChart = this.$refs.researchChart
if (researchChart.chart1) {
images.push({
name: '研究比较_趋势图',
url: researchChart.chart1.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
if (researchChart.chart2) {
images.push({
name: '研究比较_数据整理',
url: researchChart.chart2.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
}
// 4. 保存散点图(最多4个图表)
if (this.selectedTypes.includes(4) && this.$refs.scatterChart) {
const scatterChart = this.$refs.scatterChart
const charts = scatterChart.charts // 假设是一个对象 { chart0, chart1, chart2, chart3 }
const scatterNames = [
'散点图_舒张压-收缩压',
'散点图_脉率-收缩压',
'散点图_脉率-舒张压',
'散点图_脉率-平均压'
]
Object.keys(charts).forEach((key, index) => {
const chart = charts[key]
if (chart) {
images.push({
name: scatterNames[index] || `散点图_${index + 1}`,
url: chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
})
}
})
}
if (images.length === 0) {
this.$message.warning('没有可保存的图表')
return
}
// 批量下载
const patientName = this.reportData.patientName || '患者'
const timestamp = Date.now()
images.forEach((img, index) => {
setTimeout(() => {
const link = document.createElement('a')
link.href = img.url
link.download = `${patientName}_${img.name}_${timestamp}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}, index * 100) // 每个图表间隔100ms,避免浏览器阻止
})
this.$message.success(`成功保存 ${images.length} 张图片`)
} catch (error) {
this.$message.error('保存失败: ' + error.message)
} finally {
this.savingImages = false
}
}
}
}
</script>
五、高级优化
5.1 Base64 转 Blob(减少内存占用)
javascript
// 将 Base64 转换为 Blob 对象
function dataURLtoBlob(dataURL) {
const arr = dataURL.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
// 使用 Blob 下载
const imageUrl = chart.getDataURL({ type: 'png', pixelRatio: 2 })
const blob = dataURLtoBlob(imageUrl)
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '图表.png'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
// 释放内存
URL.revokeObjectURL(url)
5.2 添加水印
javascript
const imageUrl = chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff'
})
// 创建 Canvas 添加水印
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
// 绘制原图
ctx.drawImage(img, 0, 0)
// 添加水印
ctx.font = '20px Arial'
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
ctx.fillText('© 2023 公司名称', 10, canvas.height - 10)
// 导出带水印的图片
const watermarkedUrl = canvas.toDataURL('image/png')
const link = document.createElement('a')
link.href = watermarkedUrl
link.download = '图表_带水印.png'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
img.src = imageUrl
六、常见问题
6.1 跨域问题
如果图表中使用了外部图片,可能会遇到跨域问题:
javascript
// 解决方案:在图表配置中设置
const option = {
graphic: [{
type: 'image',
style: {
image: 'https://example.com/image.png',
// 添加 crossOrigin 属性
crossOrigin: 'anonymous'
}
}]
}
6.2 浏览器阻止多文件下载
某些浏览器会阻止同时下载多个文件,解决方案:
javascript
// 添加延迟
images.forEach((img, index) => {
setTimeout(() => {
// 下载逻辑
}, index * 100) // 每个文件间隔100ms
})
6.3 图片模糊问题
javascript
// 提高 pixelRatio
const imageUrl = chart.getDataURL({
type: 'png',
pixelRatio: 3, // 提高到3倍分辨率
backgroundColor: '#fff'
})
七、总结
本文介绍了在 Vue + ECharts 项目中实现图表导出功能的完整方案,包括:
1、基础实现:单图表和多图表导出
2、进阶技巧:ref 访问、自定义文件名、质量调整
3、实战案例:综合报告多图表批量导出
4、高级优化:Blob 转换、添加水印
5、常见问题:跨域、浏览器限制、图片质量
核心要点:
使用 chart.getDataURL() 获取图片数据
使用 标签的 download 属性触发下载
通过 ref 访问子组件的图表实例
添加延迟避免浏览器阻止多文件下载
合理设置 pixelRatio 平衡质量和文件大小
希望本文对你有所帮助!如果有任何问题,欢迎在评论区讨论。
相关链接: