Vue项目中excel表格文件的导入、导出
作者:互联网
目录
使用到的一些构造函数、插件
FileReader
链接:https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader/FileReader
Blob
链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
Blob
对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成ReadableStream
来用于数据操作。想要读取Blob数据的唯一方法是FileReader。
FileReader
对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File
或Blob
对象指定要读取的文件或数据。
js-xlsx
链接:https://github.com/SheetJS/sheetjs/tree/master/demos/vue
由SheetJS出品的js-xlsx是一款非常方便的只需要纯JS即可读取和导出excel的工具库,功能强大,支持格式众多,支持xls、xlsx、ods(一种OpenOffice专有表格文件格式)等十几种格式。
npm install xlsx // 安装
import XLSX from 'xlsx'; // 导入
ElementUI组件库
链接:https://element.eleme.cn/#/zh-CN/component/installation
导入文件的弹窗使用到了el-dialog组件
导入的excel文件预览
导入文件不带自定义弹窗
<template>
<div id='File'>
<button class="file-btn" @click="handleUpBtn">
<span>导入</span>
<input type="file" ref="refFile" style="display: none" @change="handleFiles" >
</button>
<button class="file-btn" @click="handleDownBtn"><span>导出</span></button>
<div class="container">
<table class="main">
<thead class="mainH">
<tr>
<td v-for="(item, index) in fileHeaders" :key="index">{{item}}</td>
</tr>
</thead>
<tbody class="mainB">
<tr v-for="(item, index) in fileData" :key="index+'1'">
<td v-for="(i, index) in item" :key="index+'2'">{{i}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import XLSX from 'xlsx';
export default {
name:'File',
data(){
return {
file: "",
fileName: "",
fileData: [],
fileHeaders: []
}
},
methods: {
// 上传文件
handleUpBtn(){
this.$refs.refFile.click(); // 触发文件按钮点击事件
},
// 在文件按钮发生变化后处理上传的文件数据
handleFiles(){
let selectedFile = this.$refs.refFile.files[0];
// selectedFile中可以获取到文件名和文件大小 selectedFile.name, selectedFile.size
this.file = selectedFile;
this.fileName = selectedFile.name;
/*
在得到selectedFile文件之后,可以直接将selectedFile文件通过接口传递到后端,进行文件内容的处理
如果文件需要在前端进行处理,就需要借助FileReader读取文件内容
在前端可以进行一些简单的文件处理
比如判断文件的大小是否超出规定内容
判断文件的格式是否符合要求等
*/
// 下面的操作对当前的文件进行读取、获取文件内容,可以借助XLSX将excel文件内容转成json格式的数据
let reader = new FileReader();
reader.readAsBinaryString(selectedFile);
/*
FileReader共有4种读取方法:
1.readAsArrayBuffer(file):将文件读取为ArrayBuffer。
2.readAsBinaryString(file):将文件读取为二进制字符串
3.readAsDataURL(file):将文件读取为Data URL
4.readAsText(file, [encoding]):将文件读取为文本,encoding缺省值为'UTF-8'
*/
reader.onload = (evt) => {
try {
let data = evt.target.result;
let workbook = XLSX.read(data, { type: "binary" });
// 以二进制流方式读取得到整份excel表格对象 type: 'base64'
let wsname = workbook.SheetNames[0]; // 取第一张表 如果有多张数据表 可以使用循环遍历
let sheet = workbook.Sheets[wsname];
let jsonData = XLSX.utils.sheet_to_json(sheet); // 生成json表格内容
jsonData.map( item => { // 保存json数据
this.fileData.push(item);
})
// 提取出表头还可以通过fileData提取,也可以使用下面的方式提取
// 保存表头数据
let range = XLSX.utils.decode_range(sheet["!ref"]);
/*
sheet['!ref']表示所有单元格的范围,例如从A1到F8则记录为 A1:F8
console.log(range);
控制台打印range 返回 {s:{c:9, r:10}, e:{c:0, r:0}}
表示表格内容最后一个单元格(10,9) 第一个单元格(0,0)
*/
let col = range.s.c, row = range.s.r; // 10列 11行
for (col = range.s.c; col <= range.e.c; ++col) {
// 查找第一行中的单元格
let cell = sheet[XLSX.utils.encode_cell({ c: col, r: row })];
let cellH = cell ? XLSX.utils.format_cell(cell) : "无";
// 表单元格标题不存在则使用"无"代替
this.fileHeaders.push(cellH);
}
this.$message.success("导入成功!");
} catch(err) {
console.error("出错了!", err);
return;
}
};
},
// 导出文件
handleDownBtn(){
/*
在点击导出按钮的时候,在这里请求后台接口,后台可能返回的文件数据是文件流数据,需要使用Blob进行解析
axios({
url: 'http://localhost:3001/saveImg',
method: 'get',
responseType: 'blob',
// 可以传递一些params
}).then( res => {
// 数据处理 res中包含了blob流文件
})
*/
// 使用blob解析流文件
let blob = new Blob([this.file],{type: "application/vnd.ms-excel;charset=utf-8"});
/*
一般在请求后台返回文件的接口中传入了responseType: 'blob',就不用再new Blob了,直接使用返回的blob流文件
生成blob地址,下载文件即可
new Blob([], {type: xxx}) 中的type有以下几种设置
type:"application/vnd.ms-excel"
type:"application/octet-stream;charset=utf-8"
type: "application/vnd.ms-excel;charset=utf-8"
type:"application/json"
type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"
type:"text/plain"
type:"image/jpeg"
type:"application/msword"
type:"application/pdf"
*/
let objectUrl = URL.createObjectURL(blob); // 将blob对象转为blob地址
/*
使用a连接触发下载文件
download为下载的文件重新指定一个文件名,href为链接的文件地址,即为url
download属性很重要,它可以指定下载文件名,并且可以告诉浏览器目标链接是一个下载链接,不是一个普通链接
download也可以不给值,这样就会使用默认的文件名
*/
let a = document.createElement('a');
a.download = this.fileName;
a.href = objectUrl;
document.body.appendChild(a);
a.click();
// 释放内存
URL.revokeObjectURL(objectUrl);
document.body.removeChild(a);
this.$message.success("导出成功!");
/*
使用a标签模拟js触发单击事件 a.click() 有兼容性问题,使用以下方法解决兼容性问题
let a = document.createElement('a');
a.href = url;
a.download = saveName || '';
// HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
let event;
if(window.MouseEvent) event = new MouseEvent('click');
else {
event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
}
a.dispatchEvent(event);
*/
},
}
}
</script>
<style scoped>
#File {
margin: 100px 50px;
background-color: #f1f2f0;
}
.file-btn {
margin: 10px;
padding: 5px 10px;
border: 0;
color: #ffffff;
background-color: #4bcffa;
}
.file-btn:hover,
.select-file:hover {
cursor: pointer;
}
.container {
height: 300px;
background-color: #f5f6fa;
overflow: auto;
padding: 10px;
}
.container .main {
width: 100%;
height: 100%;
text-align: center;
}
</style>
导入文件之后的fileData数据(控制台打印)
保存表头数据中的range数据(控制台打印)
最终展示效果
导出文件
点击导出按钮,借助a链接下载文件
导入文件带自定义弹窗
<template>
<div id='File'>
<button class="file-btn" @click="dialogVisible=true">导入</button>
<button class="file-btn" @click="handleDownBtn">导出</button>
<!-- 展示数据 -->
<div class="container">
<table class="main" ref="table">
<thead class="mainH">
<tr>
<td v-for="(item, index) in fileHeaders" :key="index">{{item}}</td>
</tr>
</thead>
<tbody class="mainB">
<tr v-for="itemObj in fileData" :key="itemObj.id">
<td v-for="(item, index) in itemObj.data" :key="index">{{item}}</td>
</tr>
</tbody>
</table>
</div>
<!-- 上传文件弹出框(elementUI组件) -->
<el-dialog title="选择要上传的文件" :visible.sync="dialogVisible" width="50%" @close="dialogVisible=false" >
<el-button type="primary" @click="handleUpBtn">上传文件</el-button>
<input type="file" ref="refFile" style="display: none" @change="handleFiles" >
<!-- 这里显示选中的文件 -->
<div class="fileName"><span>{{ fileName }}</span></div>
<span slot="footer">
<el-button @click="dialogVisible=false">取 消</el-button>
<el-button type="primary" @click="sureUpFile">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import XLSX from 'xlsx';
import { nanoid } from "nanoid";
export default {
name:'File',
data(){
return {
selectedFile: "", // 导入的文件
fileName: "", // 导入的文件名
fileData: [], // 导入的文件转成json格式的数据
fileHeaders: [], // 导入的文件表格标题
dialogVisible: false, // 控制弹窗显示和隐藏
canUpFile: false, // 控制文件是否可以上传
}
},
methods: {
// 上传文件
handleUpBtn(){
this.$refs.refFile.click(); // 触发文件按钮点击事件
},
// 在文件按钮发生变化后处理上传的文件数据
handleFiles(){
/*
在选择好文件之后,可以在这里对文件的格式,或者大小等作出判断
比如导入的文件格式是否正确,文件的大小是否符合要求,
*/
let selectedFile = this.$refs.refFile.files[0];
// 没有导入文件或者导入文件的格式出错
if(!selectedFile || !/\.(xls|xlsx)$/.test(selectedFile.name.toLowerCase())) {
this.$message.warning("请导入xls或xlsx格式的文件!");
this.canUpFile = false;
this.fileName = "";
return;
}
this.canUpFile = true;
this.selectedFile = selectedFile;
this.fileName = selectedFile.name;
},
// 弹窗中确定上传文件
sureUpFile(){ // 将文件进行保存提交
/*
开发过程中
一般在点击"确定"按钮的这个时候将selectedFile文件通过接口传递给后台
根据后台返回的内容作出判断,判断文件导入成功还是失败
下面只是普通的将文件数据获取出来并转成json格式的数据,然后显示在页面上
(真实的开发顺序是:
前端将文件通过接口传递到后台
如果文件导入成功,再进行一次接口请求,请求后台最新的数据,并将最新的数据渲染到页面)
*/
if(!this.canUpFile) {
this.$message.warning("请导入文件!");
this.fileName = "";
return;
}
this.dialogVisible = false; // 关闭弹窗
let reader = new FileReader();
reader.readAsBinaryString(this.selectedFile);
reader.onload = (evt) => {
try {
let data = evt.target.result;
let workbook = XLSX.read(data, { type: "binary" });
let wsname = workbook.SheetNames[0];
let sheet = workbook.Sheets[wsname];
let jsonData = XLSX.utils.sheet_to_json(sheet);
if(jsonData.length == 0) {
this.$message.warning("不能导入空文件!");
this.fileName = "";
return;
}
this.fileData = [];
jsonData.map( item => {
let itemObj = {};
itemObj.id = nanoid();
itemObj.data = item;
this.fileData.push(itemObj);
})
console.log(this.fileData);
// 保存文件表格标题
let range = XLSX.utils.decode_range(sheet["!ref"]);
let col = range.s.c, row = range.s.r;
for (col = range.s.c; col <= range.e.c; ++col) {
let cell = sheet[XLSX.utils.encode_cell({ c: col, r: row })];
let cellH = cell ? XLSX.utils.format_cell(cell) : "无";
this.fileHeaders.push(cellH);
}
this.$message.success("导入成功!");
} catch(err) {
this.$message.error("导入失败!");
console.error("导入失败!", err);
return;
}
};
},
// 导出文件
handleDownBtn(){
let blob = new Blob([this.selectedFile],{type: "application/vnd.ms-excel;charset=utf-8"});
let URLURL = window.URL || window.webkitURL || window
let objectUrl = URLURL.createObjectURL(blob);
let a = document.createElement('a');
a.download = this.fileName;
a.href = objectUrl;
a.style.display = "none";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(objectUrl);
document.body.removeChild(a);
this.$message.success("导出成功!");
},
}
}
</script>
<style scoped>
#File {
margin: 100px 50px;
background-color: #f1f2f0;
}
.file-btn {
margin: 10px;
padding: 5px 10px;
border: 0;
color: #ffffff;
background-color: #4bcffa;
}
.file-btn:hover,
.select-file:hover {
cursor: pointer;
}
.container {
height: 300px;
background-color: #f5f6fa;
overflow: auto;
padding: 10px;
}
.container .main {
width: 100%;
height: 100%;
text-align: center;
}
.fileName {
width: 100%;
height: 40px;
line-height: 40px;
}
</style>
自定义导入弹窗
点击导入文件,先弹出弹窗,再借助<input type="file">选择上传的文件
标签:文件,Vue,selectedFile,excel,导入,let,file,type 来源: https://blog.csdn.net/yun_huangZJ/article/details/120126343