純前端實現excel表格導入導出

前言

github: https://github.com/stardew516...javascript

以往作excel表格下載功能的時候,都是後端生成好表格後,存儲在某個地方,而後給前端一個連接,前端使用a標籤加download下載,或者使用node。其實純前端也是能夠作表格下載的,有一個很好用的javascript插件叫js-xlsx。前端

js-xlsx

github:https://github.com/SheetJS/js...
使用js-xlsx時,前端能夠將後端返回的json數據拼接成本身須要導出的格式,下載到電腦中,徹底不依賴後端。導入只需像平時同樣選擇文件,而後解析excel表格數據,轉換成json格式。vue

目前js-xlsx對各瀏覽器的支持狀況以下圖所示:
js-xlsx兼容性java

用法

以vue使用爲例node

  1. vue-cli腳手架搭好框架
  2. 安裝包xlsxnpm install xlsx --save
  3. 代碼實現(全)git

    <template>
      <div class="index" v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="拼命加載中...">
        <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>
        <el-button class="button" @click="uploadFile()">導入</el-button>
        <el-button class="button" @click="downloadFile(excelData)">導出</el-button>
        <!--錯誤信息提示-->
        <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>
        <!--展現導入信息-->
        <el-table :data="excelData" tooltip-effect="dark">
          <el-table-column label="名稱" prop="name" show-overflow-tooltip></el-table-column>
          <el-table-column label="份量" prop="size" show-overflow-tooltip></el-table-column>
          <el-table-column label="口味" prop="taste" show-overflow-tooltip></el-table-column>
          <el-table-column label="單價(元)" prop="price" show-overflow-tooltip></el-table-column>
          <el-table-column label="剩餘(份)" prop="remain" show-overflow-tooltip></el-table-column>
        </el-table>
      </div>
    </template>
    
    <script>
      // 引入xlsx
      var XLSX = require('xlsx')
      export default {
        name: 'Index',
        data () {
          return {
            fullscreenLoading: false, // 加載中
            imFile: '', // 導入文件el
            outFile: '',  // 導出文件el
            errorDialog: false, // 錯誤信息彈窗
            errorMsg: '', // 錯誤信息內容
            excelData: [  // 測試數據
              {
                name: '紅燒魚', size: '大', taste: '微辣', price: '40', remain: '100'
              },
              {
                name: '麻辣小龍蝦', size: '大', taste: '麻辣', price: '138', remain: '200'
              },
              {
                name: '清蒸小龍蝦', size: '大', taste: '清淡', price: '138', remain: '200'
              },
              {
                name: '香辣小龍蝦', size: '大', taste: '特辣', price: '138', remain: '200'
              },
              {
                name: '十三香小龍蝦', size: '大', taste: '中辣', price: '138', remain: '108'
              },
              {
                name: '蒜蓉小龍蝦', size: '大', taste: '中辣', price: '138', remain: '100'
              },
              {
                name: '涼拌牛肉', size: '中', taste: '中辣', price: '48', remain: '60'
              },
              {
                name: '蝦仁壽司', size: '大', taste: '清淡', price: '29', remain: '無限'
              },
              {
                name: '海苔壽司', size: '大', taste: '微辣', price: '26', remain: '無限'
              },
              {
                name: '金針菇壽司', size: '大', taste: '清淡', price: '23', remain: '無限'
              },
              {
                name: '泡菜壽司', size: '大', taste: '微辣', price: '24', remain: '無限'
              },
              {
                name: '鰻魚壽司', size: '大', taste: '清淡', price: '28', remain: '無限'
              },
              {
                name: '肉鬆壽司', size: '大', taste: '清淡', price: '22', remain: '無限'
              },
              {
                name: '三文魚壽司', size: '大', taste: '清淡', price: '30', remain: '無限'
              },
              {
                name: '蛋黃壽司', size: '大', taste: '清淡', price: '20', remain: '無限'
              }
            ]
          }
        },
        mounted () {
          this.imFile = document.getElementById('imFile')
          this.outFile = document.getElementById('downlink')
        },
        methods: {
          uploadFile: function () { // 點擊導入按鈕
            this.imFile.click()
          },
          downloadFile: function (rs) { // 點擊導出按鈕
            let data = [{}]
            for (let k in rs[0]) {
              data[0][k] = k
            }
            data = data.concat(rs)
            this.downloadExl(data, '菜單')
          },
          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]])
              console.log(typeof json)
              $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]) {
              keyMap.push(k)
            }
            console.info('keyMap', keyMap, json)
            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: ['mySheet'], // 保存的表標題
              Sheets: {
                'mySheet': Object.assign({},
                  tmpdata, // 內容
                  {
                    '!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1] // 設置填充區域
                  })
              }
            }
            let tmpDown = new Blob([this.s2ab(XLSX.write(tmpWB,
              {bookType: (type === undefined ? 'xlsx' : type), 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) {  // 此處能夠解析導入數據
            return data
          },
          dealFile: function (data) {   // 處理導入的數據
            console.log(data)
            this.imFile.value = ''
            this.fullscreenLoading = false
            if (data.length <= 0) {
              this.errorDialog = true
              this.errorMsg = '請導入正確信息'
            } else {
              this.excelData = 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>
      .el-table th>.cell {
        text-align: center;
      }
      .button {
        margin-bottom: 20px;
      }
    </style>
  4. 啓動項目npm run dev

效果圖

clipboard.png

相關文章
相關標籤/搜索