vue中使用js-xlsx實現前端導入導出功能


記錄下如何在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與豐富的應用記錄(出問題可以問度娘或古哥哥),穩妥可控。

 

"有時關不上冰箱的門,腳趾撞到了桌腳,臨出門找不到想要的東西,就會突然忍不住掉眼淚。你可能會覺得小題大作,無法理解,但是只有我自己知道為什么。"


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM