zoukankan      html  css  js  c++  java
  • 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与丰富的应用记录(出问题可以问度娘或古哥哥),稳妥可控。

    "有时关不上冰箱的门,脚趾撞到了桌脚,临出门找不到想要的东西,就会突然忍不住掉眼泪。你可能会觉得小题大作,无法理解,但是只有我自己知道为什么。"

  • 相关阅读:
    我的vim开发环境搭建:C/C++/Go,持续更新中
    MFC的组合框(ComboBox)控件切换下拉样式
    回顾下杂乱的10月
    C++将整型数据转换成大端或小端存储顺序
    C/C++动态分配连续空间,下标越界导致的free():invalid next size问题
    O(n)空间复杂度,打印杨辉三角形的前n行
    C指针笔试题,蛋疼的多重指针运算,谭浩强的阴影
    2017滴滴出行笔试题:异或和为0的最大区间个数
    manjaro安装
    关于top命令
  • 原文地址:https://www.cnblogs.com/yanggb/p/12875806.html
Copyright © 2011-2022 走看看