前端处理excel上传的通用组件
<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">
Drop excel file here or
<el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">
Browse
</el-button>
</div>
</div>
</template>
<script>
// 这里是 xlsx 的最新导入形式
import * as XLSX from 'xlsx'
export default {
props: {
// 上传 excel 文件之前,对文件格式、大小等校验工作
beforeUpload: Function, // eslint-disable-line
// 解析 excel 成功之后的回调函数
onSuccess: Function// eslint-disable-line
},
data() {
return {
// 上传按钮的 loading
loading: false,
// 解析后的 excel 数据
excelData: {
// excel 表头数据
header: null,
// excel 表体数据
results: null
}
}
},
methods: {
// 将转换好的 excel 数据,传递给 excelData,并调用 onSuccess,将 excelData 数据传给组件的使用者。
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) {
debugger
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
debugger
},
// 模拟文件点击
handleUpload() {
this.$refs['excel-upload-input'].click()
},
// 获取用户选择的文件
handleClick(e) {
const files = e.target.files
console.log(files);
const rawFile = files[0] // only use files[0]
console.log(rawFile);
if (!rawFile) return
// 读取文件
this.upload(rawFile)
},
// 处理选择的文件
upload(rawFile) {
// 将文件读取后的 input file 的 value 设置为 null,方便下次读取
this.$refs['excel-upload-input'].value = null
// 如果用户没传上传之前的校验函数
if (!this.beforeUpload) {
// 直接读取文件数据
this.readerData(rawFile)
return
}
// 用户传递了上传之前的校验函数,将文件传入函数,进行文件格式的校验
const before = this.beforeUpload(rawFile)
// 校验通过
if (before) {
// 开始读取文件数据
this.readerData(rawFile)
}
},
// 读取 excel 文件,得到 excel 里面的数据
readerData(rawFile) {
// 开启按钮 loading
this.loading = true
//为什么要用一个 Promise 包裹呢?因为 FileReader 是异步的,我们需要等待文件读取完毕后,再进行下一步操作。
return new Promise((resolve, reject) => {
// 创建文件读取器
const reader = new FileReader()
console.log(reader);
debugger
// 监听文件读取后触发的回调函数
reader.onload = e => {
// 文件读取后的结果
const data = e.target.result
// 使用 XLSX,将 excel 文件转换为数据
// 读取 ArrayBuffer 格式的文件,并返回工作簿
const workbook = XLSX.read(data, { type: 'array' })
// 拿到第一个工作表名称
const firstSheetName = workbook.SheetNames[0]
// 读取第一个工作表
const worksheet = workbook.Sheets[firstSheetName]
// 得到 excel 表头数据
const header = this.getHeaderRow(worksheet)
// 得到 excel 表体数据
const results = XLSX.utils.sheet_to_json(worksheet)
// 调用 generateData 方法,传入表头和表体数据
this.generateData({ header, results })
// 结束按钮 loading
this.loading = false
resolve()
}
// 读取文件并转换为 ArrayBuffer 格式的数据
reader.readAsArrayBuffer(rawFile)
debugger
})
},
// 获取 excel 表头信息
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])// decode_range 将 A1:B7 转换成 {s: {c:0, r:0}, e: {c:1, r:6}}
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 // 这个是默认的表头
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)// 这个是获取每个单元格的值
headers.push(hdr)// 将每个单元格的值放入 headers 数组中
}
return headers
},
// 判断是否为 excel 文件
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>
组件中使用
<template>
<div class="excel-container">
<upload-excel :before-upload="beforeUpload" :on-success="onSuccess" />
</div>
</template>
<script>
import UploadExcel from '@/components/UploadExcel'
export default {
name: 'ImportExcel',
components: {
UploadExcel
},
methods: {
beforeUpload(file) {
// 这个函数必须 return 一个布尔值 并且只有 return 出去的是 true 的时候才会执行后续的解析 excel 的操作
// 如果 reture false,表示校验不通过,不会进行正式的解析操作
console.log('上传前', file)
// 文件大小超过 1M 不允许导入
const isLt1M = file.size / 1024 / 1024 < 1 // 小于1M
if (isLt1M) {
return true
}
this.$message({
message: '文件体积不能超过1M',
type: 'warning'
})
return false
},
onSuccess({ results, header }) {
// results:表体数据
// header:表头数据
console.log('上传完成', results, header)
}
}
}
</script>
下面我对一些属性做一些解释
new FileReader()是干嘛的?有那些方法
new FileReader()
是用于创建一个新的FileReader对象的构造函数。FileReader对象允许web应用程序异步读取存储在用户的计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
主要的方法包括:
-
readAsArrayBuffer(blob)
: 读取文件内容,结果用ArrayBuffer对象表示。参数blob是个Blob对象实例,Blob对象表示一个不可变、原始数据的类文件对象。 -
readAsText(blob, [encoding])
: 读取文件内容,结果用字符串形式表示。参数blob是个Blob对象实例,Blob对象表示一个不可变、原始数据的类文件对象。参数encoding是一个字符串,表示文件的字符编码,默认值是 'UTF-8'。 -
readAsDataURL(blob)
: 读取文件内容,结果用data:URL的形式表示。参数blob是个Blob对象实例,Blob对象表示一个不可变、原始数据的类文件对象。 -
readAsBinaryString(blob)
: 读取文件内容,当读取操作完成时,readyState变为DONE,加载的数据将在result属性中呈现为二进制字符串。参数blob是一个Blob或File对象。 -
abort()
: 该方法将终止读取操作。当调用abort方法后,result属性将返回null,但是如果已经开始读取数据,可能已经改变了部分原始Blob数据对象。
同时,FileReader对象还提供了一系列的事件处理器,包括:onload
, onerror
, onloadstart
, onprogress
, onloadend
, onabort
等,这些处理器可以用来监听读取过程中的各种事件。
ArrayBuffer对象
ArrayBuffer对象是JavaScript中使用的一个类型,它用于表示一个通用的、固定长度的原始二进制数据缓冲区。也就是说,ArrayBuffer存在的目的是允许你操作原始数据——比如图片、音频、视频的二进制数据等。
ArrayBuffer是一个低级别的接口,通常我们不会直接操作它,而是通过TypedArray对象或者DataView对象来操作它。
TypedArray对象主要包括:Int8Array、Uint8Array、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array等。每一个TypedArray 都是以ArrayBuffer为背景,然后定义了特定类型的数组视图,以便从ArrayBuffer中读写结构性的二进制数据。
DataView对象则是一个更灵活的视图,可以从ArrayBuffer的任何位置开始,按指定格式读写数据。
总的来说,ArrayBuffer对象提供了一种操作JavaScript二进制数据的方法。
上面的代码多打debugger,一般都没啥问题,注意一下
let reader = new FileReader();
reader.onload = function(e) {
let fileData = e.target.result;
// 你可以在这里处理文件数据
};
reader.readAsText(someFile);
当FileReader
对象成功的完成读取文件后,它会触发一个load
事件,此时就可以通过e.target.result
来访问读取到的文件数据。
当FileReader
读取完文件以后,文件的内容(作为一个字符串或者ArrayBuffer,取决于你使用的读取方法)就会被存储在e.target.result里面。