1.準(zhǔn)備
excel導(dǎo)入功能需要使用npm包xlsx,所以需要安裝xlsx插件,讀取和寫入都依賴她
$ npm i xlsx@0.17.0
vue-element-admin模板提供了一個(gè)導(dǎo)入excel數(shù)據(jù)的文件,我們只需用即可
代碼地址:https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/UploadExcel/index.vue
將vue-element-admin提供的導(dǎo)入功能新建一個(gè)組件,位置:src/components/UploadExcel/index.vue
<template>
<div>
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
將文件拖到此處
<el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">
點(diǎn)擊上傳文件
</el-button>
</div>
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
props: {
beforeUpload: Function, // eslint-disable-line
onSuccess: Function// eslint-disable-line
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData)
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error('Only support uploading one file!')
return
}
const rawFile = files[0] // only use files[0]
if (!this.isExcel(rawFile)) {
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleDragover(e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<style scoped>
.excel-upload-input{
display: none;
z-index: -9999;
}
.drop{
border: 2px dashed #bbb;
width: 600px;
height: 160px;
line-height: 160px;
margin: 0 auto;
font-size: 24px;
border-radius: 5px;
text-align: center;
color: #bbb;
position: relative;
}
</style>
我們?cè)谛枰獙?dǎo)入和導(dǎo)出excel的index頁(yè)面引入此組件
分析一下上面的代碼,需要我們傳入onSuccess函數(shù)

2.實(shí)現(xiàn)excel導(dǎo)入
首先我們封裝一個(gè)向后端請(qǐng)求 數(shù)據(jù)的接口
/** *
* 封裝一個(gè)導(dǎo)入員工的接口,data是數(shù)組
*
* ***/
export function importEmployee(data) {
return request({
url: '/sys/user/batch',
method: 'post',
data
})
}
我們傳入onSuccess函數(shù)
<template>
<!-- 公共導(dǎo)入組件 -->
<upload-excel :on-success="success" />
</template>
import { importEmployee } from '@/api/employees'
// 在methods中修改success函數(shù)
async success({ header, results }) {
console.log(results)
//results就是我們通過excel導(dǎo)入進(jìn)來的數(shù)據(jù),之后需要把他傳遞給后端
}
從excel導(dǎo)進(jìn)來的數(shù)據(jù)是數(shù)組,里面一堆員工信息對(duì)象,key值是中文,需要轉(zhuǎn)化成英文導(dǎo)入

import { importEmployee } from '@/api/employees'
// 在methods中修改success函數(shù)
async success({ header, results }) {
// 如果是導(dǎo)入員工
//header中的數(shù)據(jù)是中文,results中的數(shù)據(jù)也是中文,要和新增的員工的屬性是一致的
//定義一個(gè)映射關(guān)系對(duì)象
const userRelations = {
'入職日期': 'timeOfEntry',
'手機(jī)號(hào)': 'mobile',
'姓名': 'username',
'轉(zhuǎn)正日期': 'correctionTime',
'工號(hào)': 'workNumber'
}
// map() 方法返回一個(gè)新數(shù)組,數(shù)組中的元素為原始數(shù)組元素調(diào)用函數(shù)處理后的值
var newArr = results.map(item => {
var userInfo = {}
Object.keys(item).forEach(key => {
userInfo[userRelations[key]] = item[key]// 相當(dāng)于 userInfo[mobile]=mobile這種
})
return userInfo
})
await importEmployee(newArr) // 調(diào)用導(dǎo)入接口,newArr就是轉(zhuǎn)化好的數(shù)據(jù),向后端發(fā)起請(qǐng)求
this.$message.success('導(dǎo)入excel成功')
}
但是此時(shí) 時(shí)間格式不對(duì),當(dāng)excel中有日期格式的時(shí)候,實(shí)際轉(zhuǎn)化的值為一個(gè)數(shù)字,我們需要一個(gè)方法進(jìn)行轉(zhuǎn)化
excel時(shí)間轉(zhuǎn)換
formatDate(numb, format) {
const time = new Date((numb - 1) * 24 * 3600000 + 1)
time.setYear(time.getFullYear() - 70)
const year = time.getFullYear() + ''
const month = time.getMonth() + 1 + ''
const date = time.getDate() - 1 + ''
if (format && format.length === 1) {
return year + format + month + format + date
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}
async success({ header, results }) {
// console.log(header, results)
// results里是 [{…}, {…}, {…},],每一個(gè)對(duì)象里和userRelations一樣
// 自定義一個(gè)映射關(guān)系
const userRelations = {
'入職日期': 'timeOfEntry',
'手機(jī)號(hào)': 'mobile',
'姓名': 'username',
'轉(zhuǎn)正日期': 'correctionTime',
'工號(hào)': 'workNumber'
}
// 把中文的key變成英文
var newArr = results.map(item => {
var userInfo = {}
// Object.keys(item)得到 ['手機(jī)號(hào)', '姓名', '入職日期', '轉(zhuǎn)正日期', '工號(hào)']
Object.keys(item).forEach(key => {
if (userRelations[key] === 'timeOfEntry' || userRelations[key] === 'correctionTime') {
console.log(item[key])// 44937
console.log(this.formatDate(item[key]))// 20230210
console.log(new Date(this.formatDate(item[key], '/')))// Tue Feb 21 2023 00:00:00 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間)
userInfo[userRelations[key]] = new Date(this.formatDate(item[key], '/'))
} else {
userInfo[userRelations[key]] = item[key]
}
})
return userInfo
})
// 請(qǐng)求接口
await importEmployee(newArr)
this.$message.success('導(dǎo)入excel成功')
},
// 格式化excel時(shí)間
formatDate(numb, format) {
const time = new Date((numb - 1) * 24 * 3600000 + 1)
time.setYear(time.getFullYear() - 70)
const year = time.getFullYear() + ''
const month = time.getMonth() + 1 + ''
const date = time.getDate() - 1 + ''
if (format && format.length === 1) {
return year + format + month + format + date
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}
3.實(shí)現(xiàn)excel導(dǎo)出
Excel 的導(dǎo)入導(dǎo)出都是依賴于js-xlsx來實(shí)現(xiàn)的。
由于 Export2Excel不僅依賴js-xlsx還依賴file-saver和script-loader。
在 js-xlsx的基礎(chǔ)上又封裝了Export2Excel.js來方便導(dǎo)出數(shù)據(jù)。
npm install xlsx@0.17.0 file-saver -S
npm install script-loader -S -D //懶加載,運(yùn)行和開發(fā)時(shí)都用到
由于js-xlsx體積還是很大的,導(dǎo)出功能也不是一個(gè)非常常用的功能,所以使用的時(shí)候建議使用懶加載。使用方法如下:
/*懶加載script-loader的作用,等點(diǎn)擊導(dǎo)出按鈕時(shí)候才會(huì)引入這個(gè)包然后才會(huì)執(zhí)行導(dǎo)出的方法
import('@/vendor/Export2Excel').then ,意思是先引入包,包引入成功后執(zhí)行then后得操作,then的參數(shù)excel就是包導(dǎo)出的內(nèi)容,內(nèi)容里有excel.export_json_to_excel方法里的東西
*/
import('@/vendor/Export2Excel').then(excel => {
excel.export_json_to_excel({
header: tHeader, //表頭 必填
data, //具體數(shù)據(jù) 必填 [[],[]]
filename: 'excel-list', //非必填
autoWidth: true, //非必填
bookType: 'xlsx' //非必填
})
})
參數(shù) |
說明 |
類型 |
可選值 |
默認(rèn)值 |
header |
導(dǎo)出數(shù)據(jù)的表頭 |
Array |
/ |
[] |
data |
導(dǎo)出的具體數(shù)據(jù) |
Array |
/ |
[[],[]...] |
filename |
導(dǎo)出文件名 |
String |
/ |
excel-list |
autoWidth |
單元格是否要自適應(yīng)寬度 |
Boolean |
true / false |
true |
bookType |
導(dǎo)出文件類型 |
String |
xlsx, csv, txt, more |
xlsx |
我們現(xiàn)在的數(shù)據(jù)是這種格式

需要變成這種格式才能導(dǎo)出,他沒有key,只有value

我們最重要的一件事,就是把表頭和數(shù)據(jù)進(jìn)行相應(yīng)的對(duì)應(yīng)
因?yàn)閿?shù)據(jù)中的key是英文,想要導(dǎo)出的表頭是中文的話,需要將中文和英文做對(duì)應(yīng)
// 導(dǎo)出excel數(shù)據(jù)
exportData() {
// 做操作
// 表頭對(duì)應(yīng)關(guān)系
const headers = {
'姓名': 'username',
'手機(jī)號(hào)': 'mobile',
'入職日期': 'timeOfEntry',
'聘用形式': 'formOfEmployment',
'轉(zhuǎn)正日期': 'correctionTime',
'工號(hào)': 'workNumber',
'部門': 'departmentName'
}
// 懶加載
import('@/vendor/Export2Excel').then(async excel => {
// page: this.page.total 的目的是要一次性把全部的員工列表查詢出來
//獲取所有員工數(shù)據(jù)的接口
const { rows } = await getEmployeeList({ page: 1, size: this.page.total })
// [["張三", "13811111111","2018","1", "2018", "10002"],["李思", "13911111111","2018","1", "2018", "10002"],...]
const data = this.formatList(headers, rows)
excel.export_json_to_excel({
header: Object.keys(headers),//表頭
data,//把轉(zhuǎn)化好格式的data放這里,data是[[],[]..]格式
filename: '員工信息表',
autoWidth: true,
bookType: 'xlsx'
})
})
},
// 該方法負(fù)責(zé)將數(shù)組轉(zhuǎn)化成二維數(shù)組
formatList(headers, rows) {
// 首先遍歷數(shù)組
// [{ username: '張三', mobile: '13811111111', ...},{},{}] => [[’張三', '13811111111',...],[],[]]
return rows.map(item => {
// Object.keys(headers) => ['姓名','手機(jī)號(hào)',...]
return Object.keys(headers).map(key => {
return item[headers[key]] // 張三, 13811111111, 2018, ...
}) // => ["張三", "13811111111","2018","1", "2018", "10002"]
}) // [["張三", "13811111111","2018","1", "2018", "10002"],["李四", "13911111111","2018","1", "2018", "10002"]]
}
此時(shí)導(dǎo)出來的數(shù)據(jù)就是[[],[]..]數(shù)組包數(shù)組的格式

但是發(fā)現(xiàn)日期和聘用形式不對(duì),下面還需要格式化日期
function formatDate(date, fmt = 'yyyy-MM-dd') {
if (!(date instanceof Array)) {
date = new Date(date)
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
}
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
}
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
const str = o[k] + ''
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))
}
}
return fmt
}
聘用形式需要改成文字形式,這個(gè)文件在src\api\constant\employees.js下,需要導(dǎo)入

1對(duì)應(yīng)正式,2對(duì)應(yīng)非正式
import EmployeeEnum from '@/api/constant/employees' // 員工信息的枚舉
formatList(headers, rows) {
// console.log(rows)// [{},{},{}]
// 需要把 [{ username: '張三', mobile: '13811111111', ...},{},{}] => [[’張三', '13811111111',...],[],[]]
// 需要把每個(gè)對(duì)象里的value取出來組成數(shù)組,所以需要把每個(gè)對(duì)象里的key找到
return rows.map(item => {
// item是每個(gè)員工信息的對(duì)象
return Object.keys(headers).map(key => { // key是手機(jī)號(hào),姓名..
if (headers[key] === 'timeOfEntry' || headers[key] === 'correctionTime') {
return formatDate(item[headers[key]])
} else if (headers[key] === 'formOfEmployment') {
const isRepeat = EmployeeEnum.hireType.find(obj => {
// 查找數(shù)組中符合條件的第一個(gè)元素
return obj.id === item[headers[key]]
})
return isRepeat ? isRepeat.value : '未知'
}
// headers[key]是mobile,username
return item[headers[key]]
})
})
}
復(fù)雜表頭的導(dǎo)出(拓展)
需要添加兩個(gè)額外的參數(shù)
參數(shù) |
說明 |
類型 |
可選值 |
默認(rèn)值 |
multiHeader |
復(fù)雜表頭的部分 |
Array |
/ |
[[]] |
merges |
需要合并的部分 |
Array |
/ |
[] |
multiHeader里面是一個(gè)二維數(shù)組,里面的一個(gè)元素是一行表頭,假設(shè)你想得到一個(gè)如圖的結(jié)構(gòu)


const multiHeader = [['姓名', '主要信息', '', '', '', '', '部門']]
const header = ['姓名', '手機(jī)號(hào)', '入職日期', '聘用形式', '轉(zhuǎn)正日期', '工號(hào)', '部門']

multiHeader中的一行表頭中的字段的個(gè)數(shù)需要和真正的列數(shù)相等,假設(shè)想要跨列,多余的空間需要定義成空串
它主要對(duì)應(yīng)的是標(biāo)準(zhǔn)的表頭
如果,我們要實(shí)現(xiàn)其合并的效果, 需要設(shè)定merges選項(xiàng)文章來源:http://www.zghlxwxcb.cn/news/detail-483753.html
const merges = ['A1:A2', 'B1:F1', 'G1:G2']
merges的順序是沒關(guān)系的,只要配置這兩個(gè)屬性,就可以導(dǎo)出復(fù)雜表頭的excel了文章來源地址http://www.zghlxwxcb.cn/news/detail-483753.html

到了這里,關(guān)于【前端】批量導(dǎo)入和導(dǎo)出Excel數(shù)據(jù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!