1.插件的安装和用途
安装
npm i file-saver (版本2.0.5)
npm i xlsx (版本0.16.0)
npm i xlsx-style (版本0.8.13)
file-saver
用途:
这个插件主要用于在浏览器中保存文件。它允许你将数据保存为文件,并提示用户下载这些文件。
功能:
- 生成 Blob(大型对象)并保存为文件。
- 支持多种文件类型,如文本文件、图像文件、PDF 文件等。
- 浏览器兼容性好,适用于几乎所有现代浏览器。
xlsx
用途:
这个插件是一个流行的 JavaScript 库,用于解析、操作和生成 Excel 文件(.xlsx
、.xls
等)。它可以将 JSON、数组等数据转换为 Excel 文件,也可以解析现有的 Excel 文件并提取数据。
功能:
- 读取和写入 Excel 文件。
- 支持多种数据格式(如 CSV、ODS)。
- 提供丰富的功能来操作工作表、单元格、样式等。
xlsx-style
用途:
xlsx-style
是 xlsx
插件的一个扩展,用于增强 Excel 文件中样式的支持。虽然 xlsx
可以生成 Excel 文件,但它对单元格样式(如字体、颜色、对齐方式等)的支持较弱,而 xlsx-style
通过扩展 xlsx
的功能,使得你可以在生成 Excel 文件时应用更复杂的样式。
功能:
- 支持更多样式选项,如单元格背景颜色、字体颜色、加粗、对齐方式等。
- 在生成 Excel 文件时,可以控制每个单元格的外观。
2.需求
页面有一个表格,需要导出EXCEl表(用XLSX和XLSXS制作了样式之类的表格),格式如下:
1.颜色三种
2.下面表格的字体是粗体:附、A-Z、一二三四五六七八九十,其余内容为正常字体
3.一些问题
问题1:因为使用element,而且页面是有滚动的,所以在表格的右侧还会出现几个多余的单元格,导致后面导出表格出错:
在生成sheet时输出这个div:发现多出的单元格都带属性:flutter
方法一:可以先把这些单元格删除掉
let gutters = div.querySelectorAll('.gutter');
gutters.forEach(gutter => {
gutter.remove();
});
方法二:修改他们的样式 边框设置为默认色
if (word > 'D') {
data[key].s.border = {
top: {
style: 'thin',
color: { rgb: 'e6e6e6' }
},
bottom: {
style: 'thin',
color: { rgb: 'e6e6e6' }
},
left: {
style: 'thin',
color: { rgb: '000000' }
},
right: {
style: 'thin',
color: { rgb: 'e6e6e6' }
}
}
}
问题2:表格因为使用element制作,合并的表头转为sheet后,缺失格子,需要补齐:
1.创建新的工作簿(工作簿是一个 Excel 文件,可以包含一个或多个工作表(Sheet))
const book = XLSX.utils.book_new()
2.将 HTML 表格转换为工作表(Worksheet,Excel 工作表对象 sheet
)
const sheet = XLSX.utils.table_to_sheet(div)
3.将工作表添加到工作簿中,命名为 'Sheet1'
XLSX.utils.book_append_sheet(book, sheet, 'Sheet1')
4.因为表头合并转为sheet工作表时确实格子,要补齐格子
sheet.B1 = sheet.B2 = sheet.C1 = sheet.C2 = sheet.D1 = sheet.D2 = {}
const book = XLSX.utils.book_new()
const sheet = XLSX.utils.table_to_sheet(div)
XLSX.utils.book_append_sheet(book, sheet, 'Sheet1')
sheet.B1 = sheet.B2 = sheet.C1 = sheet.C2 = sheet.D1 = sheet.D2 = {}
转化为sheet 除了表格,还有几个属性
1. !cols: []
2. !fullref: "A1:E36"
3. !merges: (2) [{…}, {…}]
4. !ref: "A1:E36"
5. !rows: []
以下是解析:
1. !cols
定义: 这是一个数组,用于定义每一列的宽度。
作用: 你可以通过在数组中设置每一列的宽度(以字符数为单位)来控制生成的 Excel 文件中每列的显示宽度。
例子: 如果 !cols 为 [{wch: 10}, {wch: 20}],则第一列宽度为10字符,第二列宽度为20字符。
2. !fullref
定义: 这个属性描述了整个工作表的引用范围(即工作表中所有使用的单元格区域)。
作用: 它表示工作表中实际使用的单元格区域,从左上角到右下角。
例子: "A1:E36" 意味着工作表中有效数据的区域从 A1 到 E36。
3. !merges
定义: 这是一个数组,包含多个合并单元格的描述。
作用: 每个元素是一个对象,描述了一个合并单元格的区域。包括起始单元格(s)和结束单元格(e)。
例子: [{s: {r: 0, c: 0}, e: {r: 0, c: 3}}, {s: {r: 1, c: 0}, e: {r: 1, c: 3}}] 表示 A1:D1 和 A2:D2 被合并。
4. !ref
定义: 表示当前表格实际使用的单元格范围。
作用: 与 !fullref 类似,但它专注于已定义的单元格范围。通常,这与 !fullref 一致,或者是 !fullref 的子集。
例子: "A1:E36" 表示表格的有效数据区域为 A1 到 E36。
5. !rows
定义: 这是一个数组,用于定义每一行的高度或其他样式属性。
作用: 可以通过在数组中设置每一行的高度(以点为单位)来控制生成的 Excel 文件中每行的显示高度。
例子: 如果 !rows 为 [{hpt: 20}, {hpt: 30}],则第一行的高度为20点,第二行的高度为30点。
我使用了 !merges 合并单元格:
data['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 3 } }, { s: { r: 1, c: 0 }, e: { r: 1, c: 3 } }];合并了几个单元格
s: { r: 0, c: 0 }
: 起始单元格位于第0
行第0
列,即 Excel 中的A1
单元格。e: { r: 0, c: 3 }
: 结束单元格位于第0
行第3
列,即 Excel 中的D1
单元格。s: { r: 0, c: 0 }
和e: { r: 0, c: 3 }
说明将A1
到D1
单元格合并为一个单元格。
4).使用 FileSaver.saveAs(插件:file-saver) ,将已经生成的 Excel 文件保存到用户的本地计算机上
let file_name = 'xxx报表.xlsx'
if (name) {
file_name = name + '.xlsx'
}
//尝试将当前table内容保存为excel文件
try {
FileSaver.saveAs(
//被导出的blob二进制对象 [wbout]
new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
//导出文件的名称+后缀名
file_name
);
} catch (e) {
if (typeof console != "undefined") console.log(e, wbout);
}
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;
}
s2ab
函数的作用是将一个字符串转换为二进制格式,以便进一步处理或保存为文件。
注:new Blob([s2ab(wbout)], { type: "application/octet-stream" }) 解析:
1. Blob:Blob (Binary Large Object) 是一个表示二进制数据的大对象,通常用于存储或传输文件。这里的 Blob 对象将存储你想要导出的 Excel 文件内容。
2. s2ab(wbout):s2ab 是一个将字符串转换为 ArrayBuffer 的辅助函数,它将工作簿数据 wbout 转换为一个 ArrayBuffer,以便能够创建 Blob 对象。wbout 是之前生成的 Excel 工作簿的二进制内容。
3. { type: "application/octet-stream" }:这是一个 MIME 类型,表示要保存的文件是一个二进制流(未加工的二进制数据),通常用于下载文件。
s2ab
函数的作用是将一个字符串转换为二进制格式,以便进一步处理或保存为文件。
5.总体代码
父组件:获取id为pdfDom的HTML,点击导出excel
注:这里使用了element的table,而且表格是嵌套的,有一级二级三级(树形数据)
<div id="pdfDom" ref="printSection">
<el-table
max-height="550px"
empty-text="暂无报告"
v-loading="loading"
:data="staffJieyuList"
@selection-change="handleSelectionChange"
default-expand-all
row-key="row"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column align="center" :label="subTitle">
<el-table-column :label="searchInformation.lysuoName">
<el-table-column prop="id" align="center" label="序号">
</el-table-column>
<el-table-column prop="name" align="center" label="项目">
</el-table-column>
<el-table-column prop="amount" align="center" label="金额">
</el-table-column>
<el-table-column prop="contractNum" align="center" label="备注">
</el-table-column>
</el-table-column>
</el-table-column>
</el-table>
</div>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-download"
size="mini"
@click="btnEXCELClick"
v-hasPermi="['lawerfin:staffJieyu:export']"
>导出EXCEL
</el-button>
</el-col>
btnEXCELClick() {
exportTableAsXLSX("pdfDom", "律师结算报表", [
{ wch: 5 },
{ wch: 70 },
{ wch: 10 },
{ wch: 40 },
]);
},
子组件:处理HTML的结构、给excel添加样式、 导出excel
//导入依赖项
import FileSaver from "file-saver";
import XLSX from "xlsx";
import XLSXS from 'xlsx-style'
/**
* elemet-ui el-table数据导出为xlsx表格
* @param {*} _targetId Element-UI el-table组件的id值
* @param {*} name 导出文件名
*/
export const exportTableAsXLSX = function (_targetId, name, wscols = []) {
let table = document.getElementById(_targetId)
if (!table) return
let div = document.createElement('div');
div.innerHTML = table.innerHTML;
// 移除掉左右的fixed
let fixedLeft = div.querySelector('.el-table__fixed');
if (fixedLeft) fixedLeft.remove();
let fixedRight = div.querySelector('.el-table__fixed-right');
if (fixedRight) fixedRight.remove();
//表格在页面滚动,导出表格右边多出了几个多余的单元格,要删掉 或者使用下方的 if (word > 'D'){}处理
// let gutters = div.querySelectorAll('.gutter');
// gutters.forEach(gutter => {
// gutter.remove();
// });
console.log('this--table--',div);
const book = XLSX.utils.book_new()
const sheet = XLSX.utils.table_to_sheet(div)
XLSX.utils.book_append_sheet(book, sheet, 'Sheet1')
sheet.B1 = sheet.B2 = sheet.C1 = sheet.C2 = sheet.D1 = sheet.D2 = {}
setExcelStyle(sheet, wscols)
let wbout = XLSXS.write(book, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
})
let file_name = 'xx结算报表.xlsx'
if (name) {
file_name = name + '.xlsx'
}
//尝试将当前table内容保存为excel文件
try {
FileSaver.saveAs(
//被导出的blob二进制对象 [wbout]
new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
//导出文件的名称+后缀名
file_name
);
} catch (e) {
if (typeof console != "undefined") console.log(e, wbout);
}
};
// 设置导出Excel样式(统一样式)
function setExcelStyle(data, wscols = [], wpx = 80) {
data["!cols"] = wscols
// data['!merges'] = // 合并前三行 前6列 标题
data['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 3 } },
{ s: { r: 1, c: 0 }, e: { r: 1, c: 3 } }];
const excludes = ['!cols', '!fullref', '!merges', '!ref', '!rows']
let fillColorCell = []
let amassMonthYear = ['本月合计', '本年累计', '本年合计']
let fontBoldCell = [] //字体加粗的格子
let fontLeftCell =[] //字体靠左的项目内容
// 匹配大写字母、大写阿拉伯数字或者特定的中文字符的正则表达式
const pattern = /^(附|[A-Z]|[一二三四五六七八九十])+$/;
//除了1-3行,还有下面匹配的这些 文字居中,项目的内容要往左
const pattern2 = /^([一二三四五六七八九十])+$/;
// 匹配需要增加背景色的格子
for (let item in data) {
if (item.startsWith("A")) {
// console.log('item----data',item,data[item]);
if (pattern.test(data[item].v)) {
// 符合要求的逻辑处理
// console.log("数据符合要求--", data[item].v, item);
fillColorCell.push(Number(item.slice(1))) // [4, 9, 15, 16, 22, 35, 40, 43, 44, 47, 55, 60, 61, 62, 63, 64]
}
}
}
//匹配需要加粗字体的格子
for (let item in data) {
if (pattern.test(data[item].v) || amassMonthYear.includes(data[item].v)) {
// 符合要求的逻辑处理
fontBoldCell.push(Number(item.slice(1)))
}
}
//字体靠左的项目内容
for (let item in data) {
if (pattern2.test(data[item].v)) {
fontLeftCell.push(Number(item.slice(1)))
}
}
for (let key in data) {
if (data.hasOwnProperty(key)) {
if (!excludes.includes(key)) {
let num = Number(key.slice(1))
let word = key.slice(0, 1)
data[key].s = {
alignment: {
horizontal: "center", //水平居中对齐
vertical: "center", // 垂直居中
wrapText: true,
},
border: {
top: {
style: 'thin',
color: { rgb: '000000' }
},
bottom: {
style: 'thin',
color: { rgb: '000000' }
},
left: {
style: 'thin',
color: { rgb: '000000' }
},
right: {
style: 'thin',
color: { rgb: '000000' }
}
},
fill: {
fgColor: { rgb: "00a2ff" },
},
font: {
sz: 12,
},
bold: true,
numFmt: 0
}
//E开始的单元格,多出了几个表格,设置边框消失
// if (word > 'D') {
// data[key].s.border = {
// top: {
// style: 'thin',
// color: { rgb: 'e6e6e6' }
// },
// bottom: {
// style: 'thin',
// color: { rgb: 'e6e6e6' }
// },
// left: {
// style: 'thin',
// color: { rgb: '000000' }
// },
// right: {
// style: 'thin',
// color: { rgb: 'e6e6e6' }
// }
// }
// }
// 根据不同行添加单元格背景颜色
let color = ''
if ((fillColorCell.includes(num) || num == 1 || num == 2 || num == 3) && word < 'E') {
color = 'd9e1f4'
data[key].s.alignment = {
horizontal: "center", //居中对其
vertical: "center", // 垂直居中
wrapText: true,
}
data[key].s.font = {
sz: 12,
// bold: true,
}
} else {
color = 'ffffff'
}
// 最三面三行标题设置不同的颜色
if (num < 4 && word < 'E') {
color = '91aadf'
}
//字体加粗
if((fontBoldCell.includes(num) || num == 1 || num == 2 || num == 3) && word < 'E'){
data[key].s.font = {
sz: 12,
bold: true,
}
}
if(((!fontLeftCell.includes(num) && num !== 1 && num !== 3) && word == 'B') || num == 2){
data[key].s.alignment = {
horizontal: "left", //左对其
vertical: "center", // 垂直居中
wrapText: true,
}
}
//设置千分位,调用excel格式,0还是0
if(!isNaN(parseFloat(data[key].v)) && Math.abs(parseFloat(data[key].v)) > 0){
// data[key].v = parseFloat(data[key].v).toLocaleString() + ''
data[key].s.numFmt = '#,##0.00_-'
}
data[key].s.fill = { fgColor: { rgb: color, patternType: 'solid' } }
// data[key].s.border = { bottom: {style: 'thin', color: { rgb: '000000' }} }
}
}
}
}
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
}