基于vue 实现 excel导出导入功能

所以我们需要下载三个包,和一个js文件 Export2Excel
npm install xlsx file-saver -S
npm install script-loader -S -D

1.1:基本表头导出功能

1.2:复杂表头导出功能

1.3:excel表格导入功能

1.1.1:excel导出逻辑
逆向思维思考:

1-最终要拿到的是一个充满数据的文件
2-如何导出一个文件:用到excel.export_json_to_excel
3-export_json_to_excel方法需要那些参数:

  multiHeader:// 多级表头
  header:, //表头 必填
  data: //具体数据 必填
  filename: //表格名字
  merges: //合并单元格
  autoWidth: true, // 是否自适应宽度
  bookType: 'xlsx', //导出格式

4-了解参数的数据类型,接下来将数据转成能够导出的数据类型,也就是处理数据
5-先有数据才能处理数据,所以应该是要拿数据
到现在导出的逻辑已经清楚,那么开始正向梳理一下逻辑
1-发请求拿数据
2-定义方法处理数据
3-调用export_json_to_excel方法导出表格
4-查看导出的表格是否满意

// 1- 获取数据并且处理完的数据
    async onExportFn() {
      // 1-请求获取所有的数据
      // {data} 是对请求拿到的数据进行结构
      const { data } = await FindStaffList({ page: 1, size: this.total })
      // 1-1 此时list里面保存的是所有的员工列表信息
      const list = data.rows
      console.log(list)
      // 2-2 对数据进行处理 此时FilnalData 拿到的就我们要的数据格式  
      // 此处我封装成了一个函数 onSortList 并且将拿到的list数据传入该方法
      return this.onSortList(list)
    },

注意一点在这里需要data的数据类型是一个二维数组[[],[],[],....[]] 在vue-element-admin 中有详细的文档,搜索“导出” 或者搜索xlsx即可

// 2-2 对数据进行处理  在这里只是讲获取数据的方法,这是最简单的一种,也可以用map对数据进行处理,方法不一,只列出最简单易理解的
onSortList(list) {
      // 2-1 先定义排列顺序  这里通常有一个学术名叫做对照表
      const sortOrder = [
        'username',
        'mobile',
        'timeOfEntry',
        'formOfEmployment',
        'correctionTime',
        'workNumber',
        'departmentName',
      ]
      // FilnalData 用来存放最后的数据
      const FilnalData = []
      list.forEach((item) => {
        const arr = []
        sortOrder.forEach((obj) => {
          if (obj === 'formOfEmployment') {
            arr.push(item[obj] === 1 ? '正式' : '非正式')
          } else {
            arr.push(item[obj])
          }
        })
        FilnalData.push(arr)
      })
      return FilnalData
    },
// 3- 此时数据已经处理完毕,那么就可以调用export_json_to_excel方法来进行导出表格
async onOrdinaryExport() {
      this.ExportLoading = true
      // onExportFn() 这里是一个primise异步方法,所以要用await
      const FilnalData = await this.onExportFn()
      //  import('@/vendor/Export2Excel') 懒加载  导入后,拿到excel对象 的对象中有export_json_to_excel 方法
      import('@/vendor/Export2Excel').then((excel) => {
        excel.export_json_to_excel({
          header: [
        '姓名',
        '手机号',
        '入职日期',
        '聘用形式',
        '转正日期',
        '工号',
        '部门',
      ], //表头 必填
          data: FilnalData, //具体数据 必填
          filename: '员工信息表格',
          autoWidth: true,
          bookType: 'xlsx',
        })
      })
      this.ExportLoading = false
    },

1.2.1复杂表头导出功能
复杂表头和基本表头的导出,思考的逻辑是一样的,所以不再赘述

复杂表头的导出,相比基本表头导出,只有在export_json_to_excel中传递的参数多了两个 一个是 multiHeader(多级表头),一个是merges(合并)

通过分析export_json_to_excel.js文件的源代码,我们可以分析出,multiHeader是二维数组,merges是一维数组,数组中的内容是字符串,而且是带有:的字符串

所以,根据要求,我们可以自由合并单元格

filename = filename || 'excel-list'
  data = [...data]
  data.unshift(header)
 
  for (let i = multiHeader.length - 1; i > -1; i--) {
    data.unshift(multiHeader[i])
  }

部分源码如图。

data中的数据,首先会先把传过来的数据data解构后放入data中,然后对多级表头进行循环,循环结束,将每个数据push到data中,因为data中的数据是个数组中放的每一个元素是数组

所以multiHeader[i]是数组,所以multiHeader 是二维数组

复杂表头代码如下,与一般表头只有在导出的时候有差别

async onComplexExport() {
      this.ComplexExportLoading = true
      // 调用onExportFn方法获取处理完成后的数据  该方法,上面有
      const FilnalData = await this.onExportFn()
      
      import('@/vendor/Export2Excel').then((excel) => {
        excel.export_json_to_excel({
          multiHeader: [['姓名', '主要信息', '', '', '', '', '部门']],
          header: [
        'username',
        'mobile',
        'timeOfEntry',
        'formOfEmployment',
        'correctionTime',
        'workNumber',
        'departmentName',
      ], //表头 必填
          data: FilnalData, //具体数据 必填
          filename: '复杂表头的员工信息表格',
          // 要合并的单元格
          merges: ['A1:A2', 'B1:F1', 'G1:G2'],
          autoWidth: true,
          bookType: 'xlsx',
        })
      })
      this.ComplexExportLoading = false
    },

1.3.1:excel表格导入功能

Excel 导入

封装了UploadExcelExcel 导入组件,支持点击和拖拽上传,同样它也是依赖js-xlsx的。

所以要实现导入功能

需要下载三个包,和一个vueUploadExcel组件

npm install xlsx file-saver -S
npm install script-loader -S -D

UploadExcel它提供了两个回调函数:beforeUpload:你可以在上传之前做一些自己的特殊判断,如判断文件的大小是否大于 1 兆?若大于 1 兆则停止解析并提示错误信息。

  • onSuccess 解析成功时候会触发的回调函数,它会返回表格的表头和内容。
handleSuccess({ results, header }) {
      this.tableData = results
      this.tableHeader = header
    }

导入的功能,总体来说,要比导出简单一些,更加容易理解一下

总结思路
反向思考
1-最终要实现的效果,给当前页面添加几行数据
2-要实现给当前页面添加几行数据,并且是我们看的到数据,因为只有数据才能改变视图,所以我们一定要发请求,才能够将数据添加上
3--发请求,必然要牵扯到请求的参数,那么就要看参数是什么格式的
4-有了参数的格式,那么我们就需要将一个方法,用来将我们得到的数据,转变成参数需要的数据类型
5-要转变,首先要先拿到表格里面的数据,所以这就需要用到我们的UploadExcel组件给我们提供的一个方法onSuccess
到现在导出的逻辑已经清楚,那么开始正向梳理一下逻辑‘
1-下载组件,在页面中导入,使用onSuccess方法,拿到表格的数据 tableData
2-定义一个方法,对拿到的数据进行处理,将数据处理成我们发请求需要的数据
3-定义接口,导入接口,发请求
4-调用接口,重新获取数据,渲染视图
逻辑清楚,代码开始
以下是我对UploadExcel组件的一些源码的分析与整理,只有简单的标注和样式的修改

<template>
  <div class="upload-excel">
    <div class="btn-upload">
      <el-button
        :loading="loading"
        size="mini"
        type="primary"
        @click="handleUpload"
      >
        点击上传
      </el-button>
    </div>
    <!-- 隐藏域 -->
    <!-- 2-监听表单的change事件,点击了button按钮,触发 表单点击事件  当选中了文件后,会触发change事件 -->
    <input
      ref="excel-upload-input"
      class="excel-upload-input"
      type="file"
      accept=".xlsx, .xls"
      @change="handleClick"
    />
    <div
      class="drop"
      @drop="handleDrop"
      @dragover="handleDragover"
      @dragenter="handleDragover"
    >
      <i class="el-icon-upload" />
      <span>将文件拖到此处</span>
    </div>
  </div>
</template>
 
<script>
import * as XLSX from 'xlsx'
 
export default {
  props: {
    beforeUpload: Function, // eslint-disable-line
    onSuccess: Function, // eslint-disable-line
  },
  data() {
    return {
      loading: false,
      excelData: {
        header: null,
        results: null,
      },
    }
  },
  methods: {
    // 8-1 generateData  方法,接收两个参数,一个是处理完的表格头数据,一个是表格内容数据
    generateData({ header, results }) {
      this.excelData.header = header
      this.excelData.results = results
      // 8-2 数据处理完成,进入逻辑判断,父传入onSuccess方法  则调用方法,并且传入读取后的表格数据
      this.onSuccess && this.onSuccess(this.excelData)
    },
    handleDrop(e) {
      e.stopPropagation()
      e.preventDefault()
      if (this.loading) return
      const files = e.dataTransfer.files
      if (files.length !== 1) {
        this.$message.error('Only support uploading one file!')
        return
      }
      const rawFile = files[0] // only use files[0]
 
      if (!this.isExcel(rawFile)) {
        this.$message.error(
          'Only supports upload .xlsx, .xls, .csv suffix files'
        )
        return false
      }
      this.upload(rawFile)
      e.stopPropagation()
      e.preventDefault()
    },
    handleDragover(e) {
      e.stopPropagation()
      e.preventDefault()
      e.dataTransfer.dropEffect = 'copy'
    },
    handleUpload() {
      // 1- 当点击button按钮的时候触发表单的点击事件
      this.$refs['excel-upload-input'].click()
    },
    handleClick(e) {
      // 3-1 选中的文件的数组
      const files = e.target.files
      //3-2 拿到具体的文件
      const rawFile = files[0]
      if (!rawFile) return
      // 3-3 触发upload方法  同时把当前拿到的文件传过去
      this.upload(rawFile)
    },
    upload(rawFile) {
      // 4-1 参数rawFile是当前选中的文件   表单清空
      this.$refs['excel-upload-input'].value = null
      // 4-2 this.beforeUpload prop传入的方法  表示加载前 有没有选中文件
      if (!this.beforeUpload) {
        this.readerData(rawFile)
        return
      }
      // 4-3 将选中的文件,传给父 父可以做一些拦截操作,可以限制文件的大小,类型等等 如果不满足条件返回false  默认返回true
      const before = this.beforeUpload(rawFile)
      if (before) {
        this.readerData(rawFile)
      }
    },
    // 5-1 满足条件,进入readerData  rawFile是当前选中的文件
    readerData(rawFile) {
      this.loading = true
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = (e) => {
          const data = e.target.result
          const workbook = XLSX.read(data, { type: 'array' })
          const firstSheetName = workbook.SheetNames[0]
          const worksheet = workbook.Sheets[firstSheetName]
          const header = this.getHeaderRow(worksheet)
          const results = XLSX.utils.sheet_to_json(worksheet)
          // 7- 在这一步,确认数据已经处理完成  跳转到新的方法generateData()中,并且把处理完的数据{ header, results } 解构后传给这个方法
          this.generateData({ header, results })
          this.loading = false
          resolve()
        }
        reader.readAsArrayBuffer(rawFile)
      })
    },
    // 6  这一块是处理数据  不用详细深究
    getHeaderRow(sheet) {
      const headers = []
      const range = XLSX.utils.decode_range(sheet['!ref'])
      let C
      const R = range.s.r
      /* start in the first row */
      for (C = range.s.c; C <= range.e.c; ++C) {
        /* walk every column in the range */
        const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
        /* find the cell in the first row */
        let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
        if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
        headers.push(hdr)
      }
      return headers
    },
    isExcel(file) {
      return /\.(xlsx|xls|csv)$/.test(file.name)
    },
  },
}
</script>
 
<style scoped lang="scss">
.upload-excel {
  display: flex;
  justify-content: center;
  margin-top: 100px;
  .excel-upload-input {
    display: none;
    z-index: -9999;
  }
  .btn-upload,
  .drop {
    border: 1px dashed #bbb;
    width: 350px;
    height: 160px;
    text-align: center;
    line-height: 160px;
  }
  .drop {
    line-height: 80px;
    color: #bbb;
    i {
      font-size: 60px;
      display: block;
    }
  }
}
</style>

接受onSuccess回调函数中,对数据进行处理

onSuccess(excelData) {
      // console.log(excelData)  在这里打印的是除了表头之外的数据 也就是我们需要的数据
      // 定义一个数据对照表
      const contrastObj = {
        入职日期: 'timeOfEntry',
        姓名: 'username',
        手机号: 'mobile',
        工号: 'workNumber',
        转正日期: 'correctionTime',
      }
      // 定义一个空数组,用来保存最后需要发请求的数据
      const excelList = []
      // 对拿到的数据进行遍历
      excelData.results.forEach((item) => {
        const obj = {}
        for (const key in item) {
          if (key === '转正日期' || key === '入职日期') {
            obj[contrastObj[key]] = formatDate(item[key], '-')
          } else {
            obj[contrastObj[key]] = item[key]
          }
        }
        excelList.push(obj)
      })
      // console.log(excelList)
      // 在这里已经完成数据的整理,调接口,发送请求
    },

感谢来源:https://blog.csdn.net/LOxia/article/details/126145142

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容