記錄下如何在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與豐富的應用記錄(出問題可以問度娘或古哥哥),穩妥可控。
"有時關不上冰箱的門,腳趾撞到了桌腳,臨出門找不到想要的東西,就會突然忍不住掉眼淚。你可能會覺得小題大作,無法理解,但是只有我自己知道為什么。"