前端文件下载的几种方式

// 1. 直接下载 (针对一些浏览器无法识别的文件格式,直接在地址栏上输入URL即可触发浏览器的下载功能)  
window.location.href = URL;
window.open(URL);

// 2. 直接下载 使用A标签download属性  
<a href="file.xlsx" download="文件名">文件下载</a>

// 3. 通过触发JS直接下载
function dowloadFile(filename, link) {
	let DownloadLink= document.createElement('a')
	DownloadLink.style = 'display: none;'
	DownloadLink.download = filename
	DownloadLink.href = link
	document.body.appendChild(DownloadLink)
	DownloadLink.click();
	document.body.removeChild(DownloadLink)
}

SheetJS 生成/解析 Excel 介绍

SheetJs Github
使用SheetJSxlsx.js实现生成excel表格
纯JS即可读取/生成excel,功能强大,支持多种格式,兼容性高

xlsx.js提供了一个中间层用于操作数据,将不同类型的文件抽象成同一个js对象,从而规避了操作不同种类数据之间的复杂性。

基本概念

  • 工作簿 workbook 对象:指的是整份 Excel 文档。
  • 工作表 worksheet 对象:指的是 Excel 文档中的表。可以包含很多张表,每张表对应一个 worksheet 对象
  • 单元格 cell 对象:指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象
{
	"SheetNames": ["sheet1"],
	"Sheets": {
		"Sheet1": {
			!ref: “A1:J3”, // 单元格范围
            !rows: [], // 表格行属性
            !cols: [
                {wpx: 90}
            ], // 单元格属性
			!merges: [], // 单元格合并规则
			A1/B1/C1...: {}, // 对应excel中的每一个具体的单元格
		}
	}
}

单元格对象
sheet中的: A1/B1/C1...: 单元格描述信息

属性 描述
t 表示内容类型 { s: ‘string’, n: 'number', b: 'boolean', d: 'date' }
v 原始值
f 公式,如: B2 + B3
h HTML内容
w 格式化后的内容
r 富文本内容

!merges合并单元格

// 数组的每一项,代表其中一个单元格的合并要求
ws['!merges'] = [
	[s: {r: 0, c: 0}, e: {r: 1, c: 0}],
	[s: {r: 0, c: 1}, e: {r: 1, c: 1}]
] 
// s: 代表合并的起始位置
// e: 代表合并的结束位置  
// r: 代表行数
// c: 代表列数
// 数组的第一个对象,表达的含义为: 以0行0列(对应单元格A1)作为起始,以1行0列(对应单元格A2)作为结束,合并这些单元格

单元格地址
xlsx使用有两种方式来描述操作中的单元格区域:

  • 一种是单元格地址对象 (Cell address object)
// 地址对象描述的是一个起始坐标(从0开始)到结束坐标之间的范围
 const start = { c: 0, r: 0 };
 const end = { c: 1, r: 1 };
  • 另一种是地址范围 (Cell range)
// 地址范围就是Excel中的引用样式
const range = 'A1:B2'

worksheet Object 工作表对象

{
	"sheet1": {
		"A1": {}, // 单元格对象
		"!ref": "A1:B5", // 表示工作表范围
		“!cols”: [
			[ wpx: 200 ], // 设置第1列列狂为200箱数
			[ wch: 50 ], // 设置第2列列宽为50字符
		],
		"!merges": [
			{
				s: { c: 1, r: 1 }, // B2
				s: { c: 3, r: 3 }, // D4
			}
		], // 合并单元格范围数据
		"!freeze": {
			"xSplit": "1", // 冻结列
			“ySplit": "1", // 冻结行
		}
	}
}

xlsx.js 引入

// 以 script 标签的形式
<script lang="javascript" src="dist/xlsx.full.min.js"></script>

// 使用 npm 安装 xlsx 模块
npm install xlsx --save
// 引入
import XLSX from 'xlsx'
// 或
const XLSX = require('xlsx')

xlsx.js 的一些常用方法

读取数据并解析:

方法名 描述
XLSX.readFile(filename[, opts]) 获取数据
xlsx.readFileSync(filename[, opts]) 异步获取数据
XLSX.read(data[, opts]) 解析数据

数据导出:

方法名 描述
XLSX.write(workbook[, opts]) 用来写入工作簿 workbook
XLSX.writeFile(workbook, filename[, opts]) 把 workbook 写入到特定的文件 filename 中

XLSX.utils对象中有一些方法可以对单元格和单元格范围进行转化

// 编码行号
XLSX.utils.encode_row(2); // "3"
// 解码行号
XLSX.utils.decode_row("2"); // 1

// 编码列
XLSX.utils.encode_col(2); // "C"
// 解码列 
XLSX.utils.decode_col("A"); // 0

// 编码单元格
XLSX.utils.encode_cell({ c: 1, r: 1 }); // "B2"
// 解码单元格
XLSX.utils.decode_cell("B1"); // { c: 1, r: 0 }

// 编码单元格范围
XLSX.utils.encode_range({
	s: { c:1, r: 0 },
	e: { c:2, r: 8 }
}); // "B1:C9"
// 解码单元格范围
XLSX.utils.decode_range("B1:C9");
// {s: {r: 1, r: 0}, e: {c: 2, r: 8}}

操作工作簿

// 1. 创建一个工作簿
const workBook = XLSX.utils.book_new();

// 2. 创建工作表对象的几种方式 
// 使用二维数组创建一个工作表对象
const workSheet = XLSX.utils.aoa_to_sheet(data); 
// 使用对象数据创建一个工作表对象
const workSheet = XLSX.utils.json_to_sheet(data);
// 根据已渲染好的表格,转成工作表数据,会自动识别对应的单元格合并数据
const workSheet = XLSX.utils.table_to_sheet("表格ID")

// 向工作簿追加一个工作表
XLSX.utils.book_append_sheet(workBook, workSheet, "工作表名称")

数据填充 - 创建工作表
工作表是实际存放数据的地方,大部分情况下我们的操作都是在对工作表对象的小左。
aoa_to_sheet 根据二维数组创建工作表

var data1 = [
	['主要信息', null, null, '其它信息'],  
	['姓名', '性别', '年龄', '注册时间'],
	['张三', '男', 18, new Date()],
	['李四', '女', 22, new Date()]
];
// 1. 新建一个工作簿
let workbook = XLSX.utils.book_new();
// 2. 生成一个工作表,
// 2.1 aoa_to_sheet 把数组转换为工作表
let sheet1 = XLSX.utils.aoa_to_sheet(data1);
// 3. 设置单元格合并
sheet1['!merges'] = [
	// 设置A1-C1的单元格合并
    { s: {r: 0, c: 0}, e: {r: 0, c: 2 }}
]; 
// 4.在工作簿中添加工作表
XLSX.utils.book_append_sheet(workbook, sheet1, "工作表名称");  
// 5.输出工作表,由文件名决定的输出格式
XLSX.writeFile(workbook, "默认的导出文件名称.xlsx");

**json_to_sheet 根据二维数组创建工作表: **

const data = [
	{ L: 8, O: 5, V: 2, E: 7, ID: 'i_8527' },
	{ L: 1, O: 5, V: 9, E: 2, IDL 'i_15926'}
]
// 基础版: 会自动提起键名作为表头
let worksheet = XLSX.utils.json_to_sheet(data)

展示结果:

L O V E ID
8 5 2 7 i_8527
1 5 9 2 I_15926

排序并自定义表头

// 自定义表格的名称
const headerDisplay = {
	L: "L栏", O: "O栏", V: "V栏", E: "E栏", ID: "ID栏" 
}
const newData = [headerDisplay, ...data]
let worksheet = XLSX.utils.json_to_sheet(data, {
    header: ['ID'] // 表格列排序, 示吧ID移动到最前面
    skipHeader: true // 忽略表格原有的表头
})
ID L O V E
i_8527 8 5 2 7
I_15926 1 5 9 2

数据填充 - 修改工作表数据

// 创建工作表
const worksheet = XLSX.utils.json_to_sheet([
	{ '列1': 1, '列2': 2, '列3': 3 },
	{ '列1': 4, '列2': 5, '列3': 6 }
], {
	header: ['列3', '列2', '列1'], // 排序
	skipHeader: true  
})
// 修改成新数据 sheet_to_aoa 二维数组方式
XLSX.utils.sheet_to_aoa(worksheet, [
	['姓名', '年龄', '地址'],
	["张三", 26, "北京"],
    ["小右", 18, "成都"]
],{
	origin: 'A1' // 从A1开始增加内容
})

// -------------------------------

// 修改成新数据 sheet_to_json 方式
XLSX.utils.sheet_to_json(worksheet, [
	{ "年龄": 26, "地址": "深圳", "姓名": "张三" },
	{ "年龄": 18, "地址": "成都", "姓名": "小右" },
], {
	origin: "A1",
	header: ["姓名", "年龄", "地址"],
	skipHeader: true
})
姓名 年龄 地址
张三 26 深圳
小右 18 成都

导出Excel文件

根据已经渲染好的表格进行导出Excel :会自动识别单元格合并

/** 
 * arrTableId 传参 多条表示有多个表
 * [ 
 *  {id: 'excel1', sheetName: 'sheet1'}, 
 *  {id: 'excel2', sheetName: 'sheet2'}
 * ]
 */
export function exportTableToExcel(arrTableId, excelFileName) {
	const workbook = XLSX.utils.book_new();
	// 循环得到每一张表的 sheet,并添加到 workbook 中
	arrTableId.forEach((item) => {
		const tableEle = document.getElementById(item.id);
		const ws = XLSX.utils.table_to_sheet(tableEle);
		XLSX.utils.book_append_sheet(workbook, ws, item.sheetName)
	})
	// 导出Excel
	XLSX.writeFile(workbook, excelFileName)
}

// 使用 
const exportParams = [{ id: "xGrid1", sheetName: "工作表名称" }]
this.exportTableToExcel(exportParams, "导出后保存的文件名称.xlsx");

根据 aoa_to_sheetjson_to_sheet创建的工作表进行导出,复杂表头需要提供合并的 !merges数据

// 复杂表头示例 
const workbook = XLSX.utils.book_new();
const data = [
	// 特别注意: 合并的地方后面预览 null
	[ "主要信息", null, null, "其他信息" ],
	[ "姓名", "性别", "年龄", "注册时间" ],
	[ "张三", "男", 26, new Date() ],
	[ "李四", "女", 18, new Date() ]
]
const sheet = XLSX.utils.aoa_to_sheet(data);
sheet['!merges'] = [
	// 设置A1-C1的单元格合并
	{ s: { r: 0, c: 0 }, e: { r: 0, c: 2 }}
]
XLSX.utils.book_append_sheet(workbook, ws, sheet)
// 导出Excel
XLSX.writeFile(workbook, excelFileName)

合并单元格方案

需要自己提供单元格合并范围的数据

const mergesArr = ['A1:A3', 'B1:F1', 'B2:B3', 'C2:F2']
const range = ["A1:A3", "B1:F1", "B2:B3", "C2:F2"].map((item) => {
	return XLSX.utils.decode_range(item);
});
sheet1["!merges"] = range;