由SheetJS出品的js-xlsx是一款非常方便的只需要纯JS即可读取和导出excel的工具库,功能强大,支持格式众多,API十分简洁方便,当前版本0.16.9
$ npm install xlsx --save
导入的时候需要定义一个模板,让用户按照模板填写excel,模板的第一行定义后台数据实体类的属性名,当数据读取成功后会是json格式[{name:‘’,phoneNo:‘’,description:‘’},{}…]
name | phoneNo | description |
---|---|---|
张三 | 13788888888 | 备注 |
<el-button type="primary" size="small" class="file-wrap"><a class="seat" download="批量导入联系人模板" href="/static/templateExcel/infoPush-batch-import.xlsx">a>文件模板下载el-button>
<el-button type="primary" size="small" class="file-wrap"><input class="seat" type="file" @change="excel2Array($event)">选择文件导入el-button>
excel2Array(evt,fn){
let _this = this
console.log('start analysis excel ')
// elementui库里的loading
let loading = this.$loading({text:'请稍后...'})
let file;
let files = evt.target.files;
if (!files || files.length == 0) return;
file = files[0];
let reader = new FileReader();
reader.onload = function (e) {
// pre-process data
let binary = "";
let bytes = new Uint8Array(e.target.result);
// 这种写法可以省去for循环
// let data = new Uint8Array(e.target.result);
// let wb = XLSX.read(data, {type: 'array'});
let length = bytes.byteLength;
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
/* read workbook */
let wb = XLSX.read(binary, {type: 'binary'});
/* grab first sheet */
let wsname = wb.SheetNames[0];
let ws = wb.Sheets[wsname];
let jsonArray = XLSX.utils.sheet_to_json(ws)
console.log('json:',jsonArray)
// jsonArray是从excel读出的数据
fn(jsonArray)
loading.close()
/* 可以调用这个方法生成HTML预览 */
// let HTML = XLSX.utils.sheet_to_html(ws);
};
reader.readAsArrayBuffer(file);
},
API解释:XLSX.utils.sheet_to_json(ws,props),把excel转成一个json对象
props.raw=false表示输出的数据的属性的值全部转成string
props.header有下面4种情况
header |
Description | eg |
---|---|---|
1 |
生成一个没有key的二维数组 | {header:1} |
"A" |
生成以ABCDEF…作为key | {header:“A”} |
array of strings | 自定义key的值 | {header:[“name”,“phoneNo”,“description”]} |
(default) | 生成以excel第一行的值作为key |
eg: XLSX.utils.sheet_to_json(ws,{header:[“name”,“phoneNo”,“description”,“sex”,“age”],raw:false}),如果这里不设置raw=false,那么mobile的类型为number;一般情况下我们都会指定header为array of strings,这样可以让我们excel的表头不与数据库字段对应,
默认情况会转成浮点型,比如44199.0571180556,可以使用XLSX.SSF.parse_date_code(44199.0571180556)得到我们想要的时间;当然也可以把excel中的时间设成字符串类型;另一种方法是指定cellDates:true,让工具直接解析成date类型而不是数字XLSX.read(binary, {type: ‘binary’,cellDates:true}),但是经过测试这种方法有一点误差;
.file-wrap{
position: relative;
overflow: hidden;
.seat{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
cursor: pointer;
}
}
导出的时候事先需要把数据格式转换成一个二维数组[[prop,prop,prop],[…]…]
array2Excel(arrayData,excelName){
let loading = this.$loading({text:'请稍后...'})
let data = [
[ "no data","no data","no data" ],
]
if (!arrayData) {
arrayData = data
}
/* convert from array of arrays to workbook */
let worksheet = XLSX.utils.aoa_to_sheet(arrayData);
// var worksheet = XLSX.utils.json_to_sheet(data.data);
/* generate workbook and add the worksheet */
let new_workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(new_workbook, worksheet, "sheet1");
/* save to file */
XLSX.writeFile(new_workbook, excelName+".xlsx");
loading.close()
}
还可以直接读取html–table导出
/* generate workbook object from table */
var wb = XLSX.utils.table_to_book(document.getElementById('out-table'));
/* generate file and force a download*/
XLSX.writeFile(wb, "sheetjs.xlsx");
html
<div id="out-table">
<table >
<tr><th>一列th><th>二列th>tr>
<tr><td>111td><td>22td>tr>
table>
div>
//自定义样式:列宽
// worksheet['!cols'] = [{wch: 10},{wch: 10}]
worksheet['!cols'] = Array.from({length:arrayData[0].length},()=>{
return {wch: 10}
})
//自定义样式:行高
worksheet['!rows'] = [{hpx: 25},{hpx: 35}]
// 单元格合并 第一行合并为一个单元格
worksheet['!merges'] = [{
// start 起始位置
s: {
// 列 col
c: 0,
// 行 row
r: 0
},
// end 结束位置
e: {
c: worksheet['!cols'].length - 1,
r: 0
}
}]
社区版的xlsx并没有提供样式修改的能力,但是社区有人提供了这个能力,详情请看xlsx-style。但遗憾的是这个库的代码有点老,有些官方好用的工具都没有同步过来,还好又有人做了跟进,下面根据layui作者修改后的xlx-style为例子抛砖引玉。点击下载
先看结果:
调用代码:
excelDownload(){
let option = {
excelName:'myExcel',
sheetNames:['结算表'],
columnWidth:[14,14,14,14,14,14,14,14,14],
lineHeight:[26,15,20,20],
// 单元格合并
merges:[{pos:'A1:I1'}],
styles:[
{
// 设置整体样式
pos:'A1:I6',
style:{
font: {
name: '宋体',
sz: 12
},
}
},
{
// 设置title
pos:'A1:A1',
style:{
font: {
bold: true,
sz: 16
},
alignment: { horizontal: "center", vertical: "center", wrap_text: true },
}
},
{
// 设置header
pos:'A3:I3',
style:{
font: {
bold: true
}
}
},
{
// 设置中心区域
pos:'A3:I4',
style:{
alignment: { vertical: "center", wrap_text: true },
border: {
top: {
style: "thin"
},
left: {
style: "thin"
},
right: {
style: "thin"
},
bottom: {
style: "thin"
}
}
}
}
]
}
this.$array2Excel([['2021.5.01-5.15无人岛收入结算表']
,[]
,['店铺名称','未核销张数','未核销金额','已核销张数','已核销金额','应支出佣金','店铺收入','旅行社收入','平台服务费']
,['无人岛',1,2,3,4,5,6,7,8]
,[]
,['董事长:','','','总经理:','','','财务:','','技术部:']],
option)
}
这里代码量最大的是设置单元格样式,不过也简单
fill:{
fgColor:{
rgb:'ff0000'
}
}
font: {
name: '宋体', //字体默认为Calibri
sz: 12, //font-size
color: {rgb:'00ff00'},
bold:true,
underline:true,
italic:true
}
border: {
top: {
style: "thin"
},
left: {
style: "thin"
},
right: {
style: "thin"
},
bottom: {
style: "thin",
color: {rgb:'00ff00'}//指定下边框颜色
}
}
alignment: {
horizontal: "center",
vertical: "center",
wrap_text: true ,
textRotation:45 // 单元格旋转45度,取值范围0-180
}
在作者提供的修改样式的API基础上,对常用的功能做了封装,支持多sheet、隔行变色、隔列变色、列宽设置、行高设置、单元格合并等,核心代码如下:
import XLSX from '../../static/xlsx.min.js';
....
$array2Excel(data,option= {}){
option.data = data
let defaultOption = {
excelName:'excel',
// 表格数据 若是三维数组对应多个sheet
data:[[]],
sheetNames:[],
// 表格行高 若是二维数组对应多个sheet
lineHeight:[[]],
// 表格列宽 若是二维数组对应多个sheet
columnWidth:[[]],
// 单元格合并 若是二维数组对应多个sheet
merges:[[]],
// 单元格样式 若是二维数组对应多个sheet
styles:[[]],
}
option = Object.assign(defaultOption,option)
let loading = this.$loading({text:'请稍后...'})
checkOption(option)
function checkOption(option){
if (!Array.isArray(option.data[0][0])){
let arr = [];
arr.push(option.data)
option.data = arr
}
checkArray(option,'merges')
checkArray(option,'styles')
checkArray(option,'columnWidth')
checkArray(option,'lineHeight')
function checkArray(option,key){
if (!Array.isArray(option[key][0])){
let arr = [];
arr.push(option[key])
option[key] = arr
}
}
let defaultSheetNames = Array.from({length:option.data.length},(val,index)=>'sheet'+(index+1))
option.sheetNames = Object.assign(defaultSheetNames,option.sheetNames)
console.log('data:',option.data)
}
let worksheetArr = []
for (let i=0;i<option.data.length;i++) {
/* convert from array of arrays to workbook */
let worksheet = XLSX.utils.aoa_to_sheet(option.data[i]);
// let worksheet = XLSX.utils.json_to_sheet(data.data);
/* --单元格合并 */
mergeFun(worksheet,option.merges[i])
/* --设置样式 */
setStyle(worksheet,option.styles[i])
/* --设置列宽和行高 */
setWidthAndHeight(worksheet,option.columnWidth[i],option.lineHeight[i])
console.log('worksheet:',worksheet)
worksheetArr.push(worksheet)
}
/* generate workbook and add the worksheet */
let new_workbook = XLSX.utils.book_new();
worksheetArr.forEach((worksheet,i)=>{
XLSX.utils.book_append_sheet(new_workbook, worksheet, option.sheetNames[i]);
})
/* save to file */
XLSX.writeFile(new_workbook, (option.excelName||'excel') + ".xlsx");
loading.close()
// 设置列宽和行高
function setWidthAndHeight(worksheet,colWidthArr=[],lineHeightArr=[]){
if (colWidthArr.length>0){
//自定义样式:列宽 [{wpx: 100},{wpx: 100}]
worksheet['!cols'] = colWidthArr.map(val=>{return {wch:val}})
}
if (lineHeightArr.length>0){
//自定义样式:行高 [{hpx: 25},{hpx: 35}]
worksheet['!rows'] = lineHeightArr.map(val=>{return {hpx:val}})
}
}
// 单元格合并
function mergeFun(worksheet,merges){
merges = merges || []
let mergesArr = []
for (const merge of merges) {
let pos = merge.pos
if (pos){
let posObj = decodePos(pos)
let p1 = posObj.p1
let p2 = posObj.p2
mergesArr.push({s:p1,e:p2})
}
}
if (mergesArr.length>0){
worksheet['!merges'] = mergesArr
}
}
// 设置样式
function setStyle(worksheet,styles){
styles = styles || []
// let styles = option.styles
// !ref: "A1:H2"
let region = worksheet['!ref']
for (const style of styles) {
// 1、按行设置 2*n 2*n+1
let nthRow = style.nthRow
// 2、按列设置 2*n 2*n+1
let nthCol = style.nthCol
if (nthRow||nthCol){
let posObj = decodePos(region)
let p1 = posObj.p1
let p2 = posObj.p2
if (nthRow){
let rowArr = []
// 行
for (var n=p1.r;n<=p2.r;n++){
let posRow = eval(nthRow)
rowArr.push(posRow)
if (rowArr.includes(n)){
// 列
for (let j = p1.c; j <=p2.c ; j++) {
// 坐标转成字母ABCDEF
let pp = XLSX.utils.encode_cell({r:n,c:j})
// worksheet[pp].s = style.style
if (worksheet[pp]){
worksheet[pp].s = assignFun(worksheet[pp].s,style.style)
}
}
}
}
console.log('rowArr:',rowArr)
}
if (nthCol){
let colArr = []
// 行
for (let i=p1.r;i<=p2.r;i++){
// 列
for (var n = p1.c; n <=p2.c; n++) {
let posCol = eval(nthCol)
colArr.push(posCol)
if (colArr.includes(n)){
// 坐标转成字母ABCDEF
let pp = XLSX.utils.encode_cell({r:i,c:n})
if (worksheet[pp]){
worksheet[pp].s = assignFun(worksheet[pp].s,style.style)
}
}
}
}
console.log('colArr:',colArr)
}
}
// 3、按位置设置
let pos = style.pos
if (pos){
let posObj = decodePos(pos)
let p1 = posObj.p1
let p2 = posObj.p2
// 行
for (let i=p1.r;i<=p2.r;i++){
// 列
for (let j = p1.c; j <=p2.c ; j++) {
// 坐标转成字母ABCDEF
let pp = XLSX.utils.encode_cell({r:i,c:j})
if (worksheet[pp]){
worksheet[pp].s = assignFun(worksheet[pp].s,style.style)
}
}
}
}
}
}
function assignFun(obj1={},obj2={}){
Object.keys(obj2).forEach(key=>{
if (typeof obj2[key]==='object'){
if (!obj1[key]){
obj1[key] = Array.isArray(obj2[key]) ? [] : {}
}
obj1[key] = assignFun(obj1[key],obj2[key])
}else{
obj1[key] = obj2[key]
}
})
return obj1;
}
// 把字母解析成坐标
function decodePos(pos){
let posArr = pos.split(':')
let p1 = XLSX.utils.decode_cell(posArr[0])
let p2 = XLSX.utils.decode_cell(posArr[1])
if (p1.r>p2.r||(p1.r==p2.r&&p1.c>p2.c)){
[p1,p2] = [p2,p1]
}
return {p1,p2}
}
}
....
columnWidth:[14,14…]指每列的宽度,数组的长度等于列的个数
lineHeight:[20,20…]指每行的行高,数组的长度等于行的个数
merges:[{pos:‘A1:I1’}]指把位置A1到I1的单元格合并,可设置多组
styles:[{pos:‘A1:I6’…}]指设置位置A1:I6的样式,可设置多组
styles:[{ nthRow:‘2*n+1’…}]指设置奇数行样式
styles:[{nthCol:‘2*n’…}]指设置偶数列样式
styles:[{nthCol:‘n+7’…}]指设置第7列之后的样式
styles:[{nthRow:‘n’…}]指设置所有行的样式
修改更复杂xlsx样式参考
更多请看xlsx GitHub