Commit 8e477e08 authored by liyan's avatar liyan

5月分享

parent 95a67035
---
title: 用xlsx-style玩转Excel导出——拿走即用的Excel导出方法封装
date: 2022/5/24 09:15:20
tags:
- 前端
- xlsx-style
- JavaScript
author: 李燕
---
[# 用xlsx-style玩转Excel导出——像手动操作Excel表格一样用JS操作Excel](https://juejin.cn/post/7090185856832503844)一文中,我们详细介绍了xlsx-style插件相关的概念、属性和方法,今天,我们将利用xlsx-style插件封装出一个通用的、可自定义的Excel导出方法,以期能够满足日常Web项目的导出需求。
<!-- more -->
**一、常见自定义需求**
在正式开始封装之前,我们先梳理一下,我们Excel导出一般都有那些自定义需求。
1. 导出的Excel文件名自定义
2. 支持单sheet、多sheet导出
3. sheet名自定义
4. 表头内容自定义
5. 表格内容自定义
6. 表格样式自定义
7. 支持合并单元格
8. 支持冻结单元格
**二、抽象导出方法名及参数**
根据上述自定义需求,我们可以初步将Excel导出方法及参数定义如下:
```js
/**
* 自定义导出excel
* @param { 导出的数据 } exportData
* @param { 工作簿配置 } workBookConfig
* @param { 导出文件名 } fileName
*/
function exportExcel(exportData, workBookConfig = defaultWorkBook, fileName = '未命名') {
}
```
**三、参数介绍及处理**
(一)exportData
参数exportData是一个对象数组,可以满足单sheet、多sheet的导出,同时可以配置每个sheet的表头、表格内容、样式等,格式如下:
```js
[
{
header: [] // 表头
dataSource: [], // 数据源
workSheetConfig: {}, // 工作表配置
cellConfig: {} // 单元格配置
sheetName: '' // sheet名
}
]
```
+ header
header是一个对象数组,用来设置表头,格式如下:
```js
[
{ title: '分站', dataIndex: 'subName', width: 140 },
{ title: '企业总数', dataIndex: 'enterpriseCount', width: 140 },
]
```
接收到header后,需将header内容处理成表格语言,具体如下:
```js
const _header = header.map((item, i) =>
Object.assign({}, {
key: item.dataIndex,
title: item.title,
// 定位单元格
position: getCharCol(i) + 1,
// 设置表头样式
s: data.cellConfig && data.cellConfig.headerStyle ? data.cellConfig.headerStyle : defaultCellStyle.headerStyle,
})
).reduce((prev, next) =>
Object.assign({}, prev, {
[next.position]: { v: next.title, key: next.key,s: next.s },
}), {},
)
```
其中,_header的打印格式如下:
![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3924e8fdb5f846da9f2eb7b3c6eef381~tplv-k3u1fbpfcp-watermark.image?)
从上述打印可以看出,header已经被处理成worksheet Object,而其中很重要一步,就是定位单元格,而我们Excel是用字母+数字的形式定位单元格的,所以我们可以通过如下代码定位单元格。
```js
position: getCharCol(i) + 1
```
其中,getCharCol()方法是用来生成单元格对应的字母的,代码如下:
```js
/**
* 生成ASCll值 从A开始
* @param {*} n
*/
function getCharCol(n) {
if( n > 25) {
let s = ''
let m = 0
while (n > 0) {
m = n % 26 + 1
s = String.fromCharCode(m + 64) + s
n = (n - m) / 26
}
return s
}
return String.fromCharCode(65 + n)
}
```
+ cellConfig
cellConfig是一个对象,用来配置单元格的样式,格式如下:
```js
{
headerStyle: { // 表头区域样式配置
border: {}
font: {},
alignment: {},
fill: {},
},
dataStyle: { // 内容样式配置
border: {}
font: {},
alignment: {},
fill: {},
}
}
```
+ dataSource
dataSource是一个对象数组,用来表示表格内容,格式如下:
```js
[
{ subName'合肥' },
{ enterpriseCount: '100' },
]
```
同样,接收到dataSource后,需将dataSource内容处理成表格语言,具体如下:
```js
let _data = {};
// 处理表格内容及样式
dataSource.forEach((item, i) => {
header.forEach((obj, index) => {
const key = getCharCol(index) + (i + 2);
const key_t = obj.dataIndex;
_data[key] = {
v: item[key_t],
s: cellConfig.dataStyle? cellConfig.dataStyle : defaultCellStyle.dataStyle
}
})
});
```
+ workSheetConfig
workSheetConfig是一个对象,用来配置工作表的合并单元格、冻结单元格等功能,格式如下:
```js
{
merges: [ // 合并单元格
{ s: { c: 1, r: 1 },e: { c: 3, r: 3 } }
],
freeze{ // 冻结单元格
xSplit: '1',
ySplit: '1',
topLeftCell: 'B2',
state: 'frozen',
}
}
```
(二)workBookConfig
workBookConfig是一个对象,用来配置工作簿的类型等,格式如下:
```js
{
bookType: 'xlsx', // 工作簿的类型
bookSST: false,
type: 'binary', // 输出数据类型
}
```
**四、生成workbook object**
```js
const wb = {SheetNames: [], Sheets: {}}
exportData.forEach( (data, index) => {
const output = Object.assign({}, _headers, _data);
const outputPos = Object.keys(output);
// 设置单元格宽度
const colWidth = data.header.map( item => { wpx: item.width || 60 })
const merges = data.workSheetConfig && data.workSheetConfig.merges
const freeze = data.workSheetConfig && data.workSheetConfig.freeze
// 处理sheet名
wb.SheetNames[index] = data.sheetName ? data.sheetName : 'Sheet' + (index + 1)
// 处理sheet数据
wb.Sheets[wb.SheetNames[index]] = Object.assign({},
output, //导出的内容
{
'!ref': `${outputPos[0]}:${outputPos[outputPos.length - 1]}`,
'!cols': [...colWidth],
'!merges': merges ? [...merges] : undefined,
'!freeze': freeze ? [...freeze] : undefined,
}
)
})
```
**五、new Blob转换成二进制类型的对象**
```js
const tmpDown = new Blob(
[ s2ab( XLSX.write(wb, workBookConfig))],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}
)
// 字符串转字符流---转化为二进制的数据流
function s2ab(s) {
if (typeof ArrayBuffer !== 'undefined') {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf;
} else {
const buf = new Array(s.length);
for (let i = 0; i !== s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff;
return buf;
}
}
```
**六、下载Excel**
```js
downExcel(tmpDown, `${fileName + '.'}${workBookConfig.bookType == 'biff2' ? 'xls' : workBookConfig.bookType}`)
function downExcel(obj, fileName) {
const a_node = document.createElement('a');
a_node.download = fileName;
// 兼容ie
if ('msSaveOrOpenBlob' in navigator) {
window.navigator.msSaveOrOpenBlob(obj, fileName);
} else {
// 新的对象URL指向执行的File对象或者是Blob对象.
a_node.href = URL.createObjectURL(obj);
}
a_node.click();
setTimeout(() => {
URL.revokeObjectURL(obj);
}, 100);
}
```
**七、默认配置**
```js
// 默认工作簿配置
const defaultWorkBook = {
bookType: 'xlsx',
bookSST: false,
type: 'binary',
}
// 默认样式配置
const borderAll = { //单元格外侧框线
top: {
style: 'thin',
},
bottom: {
style: 'thin'
},
left: {
style: 'thin'
},
right: {
style: 'thin'
}
};
const defaultCellStyle = {
headerStyle: { // 表头区域样式配置
border: borderAll
font: { name: '宋体', sz: 11, italic: false, underline: false, bold: true },
alignment: { vertical: 'center', horizontal: 'center'},
fill: { fgColor: { rgb: 'FFFFFF' } },
},
dataStyle: { // 内容样式配置
border: borderAll
font: { name: '宋体', sz: 11, italic: false, underline: false },
alignment: { vertical: 'center', horizontal: 'left', wrapText: true},
fill: { fgColor: { rgb: 'FFFFFF' } },
}
}
```
**八、完整代码**
```js
import XLSX from 'xlsx-style'
// 默认工作簿配置
const defaultWorkBook = {
bookType: 'xlsx',
bookSST: false,
type: 'binary',
}
// 默认样式配置
const borderAll = {
top: {
style: 'thin',
},
bottom: {
style: 'thin',
},
left: {
style: 'thin',
},
right: {
style: 'thin',
},
}
const defaultCellStyle = {
// 表头区域样式配置
headerStyle: {
border: borderAll,
font: { name: '宋体', sz: 11, italic: false, underline: false, bold: true },
alignment: { vertical: 'center', horizontal: 'center' },
fill: { fgColor: { rgb: 'FFFFFF' } },
},
// 内容区域样式配置
dataStyle: {
border: borderAll,
font: { name: '宋体', sz: 11, italic: false, underline: false },
alignment: { vertical: 'center', horizontal: 'left', wrapText: true },
fill: { fgColor: { rgb: 'FFFFFF' } },
},
}
function exportExcel(exportData, workBookConfig = defaultWorkBook, fileName = '未命名') {
if (!(exportData && exportData.length)) {
return
}
// 定义工作簿对象
const wb = { SheetNames: [], Sheets: {} }
exportData.forEach((data, index) => {
// 处理sheet表头
const _header = data.header.map((item, i) =>
Object.assign({}, {
key: item.dataIndex,
title: item.title,
// 定位单元格
position: getCharCol(i) + 1,
// 设置表头样式
s: data.cellConfig && data.cellConfig.headerStyle ? data.cellConfig.headerStyle : defaultCellStyle.headerStyle,
})
).reduce((prev, next) =>
Object.assign({}, prev, {
[next.position]: { v: next.title, key: next.key, s: next.s },
}), {}
)
// 处理sheet内容
const _data = {}
data.dataSource.forEach((item, i) => {
data.header.forEach((obj, index) => {
const key = getCharCol(index) + (i + 2)
const key_t = obj.dataIndex
_data[key] = {
v: item[key_t],
s: data.cellConfig && data.cellConfig.dataStyle ? data.cellConfig.dataStyle : defaultCellStyle.dataStyle,
}
})
})
const output = Object.assign({}, _header, _data)
const outputPos = Object.keys(output)
// 设置单元格宽度
const colWidth = data.header.map(item => { return { wpx: item.width || 80 } })
const merges = data.workSheetConfig && data.workSheetConfig.merges
const freeze = data.workSheetConfig && data.workSheetConfig.freeze
// 处理sheet名
wb.SheetNames[index] = data.sheetName ? data.sheetName : 'Sheet' + (index + 1)
// 处理sheet数据
wb.Sheets[wb.SheetNames[index]] = Object.assign({},
output, // 导出的内容
{
'!ref': `${outputPos[0]}:${outputPos[outputPos.length - 1]}`,
'!cols': [...colWidth],
'!merges': merges ? [...merges] : undefined,
'!freeze': freeze ? [...freeze] : undefined,
}
)
})
// 转成二进制对象
const tmpDown = new Blob(
[s2ab(XLSX.write(wb, workBookConfig))],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
)
// 下载表格
downExcel(tmpDown, `${fileName + '.'}${workBookConfig.bookType === 'biff2' ? 'xls' : workBookConfig.bookType}`)
}
/**
* 生成ASCll值 从A开始
* @param {*} n
*/
function getCharCol(n) {
if (n > 25) {
let s = ''
let m = 0
while (n > 0) {
m = n % 26 + 1
s = String.fromCharCode(m + 64) + s
n = (n - m) / 26
}
return s
}
return String.fromCharCode(65 + n)
}
// 字符串转字符流---转化为二进制的数据流
function s2ab(s) {
if (typeof ArrayBuffer !== 'undefined') {
const buf = new ArrayBuffer(s.length)
const view = new Uint8Array(buf)
for (let i = 0; i !== s.length; ++i) { view[i] = s.charCodeAt(i) & 0xff }
return buf
} else {
const buf = new Array(s.length)
for (let i = 0; i !== s.length; ++i) { buf[i] = s.charCodeAt(i) & 0xff }
return buf
}
}
function downExcel(obj, fileName) {
const a_node = document.createElement('a')
a_node.download = fileName
// 兼容ie
if ('msSaveOrOpenBlob' in navigator) {
window.navigator.msSaveOrOpenBlob(obj, fileName)
} else {
// 新的对象URL指向执行的File对象或者是Blob对象.
a_node.href = URL.createObjectURL(obj)
}
a_node.click()
setTimeout(() => {
URL.revokeObjectURL(obj)
}, 100)
}
export {
exportExcel,
}
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment