记录下如何在vue中使用xlsx实现前端导入导出功能。
引用js-xlsx的依赖(来源https://github.com/SheetJS/sheetjs):
npm install xlsx --save
抽提出一个公共组件:
<!-- 导入导出组件(纯前端) --> <template> <span> <input type="file" @change="importFile(this)" id="imFile" style="display:none;" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"/> <a id="downlink"></a> <span @click="downloadFile"> <slot name="export"> <el-button class="button">导出</el-button> </slot> </span> <span @click="uploadFile"> <slot name="import"> <el-button class="button">导入</el-button> </slot> </span> <!-- 错误信息提示 --> <el-dialog title="提示" v-model="errorDialog" size="tiny"> <span>{{ errorMsg }}</span> <span slot="footer" class="dialog-footer"> <el-button type="primary" @click="errorDialog=false">确认</el-button> </span> </el-dialog> </span> </template> <script> var XLSX = require('xlsx') export default { name: 'ProgExportImport', data() { return { fullscreenLoading: false, // 加载中 imFile: '', // 导入文件el对象 outFile: '', // 导出文件el对象 errorDialog: false, // 错误信息弹窗 errorMsg: '', // 错误信息内容 excelTitle: {}, // excel标题 excelData: [] // excel处理数据 } }, props: { setExportData: { type: Function, default: function() { console.warn('未传递获取excel导出数据方法') } }, getImportData: { type: Function, default: function() { console.warn('未传递设置excel导入数据方法') } } }, mounted() { this.imFile = document.getElementById('imFile') this.outFile = document.getElementById('downlink') }, methods: { uploadFile: function() { // 导入文件点击事件 this.imFile.click() }, downloadFile: function() { // 导出文件点击事件 let exportData = this.setExportData() this.excelTitle = exportData.excelTitle this.excelData = exportData.excelData let data = [{},{}] for (let k in this.excelData[0]) { // 设置第1行为数据库字段行 if (this.excelData[0].hasOwnProperty(k)) { data[0][k] = k data[1][k] = this.excelTitle[k] // 中文标题 } } data = data.concat(this.excelData) this.downloadExl(data, exportData.excelName || '导出文件') }, importFile: function() { // 导入excel this.fullscreenLoading = true let obj = this.imFile if (!obj.files) { this.fullscreenLoading = false return } var f = obj.files[0] var reader = new FileReader() let $t = this reader.onload = function(e) { var data = e.target.result if ($t.rABS) { $t.wb = XLSX.read(btoa(this.fixdata(data)), { // 手动转化 type: 'base64' }) } else { $t.wb = XLSX.read(data, { type: 'binary' }) } let json = XLSX.utils.sheet_to_json($t.wb.Sheets[$t.wb.SheetNames[0]]) $t.dealFile($t.analyzeData(json)) // analyzeData: 解析导入数据 } if (this.rABS) { reader.readAsArrayBuffer(f) } else { reader.readAsBinaryString(f) } }, downloadExl: function(json, downName, type) { // 导出到excel let keyMap = [] // 获取键 for (let k in json[0]) { if (json[0].hasOwnProperty(k)) { keyMap.push(k) } } let tmpdata = [] // 用来保存转换好的json json.map((v, i) => keyMap.map((k, j) => Object.assign({}, { v: v[k] || '', position: (j > 25 ? this.getCharCol(j) : String.fromCharCode(65 + j)) + (i + 1) }))).reduce((prev, next) => prev.concat(next)).forEach(function(v) { tmpdata[v.position] = { v: v.v } }) let outputPos = Object.keys(tmpdata) // 设置区域,比如表格从A1到D10 let tmpWB = { SheetNames: ['sheet'], // 保存的表标题 Sheets: { 'sheet': Object.assign({}, tmpdata, // 内容 { '!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1] // 设置填充区域 }) } } let tmpDown = new Blob([this.s2ab(XLSX.write(tmpWB, { bookType: (type || 'xlsx'), bookSST: false, type: 'binary' } // 这里的数据是用来定义导出的格式类型 ))], { type: '' }) // 创建二进制对象写入转换好的字节流 var href = URL.createObjectURL(tmpDown) // 创建对象超链接 this.outFile.download = downName + '.xlsx' // 下载名称 this.outFile.href = href // 绑定a标签 this.outFile.click() // 模拟点击实现下载 setTimeout(function() { // 延时释放 URL.revokeObjectURL(tmpDown) // 用URL.revokeObjectURL()来释放这个object URL }, 100) }, analyzeData: function(data) { // 此处可以解析导入数据 data.splice(0, 1) // 去除第二行(中文标题行) return data }, dealFile: function(data) { // 处理导入的数据 this.imFile.value = '' this.fullscreenLoading = false if (data.length <= 0) { this.errorDialog = true this.errorMsg = '请导入正确信息' } else { this.excelData = data this.getImportData(data) } }, s2ab: function(s) { // 字符串转字符流 var buf = new ArrayBuffer(s.length) var view = new Uint8Array(buf) for (var i = 0; i !== s.length; ++i) { view[i] = s.charCodeAt(i) & 0xFF } return buf }, getCharCol: function(n) { // 将指定的自然数转换为26进制表示。映射关系:[0-25] -> [A-Z]。 let s = '' let m = 0 while (n > 0) { m = n % 26 + 1 s = String.fromCharCode(m + 64) + s n = (n - m) / 26 } return s }, fixdata: function(data) { // 文件流转BinaryString var o = '' var l = 0 var w = 10240 for (; l < data.byteLength / w; ++l) { o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))) } o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))) return o } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style> </style>
引用组件:
import exportImport from '@/components/export-import.vue'
定义组件方法:
setExportData() { return { excelTitle: { code: '编号', name: '名称' }, excelData: [], excelName: '导出' } }, getImportData(excelData) { // .. }
使用组件:
<export-import :set-export-data="setExportData" :get-import-data="getImportData"> <template slot="export"> <el-button type="primary" plain size="mini">导出</el-button> </template> <template slot="import"> <el-button type="primary" plain size="mini">导入</el-button> </template> </export-import>
这样就实现了在前端进行导入导出的功能,但是只能实现简单的功能,如果需要定制复杂的excel,比如定制单元格样式,设置列类型(文本、日期和数值等)这些样的高级功能就无能为力了。
抑或者是数据量很大的话也可能会导致前端浏览器卡死而导出失败。
建议还是通过后台使用poi的方式进行导入导出功能的实现,提供强大的api与丰富的应用记录(出问题可以问度娘或古哥哥),稳妥可控。
"有时关不上冰箱的门,脚趾撞到了桌脚,临出门找不到想要的东西,就会突然忍不住掉眼泪。你可能会觉得小题大作,无法理解,但是只有我自己知道为什么。"