首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Vue3中Echarts图以Excel导出

说明

我们要把上变的Echarts树状图要以Excel的形式导出,导出来的结果大概就是以下样子:

安装工具依赖

在我们项目中执行以下命令安装所需依赖:

npm install exceljs file-saver --save

// exceljs包具体使用:https://github.com/exceljs/exceljs/blob/master/README_zh.md

项目使用

先添加以下导出图标的按钮

导出图表

import * as echarts from 'echarts'

import { changeZoneOption } from './statisticAnalysisEcharts' // echarts的options

import { Excel } from './exportExcel' // 封装的excel导出工具

const changeChart = ref(null)

let areaChart: any

// 导出excel

function exportExcel() {

const excel = new Excel()

....

...

}

onMounted(() => {

areaChart = echarts.init(changeChart.value)

changeZoneOption && areaChart.setOption(changeZoneOption)

})

我们主要实现exportExcel封装的工具,在实现之前我们需要看一下exportExcel函数做了哪些事儿:

获取图表实例生成数据(也可以直接从后端获取我们想要的数据格式)

我们导出的excel有两页一页是数据表格,一页是是数据图片表,所以我们需要获得数据还有生成图片

// 导出excel

function exportExcel() {

const chart: any = echarts.getInstanceByDom(changeChart.value) // 获取图表实例

const option = chart.getOption() // 获取到的option进行数据改造,这里不再展示

const base64Image = chart.getDataURL({

pixelRatio: 2, // 导出图片的分辨率比例,默认为1,即图片的分辨率为屏幕分辨率的一倍

backgroundColor: '#fff', // 导出图片的背景色

})

const excel = new Excel()

excel.exportExcel([

{

type: '数据表',

name: '表格文件名称',

title: '表格标题',

header: ['月份', '2022', '2023', '2024'],

data: [

['6月', 1, 2, 0],

['7月', 3, 2, 0],

['8月', 1, 2, 0],

['9月', 3, 2, 0],

['10月', 1, 2, 0],

['11月', 3, 2, 0],

['12月', 1, 2, 0],

],

customHeader: ['月份', '2022', '2023', '2024'],

sheetName: '数据表',

},

{

type: '图片表',

sheetName: '图片表',

base64Image: base64Image,

},

])

....

...

}

实现封装Excel工具类创建exportExcel.ts文件

/**

* 封装excel.ts工具文件

*/

import ExcelJS from 'exceljs'

import { saveAs } from 'file-saver' // 引入file-saver, 用于保存文件

// 合并单元格数据类型

export interface mergeListType {

startRow: number

startColumn: number

endRow: number

endColumn: number

}

export interface exportExcelType {

type: '数据表'

// 数据

data: { [key: string]: any }[]

// 文件名

name: string

// 表头字段

header: string[]

// 表头字段对应中文

customHeader: string[]

// 工作表名

sheetName?: string

// 标题

title?: string

// 小标题

subTitle?: string

// 工作表保护密码

password?: string

// 对齐方式

alignment?: Partial

// 合并单元格

mergeList?: mergeListType[]

// 标题样式

titleStyle?: Partial

// 小标题样式

subTitleStyle?: Partial

// 表头样式

headerStyle?: Partial

// 单元格统一样式

cellStyle?: Partial

}

export interface excelImageType {

type: '图片表'

// 数据

base64Image: any

sheetName?: string

}

export class Excel {

worksheet?: ExcelJS.Worksheet // 当前工作表

header: string[] // 表头字段数组

workbook: ExcelJS.Workbook // 工作簿对象

constructor() {

this.worksheet = undefined

this.header = []

this.workbook = new ExcelJS.Workbook()

this.workbook.creator = '作者' // excel属性作者

this.workbook.created = new Date() // excel属性创建时间

}

...

...

...

}

实现exportExcel方法我们看到exportExcel方法传入了一个数组,包含了第一页的数据页和第二页的图片表页

public async exportExcel(optionsArray: [exportExcelType, excelImageType]): Promise {

const promisAll: any[] = []

optionsArray.forEach((item: exportExcelType | excelImageType) => {

if (item.type === '数据表') {

promisAll.push(this.excelOption(item)) // 生成数据表的方法

} else if (item.type === '图片表') {

promisAll.push(this.excelImage(item)) // 生成图片表的方法

}

})

Promise.all(promisAll).then(() => {

// 导出excel

this.workbook.xlsx.writeBuffer().then((buffer) => {

// 生成excel文件的二进制数据

saveAs.saveAs(

new Blob([buffer], {

// 生成Blob对象

type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',

}),

'文件名字' + '.xlsx',

) // 指定文件名

})

})

}

实现excelOption生成数据表的方法该方法需要对数据Excel进行处理,最基本的插入单元格数据,插入大标题等

private async excelOption(options: exportExcelType): Promise {

const { data, header, customHeader, sheetName = 'sheet1', title = '', subTitle = '', password = '', mergeList = [], titleStyle, subTitleStyle, headerStyle, cellStyle } = options

// 添加sheet

this.worksheet = this.workbook.addWorksheet(sheetName)

this.header = header

// 表头行序号

let headerRowId = 1

if (title) {

headerRowId++

}

if (subTitle) {

headerRowId++

}

// 插入单元格数据

this.setCells(data, customHeader, cellStyle)

// 插入大标题

this.getTitle(title, titleStyle)

// 插入小标题

this.getSubTitle(subTitle, subTitleStyle)

// 处理表头

this.setHeaderStyle(headerRowId, data, headerStyle)

// 更多处理

this.handleDealExcel(password, headerRowId, mergeList)

}

// 插入大标题

private getTitle(title: string, style?: Partial): void {

if (title) {

// 插入标题

this.worksheet?.spliceRows(1, 0, [title])

this.worksheet?.mergeCells(1, 1, 1, this.header?.length)

// 调正标题样式

const titleRow = this.worksheet!.getRow(1)

// 高度

titleRow.height = style?.height || 40

// 字体

titleRow.font = style?.font || {

size: 20,

bold: true,

}

// 背景色

titleRow.fill = style?.fill || {

bgColor: {

argb: 'FFFFFF00',

},

type: 'pattern',

pattern: 'none',

}

// 对齐方式

titleRow.alignment = style?.alignment || {

horizontal: 'center',

vertical: 'middle',

}

}

}

// 插入小标题

private getSubTitle(subTitle: string, style?: Partial): void {

if (subTitle) {

this.worksheet?.spliceRows(2, 0, [subTitle])

this.worksheet?.mergeCells(2, 1, 2, this.header.length)

// 调整标题样式

const subtitleRow = this.worksheet!.getRow(2)

// 高度

subtitleRow.height = style?.height || 20

// 字体设置

subtitleRow.font = style?.font || {

size: 14,

}

// 背景色

subtitleRow.fill = style?.fill || {

bgColor: {

argb: 'FFFFFF00',

},

type: 'pattern',

pattern: 'none',

}

// 对齐方式

subtitleRow.alignment = style?.alignment || {

horizontal: 'right',

vertical: 'middle',

}

}

}

// 表头样式

private setHeaderStyle(num: number,  data: exportExcelType['data'], style?: Partial, data:): void {

// 自动筛选器

this.worksheet!.autoFilter = {

from: {

row: num,

column: 1,

},

to: {

row: data.length,

column: this.header.length,

},

}

// 给表头添加背景色

const headerRow = this.worksheet!.getRow(num)

headerRow!.height = style?.height || 30

// 通过 cell 设置背景色,更精准

headerRow?.eachCell((cell) => {

cell.fill = style?.fill || {

type: 'pattern',

pattern: 'solid',

fgColor: { argb: 'dde0e7' },

}

cell.font = style?.font || {

size: 12,

}

})

}

// 处理excel

private handleDealExcel(password: string, headerRowId: number, mergeList?: mergeListType[]) {

// 添加工作表保护

if (password) {

this.worksheet?.protect(password, {

autoFilter: true,

selectLockedCells: false,

})

}

// 合并单元格

mergeList?.forEach((item) => {

// 行数为表格数据所在行行数+表头行序号headerRowId

const startRow = item.startRow + headerRowId

const endRow = item.endRow + headerRowId

this.worksheet?.mergeCells(startRow, item.startColumn, endRow, item.endColumn)

})

// 冻结前几行

this.worksheet!.views = [{ state: 'frozen', xSplit: 0, ySplit: headerRowId }]

}

实现excelImage生成图片表的方法

private async excelImage(options: excelImageType): Promise {

const { sheetName = 'sheet1', base64Image } = options

const worksheet = this.workbook.addWorksheet(sheetName)

const image = this.workbook.addImage({

// 添加图片

base64: base64Image, // 图片的base64编码

extension: 'png', // 图片的扩展名

})

worksheet.addImage(image, 'A1:J20') // 将图片添加到工作表中

}

完整代码

/**

* 封装excel.ts工具文件

*/

import ExcelJS from 'exceljs'

import { saveAs } from 'file-saver' // 引入file-saver, 用于保存文件

// 合并单元格数据类型

export interface mergeListType {

startRow: number

startColumn: number

endRow: number

endColumn: number

}

export interface exportExcelType {

type: '数据表'

// 数据

data: { [key: string]: any }[]

// 文件名

name: string

// 表头字段

header: string[]

// 表头字段对应中文

customHeader: string[]

// 工作表名

sheetName?: string

// 标题

title?: string

// 小标题

subTitle?: string

// 工作表保护密码

password?: string

// 对齐方式

alignment?: Partial

// 合并单元格

mergeList?: mergeListType[]

// 标题样式

titleStyle?: Partial

// 小标题样式

subTitleStyle?: Partial

// 表头样式

headerStyle?: Partial

// 单元格统一样式

cellStyle?: Partial

}

export interface excelImageType {

type: '图片表'

// 数据

base64Image: any

sheetName?: string

}

export class Excel {

worksheet?: ExcelJS.Worksheet // 当前工作表

header: string[] // 表头字段数组

workbook: ExcelJS.Workbook // 工作簿对象

constructor() {

this.worksheet = undefined

this.header = []

this.workbook = new ExcelJS.Workbook()

this.workbook.creator = '作者' // excel属性作者

this.workbook.created = new Date() // excel属性创建时间

}

public async exportExcel(optionsArray: [exportExcelType, excelImageType]): Promise {

const promisAll: any[] = []

optionsArray.forEach((item: exportExcelType | excelImageType) => {

if (item.type === '数据表') {

promisAll.push(this.excelOption(item))

} else if (item.type === '图片表') {

promisAll.push(this.excelImage(item))

}

})

Promise.all(promisAll).then(() => {

// 导出excel

this.workbook.xlsx.writeBuffer().then((buffer) => {

// 生成excel文件的二进制数据

saveAs.saveAs(

new Blob([buffer], {

// 生成Blob对象

type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',

}),

'name' + '.xlsx',

) // 指定文件名

})

})

}

private async excelImage(options: excelImageType): Promise {

const { sheetName = 'sheet1', base64Image } = options

const worksheet = this.workbook.addWorksheet(sheetName)

const image = this.workbook.addImage({

// 添加图片

base64: base64Image, // 图片的base64编码

extension: 'png', // 图片的扩展名

})

worksheet.addImage(image, 'A1:J20') // 将图片添加到工作表中

}

private async excelOption(options: exportExcelType): Promise {

const { data, header, customHeader, sheetName = 'sheet1', title = '', subTitle = '', password = '', mergeList = [], titleStyle, subTitleStyle, headerStyle, cellStyle } = options

// 添加sheet

this.worksheet = this.workbook.addWorksheet(sheetName)

this.header = header

// 表头行序号

let headerRowId = 1

if (title) {

headerRowId++

}

if (subTitle) {

headerRowId++

}

// 插入单元格数据

this.setCells(data, customHeader, cellStyle)

// 插入大标题

this.getTitle(title, titleStyle)

// 插入小标题

this.getSubTitle(subTitle, subTitleStyle)

// 处理表头

this.setHeaderStyle(headerRowId, data, headerStyle)

// 更多处理

this.handleDealExcel(password, headerRowId, mergeList)

}

// 插入单元格数据

private setCells(data: exportExcelType['data'], customHeader: string[], style?: Partial): void {

// 设置列,插入中文表头

const column: Partial[] = []

this.header?.forEach((item, index) => {

column.push({

header: customHeader[index],

key: item,

width: style?.width || 25,

})

})

this.worksheet!.columns = column

// 设置行,插入数据

this.worksheet?.addRows(data)

// 设置行高

this.worksheet?.eachRow({ includeEmpty: true }, (row) => {

row.height = style?.height || 20

})

// 获取每一列数据,再依次对齐

this.worksheet!.columns.forEach((column) => {

column.alignment = style?.alignment || { vertical: 'middle', horizontal: 'center', wrapText: true }

})

}

// 插入大标题

private getTitle(title: string, style?: Partial): void {

if (title) {

// 插入标题

this.worksheet?.spliceRows(1, 0, [title])

this.worksheet?.mergeCells(1, 1, 1, this.header?.length)

// 调正标题样式

const titleRow = this.worksheet!.getRow(1)

// 高度

titleRow.height = style?.height || 40

// 字体

titleRow.font = style?.font || {

size: 20,

bold: true,

}

// 背景色

titleRow.fill = style?.fill || {

bgColor: {

argb: 'FFFFFF00',

},

type: 'pattern',

pattern: 'none',

}

// 对齐方式

titleRow.alignment = style?.alignment || {

horizontal: 'center',

vertical: 'middle',

}

}

}

// 插入小标题

private getSubTitle(subTitle: string, style?: Partial): void {

if (subTitle) {

this.worksheet?.spliceRows(2, 0, [subTitle])

this.worksheet?.mergeCells(2, 1, 2, this.header.length)

// 调整标题样式

const subtitleRow = this.worksheet!.getRow(2)

// 高度

subtitleRow.height = style?.height || 20

// 字体设置

subtitleRow.font = style?.font || {

size: 14,

}

// 背景色

subtitleRow.fill = style?.fill || {

bgColor: {

argb: 'FFFFFF00',

},

type: 'pattern',

pattern: 'none',

}

// 对齐方式

subtitleRow.alignment = style?.alignment || {

horizontal: 'right',

vertical: 'middle',

}

}

}

// 表头样式

private setHeaderStyle(num: number,  data: exportExcelType['data'], style?: Partial, data:): void {

// 自动筛选器

this.worksheet!.autoFilter = {

from: {

row: num,

column: 1,

},

to: {

row: data.length,

column: this.header.length,

},

}

// 给表头添加背景色

const headerRow = this.worksheet!.getRow(num)

headerRow!.height = style?.height || 30

// 通过 cell 设置背景色,更精准

headerRow?.eachCell((cell) => {

cell.fill = style?.fill || {

type: 'pattern',

pattern: 'solid',

fgColor: { argb: 'dde0e7' },

}

cell.font = style?.font || {

size: 12,

}

})

}

// 处理excel

private handleDealExcel(password: string, headerRowId: number, mergeList?: mergeListType[]) {

// 添加工作表保护

if (password) {

this.worksheet?.protect(password, {

autoFilter: true,

selectLockedCells: false,

})

}

// 合并单元格

mergeList?.forEach((item) => {

// 行数为表格数据所在行行数+表头行序号headerRowId

const startRow = item.startRow + headerRowId

const endRow = item.endRow + headerRowId

this.worksheet?.mergeCells(startRow, item.startColumn, endRow, item.endColumn)

})

// 冻结前几行

this.worksheet!.views = [{ state: 'frozen', xSplit: 0, ySplit: headerRowId }]

}

}

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OY8cvejlPIiJIOCpbzAwgm5Q0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券