【vue+excel】导出excel(目前是可以导出两个sheet)
项目里经常用到的导出ecxel功能是默认导出一个sheet页
现在需要导出两个sheet,一个是总计,另一个是明细
效果如下:
我就在现有的单个导出的功能上改造了一下,只支持导出两个(代码不够灵活,如果需要多个,还需要再优化或直接手动往里面加)
1、导出按钮的点击事件中
// 导出
handleExport() {
// 参数分别是:id数组(没用到,传空),sheet1名称,sheet2名称,sheet1表头,sheet2表头,sheet1数据源,sheet2数据源
this.double_sheet_exportExcel('', 'xx统计','xx明细', this.columns,this.detailColumns,
this.dataSource,this.detailDataSource)
}
2、安装excel相关的库
npm install --save xlsx file-saver
npm install --save xlsx-style
3、在utils的公共js文件中,如common.js(自己项目定义的放公共方法的文件中)
export function double_sheet_exportExcel(ids, filename1,filename2, excelProps1, excelProps2, excelList1, excelList2, dictOptions = {}) {
import('@/utils/export2Excel').then(excel => {
// 处理第一个工作表的表头和数据
const newExcelProps1 = JSON.parse(JSON.stringify(excelProps1))
// 处理第一个工作表的表头
newExcelProps1.forEach((prop, index) => {
if (prop.visible === false || prop.field === 'operation') {
newExcelProps1.splice(index, 1)
}
})
const name = 'title'
const tHeader1 = newExcelProps1.map(item => item[name]) // 第一个工作表的表头
// 处理第一个工作表的数据
let fileData1
if (ids === undefined || ids.length === 0) {
fileData1 = excelList1
} else {
let arr1 = JSON.parse(JSON.stringify(excelList1))
let arrMap1 = new Map(arr1.map(obj => [obj.id, obj]))
let resultArr1 = ids.map(id => arrMap1.get(id))
fileData1 = resultArr1
}
fileData1.forEach(item => {
newExcelProps1.forEach(v => {
if (v.type === 'dict' && v.scopedSlots.content) {
let dictField = v.dictField || ['dictValue', 'dictLabel']
const filterData = dictOptions[v.field].filter(f => item[v.field] == f[dictField[0]])
item[v.field] = !filterData.length ? '' : filterData[0][dictField[1]]
}
})
})
const data1 = fileData1.map(v => newExcelProps1.map(({ field }) => v[field]))
// 处理第二个工作表的表头和数据
const newExcelProps2 = JSON.parse(JSON.stringify(excelProps2))
// 处理第二个工作表的表头
newExcelProps2.forEach((prop, index) => {
if (prop.visible === false || prop.field === 'operation') {
newExcelProps2.splice(index, 1)
}
})
const tHeader2 = newExcelProps2.map(item => item[name]) // 第二个工作表的表头
// 处理第二个工作表的数据
let fileData2
if (ids === undefined || ids.length === 0) {
fileData2 = excelList2
} else {
let arr2 = JSON.parse(JSON.stringify(excelList2))
let arrMap2 = new Map(arr2.map(obj => [obj.id, obj]))
let resultArr2 = ids.map(id => arrMap2.get(id))
fileData2 = resultArr2
}
fileData2.forEach(item => {
newExcelProps2.forEach(v => {
if (v.type === 'dict' && v.scopedSlots.content) {
let dictField = v.dictField || ['dictValue', 'dictLabel']
const filterData = dictOptions[v.field].filter(f => item[v.field] == f[dictField[0]])
item[v.field] = !filterData.length ? '' : filterData[0][dictField[1]]
}
})
})
const data2 = fileData2.map(v => newExcelProps2.map(({ field }) => v[field]))
// 创建多 Sheet 的 Excel 文件
const multiHeader = [[filename1], [filename2]] // 多 Sheet 的标题
const merges = [
[`A1:${numToEng(newExcelProps1.length)}1`], // 第一个 Sheet 的合并单元格
[`A1:${numToEng(newExcelProps2.length)}1`], // 第二个 Sheet 的合并单元格
]
const filename = filename1
excel.double_sheet_export_json_to_excel({
title: [],
multiHeader, // 多 Sheet 的标题
header: [tHeader1, tHeader2], // 每个 Sheet 的表头
data: [data1, data2], // 每个 Sheet 的数据
merges, // 合并单元格
filename, // 文件名
autoWidth: true, // 自动宽度
bookType: 'xlsx', // 文件格式
sheetName: [filename1, filename2], // 每个 Sheet 的名称
})
})
}
4、在main.js中全局挂载
import {
double_sheet_exportExcel,
}
Vue.prototype.double_sheet_exportExcel = double_sheet_exportExcel
5、在utils中新建ecport2Excel.js
export function double_sheet_export_json_to_excel({
title,
multiHeader = [],
header,
data,
filename,
departName = [],
merges = [], // 二维数组,每个子数组对应一个 Sheet 的合并单元格
autoWidth = true,
bookType = 'xlsx',
sheetName = ['Sheet1'], // 每个 Sheet 的名称
exportAlignleft,
} = {}) {
const wb = new Workbook()
// 确保 header 和 data 是数组的数组(多 Sheet 支持)
if (!Array.isArray(header[0])) {
header = [header]
}
if (!Array.isArray(data[0])) {
data = [data]
}
if (!Array.isArray(merges[0])) {
merges = [merges]
}
header.forEach((sheetHeader, index) => {
const sheetData = data[index] || []
const sheetMerges = merges[index] || []
const ws_name = sheetName[index] || `Sheet${index + 1}`
const doubleMutiHeader = multiHeader[index]
// console.log('sheetHeader:', sheetHeader)
// console.log('sheetData:', sheetData)
// 构造工作表数据
const ws_data = [...title, ...departName, doubleMutiHeader, sheetHeader, ...sheetData]
// 创建工作表
const ws = double_sheet_from_array_of_arrays(ws_data)
// 处理合并单元格
if (sheetMerges && sheetMerges.length > 0) {
if (!ws['!merges']) ws['!merges'] = []
sheetMerges.forEach(item => {
try {
ws['!merges'].push(XLSX.utils.decode_range(item))
} catch (e) {
console.error(`Invalid merge range: ${item}`, e)
}
})
}
// 设置列宽
if (autoWidth) {
const validRows = ws_data.filter(row => Array.isArray(row) && row.length > 0)
const colWidth = validRows.map(row => {
return row.map(val => {
if (val == null || val === '') {
return { wch: 40 } // 默认宽度
} else if (val.toString().charCodeAt(0) > 255) {
return { wch: val.toString().length * 5 } // 中文字符宽度
} else {
return { wch: val.toString().length * 1 } // 英文字符宽度
}
})
})
// let result = colWidth[1] || []原先取的是colWidth[0],打印发现colWidth[0]只有一项,应该是colWidth[1],长度同列表总列数
let result = colWidth[1] || []
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]?.wch < colWidth[i][j].wch) {
result[j] = colWidth[i][j]
}
}
}
// ws['!cols'] = result
// ws['!cols'],假如导出共有7列,格式:[{wch:25},{wch:45},{wch:25},{wch:25},{wch:25},{wch:25},{wch:25}]
ws['!cols'] = result.map(col => {
return { wch: col.wch }
})
}
// 设置单元格样式
const borderAll = {
top: { style: 'thin' },
bottom: { style: 'thin' },
left: { style: 'thin' },
right: { style: 'thin' },
}
const dataInfo = ws
const row = ['!ref', '!merges', '!cols']
for (var i in dataInfo) {
if (row.indexOf(i) === -1) {
if (exportAlignleft == 'exportAlignleft') {
dataInfo[i].s = {
border: borderAll,
alignment: { horizontal: 'left', vertical: 'center' },
}
} else {
if (dataInfo[i].t == 'n') {
dataInfo[i].s = {
border: borderAll,
alignment: { horizontal: 'right', vertical: 'center' },
}
} else {
dataInfo[i].s = {
border: borderAll,
alignment: { horizontal: 'center', vertical: 'center' },
}
}
}
}
}
// 添加工作表到工作簿
wb.SheetNames.push(ws_name)
wb.Sheets[ws_name] = ws
// 根据表头长度生成单元格列数组
const arr = generateHeaderArr(header[0].length)
// 表头加上样式
arr.forEach(v => {
let v2 = v + '2'
if (dataInfo.hasOwnProperty(v2)) {
dataInfo[v2].s = {
border: borderAll,
font: {
color: { rgb: 'FFFFFF' },
bold: false,
italic: false,
underline: false,
},
alignment: {
horizontal: 'center',
vertical: 'center',
},
fill: {
fgColor: { rgb: '808080' },
},
}
}
})
//设置主标题样式
dataInfo['A1'].s = {
font: {
name: '微软雅黑',
sz: 16,
color: { rgb: '000000' },
bold: false,
italic: false,
underline: false,
},
alignment: {
horizontal: 'center',
vertical: 'center',
},
}
})
// 导出 Excel 文件
const wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary',
})
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream',
}),
`${filename}.${bookType}`
)
}
function double_sheet_from_array_of_arrays(data, opts) {
var ws = {}
var range = {
s: { c: 10000000, r: 10000000 }, // 初始化范围的起始点
e: { c: 0, r: 0 }, // 初始化范围的结束点
}
// 确保 data 是一个数组
if (!Array.isArray(data)) {
console.error('Data is not an array:', data)
return ws
}
for (var R = 0; R < data.length; ++R) {
// 确保 data[R] 是一个数组
if (!Array.isArray(data[R])) {
console.warn('Row', R, 'is not an array:', data[R])
continue // 跳过非数组行
}
for (var C = 0; C < data[R].length; ++C) {
if (range.s.r > R) range.s.r = R
if (range.s.c > C) range.s.c = C
if (range.e.r < R) range.e.r = R
if (range.e.c < C) range.e.c = C
var cell = { v: data[R][C] }
if (cell.v == null) continue
var cell_ref = XLSX.utils.encode_cell({ c: C, r: R })
if (typeof cell.v === 'number') cell.t = 'n'
else if (typeof cell.v === 'boolean') cell.t = 'b'
else if (cell.v instanceof Date) {
cell.t = 'd'
cell.z = XLSX.SSF._table[14]
cell.v = datenum(cell.v)
} else cell.t = 's'
ws[cell_ref] = cell
}
}
if (range.s.c < 10000000) {
ws['!ref'] = XLSX.utils.encode_range(range)
}
return ws
}
// 迭代生成单元格表头数组
function generateHeaderArr(length) {
const result = []
const generator = generateHeaderString()
for (let i = 0; i < length; i++) {
const nextString = generator.next().value
result.push(nextString)
}
return result
}
// 生成器
function* generateHeaderString() {
let index = 0
const letterString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
while (true) {
let str = ''
let n = index
while (n >= 0) {
str = letterString[n % 26] + str
n = Math.floor(n / 26) - 1
}
yield str
index++
}
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook()
this.SheetNames = []
this.Sheets = {}
}
function datenum(v, date1904) {
if (date1904) v += 1462
var epoch = Date.parse(v)
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
return buf
}