1.xlsx簡介
通俗的說,xlsx這個插件可以把html中的table元素或者json數據轉換成表格后進行導出
<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.4/xlsx.js"></script>
2.基本概念
一個excel文件就是一個book,一個book中又包含一個或多個sheet,以下方法均由XLSX.utils對象調用
常用方法 | 參數 | 說明 |
---|---|---|
book_new | - | 創建一個空的book |
table_to_book | table元素(dom) | 將table元素轉換成 book |
table_to_sheet | table元素(dom) | 將table元素轉換成 sheet |
json_to_sheet | json數據(數組) | 將json數據轉換成 sheet |
book_append_sheet | 參數1:book對象,參數2:sheet對象,參數3:sheet名稱 | 將sheet添加到book中 |
writeFile | 參數1:book對象,參數2:導出的文件名稱 | 導出表格文件 |
3.JSON轉表格
- 基本代碼
exportEvent () {
// 要打印的數據格式 對象中的key將會作為表頭渲染
var print_data = [
{'序號':1,'姓名':'張三','年齡':20},
{'序號':2,'姓名':'李四','年齡':25},
{'序號':3,'姓名':'王五','年齡':30}
]
// 創建一個新sheet
const new_sheet = XLSX.utils.json_to_sheet(print_data)
// 設置每列的列寬(可選),10代表10個字符,注意中文占2個字符
new_sheet['!cols'] = [
{ wch: 10 },
{ wch: 30 },
{ wch: 25 }
]
// 新建book
const new_book = XLSX.utils.book_new()
// 將 sheet 添加到 book 中
XLSX.utils.book_append_sheet(new_book, new_sheet, '人員名單')
// 導出excel文件
XLSX.writeFile(new_book, '數據導出.xlsx')
}
- 表頭標題替換方法
replaceKeyForTableData(tableData = [],mapData = {}){
//要返回的最終數據
var result = []
//循環數組的每行數據
tableData.forEach(row=>{
//新的行數據
var new_row = {}
//循環每個鍵值
Object.keys(row).forEach(key=>{
//判斷這個可以是否 可以在映色中找到對應的值 有則使用,無則使用原來的key
var new_key = mapData[key]? mapData[key]:key
//為新的行數據添加鍵值對
new_row[new_key] = row[key]
})
//新的行添加到表格數據中
result.push(new_row)
})
return result
}
4.HTML-table轉表格
- 導出原生表格:將table元素傳入即可導出
//獲取dom元素
var table_dom = document.querySelector('table')
//將dom轉換為book
const new_book = XLSX.utils.table_to_book(table_dom)
// 導出excel文件 如導出后的文件不能打開,請將后綴替換為 .xls
XLSX.writeFile(new_book, '數據導出.xlsx')
- 導出插件生成的表格:插件生成的表格因為兼容一些功能,最終渲染的時候其實是2個table元素,為了能將表格完整的導出(thead+body),需要手動拼接dom
//這里以vxe-table為例
exportEvent () {
// 創建新的table元素
var print_table_dom = document.createElement('table')
// copy一份thead
var print_table_dom_thead = this.$refs.p_table.$el.querySelector('.vxe-table--header-wrapper > .vxe-table--header thead').cloneNode(true)
// copy一份tbody
var print_table_body = this.$refs.p_table.$el.querySelector('.vxe-table--body-wrapper > .vxe-table--body tbody').cloneNode(true)
// 將thead和tbody添加到 目標table中
print_table_dom.appendChild(print_table_dom_thead)
print_table_dom.appendChild(print_table_body)
// 生成 book
const new_sheet = XLSX.utils.table_to_book(print_table_dom)
// 導出excel
XLSX.writeFile(new_sheet, '數據導出.xlsx')
},
- 數字格式的問題:excel時,以0開頭的數據,其開頭0被忽略,如001234導出到excel后變為了1234,只需要將dom轉換為book的時候,傳入第二個參數即可
//將dom轉換為book,傳入第二個參數{raw:true}
const new_book = XLSX.utils.table_to_book(table_dom,{raw:true})
5.jsonToxlsx封裝
- 1.表格轉換過程中,表頭可能需要替換
- 2.表格列寬動態設定
- 3.20230906升級:導出表格列的順序已表頭信息配置為准,自動過濾表頭沒有的列(表頭信息配置為空時,使用原邏輯)
- 4.代碼已上傳至gitee
//此插件依賴xlsl.js
//@author zhoulianli 2022-04-15
//此插件主要用來導出表格數據,在xlsl.js基礎上封裝了列寬自適應,表頭標題替換功能
//使用方法 jsonToxlsx(參數)
//參數1: 表格數據 [{},{}]
//參數2:可選,表頭標題映射數據 {name:"姓名",age:"年齡"}
//參數3:可選,文件名 例如:"數據導出"
//參數4:可選,sheet名 例如:"能耗比數據"
//閉包 傳入當前環境的this
(function (global, factory) {
//1.先判斷當前環境是否支持CommonJS規范(node.js)
if (typeof exports == 'object' && typeof module !== 'undefined') {
//console.log('CommonJS規范')
module.exports = factory()
} else if (typeof define == 'function' && define.amd) {//2.再判斷是否支持AMD規范(require.js)
//console.log('AMD規范')
define(factory)
} else {
//console.log('script標簽引入')
//接收該對象
this.jsonToxlsx = factory()
}
}(this,function(){
//將表格數組導出的方法
function jsonToxlsx(tableData = [],tHeadMap = {},fileName = "數據導出",sheetName = "數據導出"){
//判斷依賴是否存在
if(typeof XLSX == undefined){
console.log('請先引入XLSX.js')
return
}
//判斷是否有表頭信息
if(tHeadMap && Object.keys(tHeadMap).length > 0){
//轉換數據格式2
var print_data = transFormTableData2(tableData,tHeadMap)
}else{
//轉換數據格式1
var print_data = transFormTableData(tableData,tHeadMap)
}
// 創建一個新sheet
const new_sheet = XLSX.utils.json_to_sheet(print_data)
// 根據每列數據智能設定列寬
var two_dimensional_arr = transformToTwoDimensional(print_data)
var width_arr = two_dimensional_arr.map(arr=>{
return {
wch: getMaxLenByArr(arr) + 2
}
})
new_sheet['!cols'] = width_arr
// 新建book
const new_book = XLSX.utils.book_new()
// 將 sheet 添加到 book 中
XLSX.utils.book_append_sheet(new_book, new_sheet, sheetName)
// 導出excel文件
XLSX.writeFile(new_book, `${fileName}${Moment().format('YYYYMMDDHHmmss')}.xlsx`)
}
//轉換表格數據
function transFormTableData(tableData = [],mapData = {}){
//要返回的最終數據
var result = []
//循環數組的每行數據
tableData.forEach(row=>{
//新的行數據
var new_row = {}
//循環每個鍵值
Object.keys(row).forEach(key=>{
//判斷這個可以是否 可以在映色中找到對應的值 有則使用,無則使用原來的key
var new_key = mapData[key]? mapData[key]:key
//為新的行數據添加鍵值對
new_row[new_key] = row[key]
})
//新的行添加到表格數據中
result.push(new_row)
})
return result
}
//轉換表格數據2
//表格行的屬性順序和數量由表頭配置來決定
function transFormTableData2(tableData = [],mapData = {}){
//要返回的最終數據
var result = []
//循環數組的每行數據
tableData.forEach(row=>{
//新的行數據
var new_row = {}
//循環表頭每個鍵值
for(var key in mapData){
var target_key = mapData[key]
//給表格行添加字段
new_row[target_key] = row[key]
}
//新的行添加到表格數據中
result.push(new_row)
})
return result
}
//將表格數組轉換為二維數組 [[col1-1,col1-2],[col2-1,col2-2]]
function transformToTwoDimensional(tableData){
//如果數據源為空 則返回空
if (tableData == 0) {
return []
}
//對象有多少個key,就有多少列,每列數據是一個數組
//先填充標題
var result = []
for (key in tableData[0]) {
result.push([key])
}
//接着填充數據
tableData.forEach(row => {
var index = 0
for (key in row) {
result[index].push(row[key])
index++
}
})
return result
}
//從一維數組中提取出 最大的字節占位
function getMaxLenByArr(strArr){
var max = 0
strArr.forEach(item => {
//轉為字符串
var str = item + ''
var strlen = 0;
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255) //如果是漢字,則字符串長度加2
strlen += 2;
else
strlen++;
}
if( strlen > max){
max = strlen
}
})
return max
}
return jsonToxlsx;
}))
6.表格的導入
表格的導入一般經過幾個步驟:
1.文件讀取:借助文件域+H5的FileReader對文件進行讀取
2.一次轉碼:使用XLSX對FileReader讀取到的數據轉換成bookData
3.二次轉碼:再借助vxe-table中的方法將上個步驟的數據轉換成csvData
4.三次轉碼:將csvData轉換為json數據
值得說明的是,前面三個步驟調用的都是瀏覽器或者插件現成的接口,而且也不會涉及業務層面的邏輯,所以我們不去動它。而第四步將csvData轉換為json數據,目標json的數據格式需要根據業務來決定,所以第四步就值得探討一番
我先說結論,csvData轉換為json數據只能通過下標進行匹配,這也就意味着excel文件的列需要一個固定的格式,列的順序一旦出錯,導入就會有問題
這是excel表格數據:
這是轉換后的csvData:
以下代碼作為參考:
data: {
tableColumn:[
{field:'id',title:'id'},
{field:'name',title:'姓名'},
{field:'role',title:'角色'},
{field:'sex',title:'性別'},
{field:'age',title:'年齡'},
{field:'address',title:'地址'}
],
tableData: [
{ id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' },
{ id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
{ id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' },
{ id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 24, address: 'Shanghai' }
]
},
methods:{
//選擇文件
fileChange(){
var file = document.querySelector('#file').files[0]
//如果文件存在 則讀取
if(file){
//創建reader對象
var reader = new FileReader()
//讀取選擇的文件
reader.readAsBinaryString(file)
//監聽讀取事件
var that = this
reader.onload = function(ev){
const data = ev.target.result
//轉換book對象
const workbook = XLSX.read(data, { type: 'binary' })
//將某個sheet轉換成csvData 注意 sheet 的名稱
const csvData = XLSX.utils.sheet_to_csv(workbook.Sheets['Sheet1'])
that.tableData = that.csvToJson(csvData,that.tableColumn)
}
}
},
//將csv數據轉換為json數據
csvToJson(csvData,tableColumn=[]){
//1.一般情況下,表格數據與tableColumn根據下標來進行匹配
//1.如果導入的表格有表頭,則與表頭與tableColumn進行匹配
const tableData = []
// 解析數據
csvData.split('\n').forEach((vRow) => {
//遍歷行
if (vRow) {
const vCols = vRow.split(',')
const item = {}
//遍歷列
vCols.forEach((val, cIndex) => {
//根據列的下標找到對應的 field
const column = tableColumn[cIndex]
if (column.field) {
//添加字段
item[column.field] = val
}
})
tableData.push(item)
}
})
return tableData
}
}