Excel导入/导出功能的实现

知识点回顾:

Object.keys(item) 取出对象所有的属性名组成一个数组

Object.values(item) 取出对象所有的属性的值组成一个数组

const obj={ 'a' :1 , 'b'=2}
Object.keys(obj) = ['a'  , 'b' ]
-----------------------------------
Object.values(obj) = [ 1  ,  2 ]

一.导入

1.操作步骤

  • 后端提供一个excel模板文件
  • 软件的使用者填写excel文件
  • 通过上传这个文件,实现批量导入功能

2.启动vue-admin-element中引入现成方案,并安装依赖包 yarn add XLSX -s

子组件 模板   vue-element-admin/src/components/UploadExcel/index.vue 中

子组件完整代码

<template>
  <div>
    <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">
      把excel拖动到这里,或者
      <el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">
        上传
      </el-button>
    </div>
  </div>
</template>

<script>
import 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: {
    generateData({ header, results }) {
      this.excelData.header = header
      this.excelData.results = results
      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() {
      this.$refs['excel-upload-input'].click()
    },
    handleClick(e) {
      const files = e.target.files
      const rawFile = files[0] // only use files[0]
      if (!rawFile) return
      this.upload(rawFile)
    },
    upload(rawFile) {
      this.$refs['excel-upload-input'].value = null // fix can't select the same excel

      if (!this.beforeUpload) {
        this.readerData(rawFile)
        return
      }
      const before = this.beforeUpload(rawFile)
      if (before) {
        this.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)
          this.generateData({ header, results })
          this.loading = false
          resolve()
        }
        reader.readAsArrayBuffer(rawFile)
      })
    },
    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>
.excel-upload-input{
  display: none;
  z-index: -9999;
}
.drop{
  border: 2px dashed #bbb;
  width: 600px;
  height: 160px;
  line-height: 160px;
  margin: 0 auto;
  font-size: 24px;
  border-radius: 5px;
  text-align: center;
  color: #bbb;
  position: relative;
}
</style>

父组件  src\views\excel\upload-excel.vue ,这个页面是引用了上边定义的组件,并传入两个属性:

父传子传递一个导入成功之后的方法,on-success   

UploadExcel 为组件标签 , onSuccess为定义的方法,传递到子组件中

<upload-excel 
      :on-success="handleSuccess" 
      :before-upload="beforeUpload" 
    />

父组件核心代码

methods: {
    beforeUpload(file) {
      const isLt1M = file.size / 1024 / 1024 < 1
      if (isLt1M) {
        return true
      }
      this.$message({
        message: 'Please do not upload files larger than 1m in size.',
        type: 'warning'
      })
      return false
    },
    handleSuccess({ results, header }) { // 成功调用之后的函数,成功之后导入操作
      console.log(results) // 上传之后的各个字段信息
      其中参数results 为 上传的各个字段的信息结果
      header 为表头
      然后得到results 传入数据转换的方法中,
      调用接口,
      路由跳转
    const arr = this.transExcel(results) //进行数据转换
    this.getImportEmployee(arr)  //调用接口传入参数
    注意: getImportEmployee() 方法是在methods中单独声明的
})

3.引入UploadExcel组件并注册为全局组件  从框架 拷贝出文件夹 ---> 全局引入

复制到我们自己的项目 src/components/UploadExcel下,同时在src/components/index.js将它注册成全局组件

import PageTools from './PageTools'
import UploadExcel from './UploadExcel'

export default {
  // 插件的初始化, 插件给你提供的全局的功能, 都可以在这里配置
  install(Vue) {
    // 进行组件的全局注册
    Vue.component('PageTools', PageTools) // 注册工具栏组件
    Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件
  }
}

4.建立新建一个公共的导入页面,即import路由组件 src/views/import/index.vue

配置路由 不需要根据权限控制,直接定义为静态路由,在src/router/index.js下的静态路由中添加一个路由

 {
    path: '/import',
    component: Layout,
    hidden: true, // 不显示到左侧 不参与遍历
    children: [{
      path: '',
      component: () => import('@/views/import')
    }]
  }

测试路由结果 浏览器手动输入/import

 //点击导入   $router.push('/路由页面')
 <el-button type="warning" size="small" @click="$router.push('/import')">导入Excel</el-button>

5.配置上传成功的回调函数 配置成功函数(excel解析成功之后自动调用)

调用接口进行excel上传的重点其实是数据的处理,我们需要按照接口的要求,把excel表格中经过插件处理好的数据处理成后端接口要求的格式,主要包括两个方面

1接口字段的key需要从中文转成英文(字段中文转英文。excel中读入的是姓名,而后端需要的是username)

2涉及到时间的字段需要进行严格的时间格式化处理(日期处理。从excel中读入的时间一个number值,而后端需要的是标准日期。)

5.1 中文转英文    views/excel/upload-excel.vue

  transExcelHander(results) {
      const userRelations = {
        '入职日期': 'timeOfEntry',
        '手机号': 'mobile',
        '姓名': 'username',
        '转正日期': 'correctionTime',
        '工号': 'workNumber',
        '部门': 'departmentName',
        '聘用形式': 'formOfEmployment'
      }
      return results.map(item => {
        // 取出对象所有的属性名组成一个数组
        // Object.keys(item) ===> ['入职日期', '姓名']
        const rs = {}
        Object.keys(item).forEach(key => {
          const enKey = userRelations[key]
            rs[enKey] = item[key]
             })
        return rs
      })
      }

5.2 日期处理 (utils /index.js)

// 把excel文件中的日期格式的内容转回成标准时间
export function formatExcelDate(numb, format = '/') {
  const time = new Date((numb - 1) * 24 * 3600000 + 1)
  time.setYear(time.getFullYear() - 70)
  const year = time.getFullYear() + ''
  const month = time.getMonth() + 1 + ''
  const date = time.getDate() - 1 + ''
  if (format && format.length === 1) {
    return year + format + (month < 10 ? '0' + month : month) + format + (date < 10 ? '0' + date : date)
  }
  return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}

5.3  调用接口 (完成按钮跳转,导入完成(接口调用)之后,再跳回到原来的页面)  api/employment

export function importEmployee(data) {
  return request({
    url: '/sys/user/batch',
    method: 'post',
    data
  })
}

5.4 引入 

import { formatExcelDate } from '@/utils/index'   //  日期转换
import { importEmployee } from '@/api/employees'  //把excel数据传走
export default {
  name: 'Import',
  methods: {
    beforeUpload(file) {
      const isLt1M = file.size / 1024 / 1024 < 1
      if (isLt1M) {
        return true
      }
      this.$message({
        message: 'Please do not upload files larger than 1m in size.',
        type: 'warning'
      })
      return false
    },
    -------------------------这里变了-----------------------------
    async   handleSuccess({ header, results }) {
      try {
        console.log('从当前excel文件中读出的内容是', results)
        console.log('从当前excel文件 头部', header)
        // results [{姓名:xxx}] 变为  results  [{ username:xxx}] 转英文
        const arr = this.transExcelHander(results)
        console.log('转换之后的格式是', arr)
        // 调用上传的接口,
        const rs = await importEmployee(arr)
        console.log('调用上传的接口', rs)
        // 上传成功之后,回去刚才的页面
        this.$router.back()
        this.$message.success('操作成功')
      } catch (err) {
        this.$message.error(err.message)
      }
    },
    transExcelHander(results) {
      const userRelations = {
        '入职日期': 'timeOfEntry',
        '手机号': 'mobile',
        '姓名': 'username',
        '转正日期': 'correctionTime',
        '工号': 'workNumber',
        '部门': 'departmentName',
        '聘用形式': 'formOfEmployment'
      }
     -------------------------------这里变了--------------------------------------------- 
      return results.map(item => {
        const rs = {}
        Object.keys(item).forEach(key => {
          const enkey = userRelations[key]
          if (enkey === 'timeOfEntry' || enkey === 'correctionTime') {
            rs[enkey] = new Date(formatExcelDate(item[key]))
          } else {
            rs[enkey] = item[key]
          }
        })
        return rs
      })
    }
  }
}
</script>

二. 导出Excel

( js数据 ----> excel文件 )

1.在vue-element-admin中有现成的功能参考 代码核心

import('@/vendor/Export2Excel').then(excel => {
    const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
    const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
    const list = this.list
    const data = this.formatJson(filterVal, list)
    excel.export_json_to_excel({
      header: tHeader,
      data,
      filename: this.filename,
      autoWidth: this.autoWidth,
      bookType: this.bookType
    })
    this.downloadLoading = false
 })

2.由于js-xlsx体积还是很大的,导出功能也不是一个非常常用的功能,所以上面在使用的时候建议使用懒加载技术

2.1.插件包位于src/vendor/export2Excel中,采用的是按需引入的方式

import ( ' @/vender/Export2Excel') .then (excel = >{   })

2.2 Export2Excel依赖的包有

npm install file-saver script-loader --s

3.给导出按钮添加点击事件

从后台重新获取数据(这样才能确保是最新的)

把数据的格式进行转换(后端给的数据字段名都是英文的),以用来做导出

要实现业务导出功能主要有以下关键点

  1. 本质:把后端接口的数据导出到excel表格中

  2. 准备好业务需要的表头数据header

  3. 准备好业务需要的表格数据data(二维数组)

3.1 表头header

const map = {
  'id': '编号',
  'password': '密码',
  'mobile': '手机号',
  'username': '姓名',
  'timeOfEntry': '入职日期',
  'formOfEmployment': '聘用形式',
  'correctionTime': '转正日期',
  'workNumber': '工号',
  'departmentName': '部门',
  'staffPhoto': '头像地址'
}

 3.2 data数据

具体的表格数据我们需要通过接口从后端获取回来,难点在于如何把后端返回的数据处理成Export2Excel插件需求的二维数组格式

代码完成

 method:{
 transData(arr) {
      // 把formOfEmployment中的1改成正式, 2改成非正式
      arr.forEach(item => {
        if (item.formOfEmployment) {
          // 1 --> 正式
          // 2 --> 非正式
          item.formOfEmployment = hireType[item.formOfEmployment]
        }
      })
      const map = {
        'id': '编号',
        'password': '密码',
        'mobile': '手机号',
        'username': '姓名',
        'timeOfEntry': '入职日期',
        'formOfEmployment': '聘用形式',
        'correctionTime': '转正日期',
        'workNumber': '工号',
        'departmentName': '部门',
        'staffPhoto': '头像地址'
      }
      // 取出数组中第一个元素,来根据key生成对应的中文
      const first = arr[0]
      const header = Object.keys(first).map(item => map[item])
      console.log('表头是', header)
      const data = arr.map(obj => {
        return Object.values(obj)
      })
      console.log('数据是', data)
      return { header, data }
    },
    // 把当前页的数据导出来,保存成excel文件
    // 有一个现成的工具:/vendor/Export2Excel
    hExportExcel() {
      // 懒加载的写法
      import('@/vendor/Export2Excel').then(async excel => {
        // 1. 发请求,获取当前页的数据
        const rs = await reqGetEmployeeList(this.params)
        console.log('从后端获取的员工列表是', rs.data.rows)

        // 2. 修改一下数据格式,以便导出
        const { header, data } = this.transData(rs.data.rows)
        // 3. 导出
        excel.export_json_to_excel({
          header, // 表头 必填   ["姓名", "手机号"]
          data, // 数据          [ ["小王", "13797979782"],  ["小李", "13798779782"]]
          filename: 'test-export-excel', // 文件名
          autoWidth: true, // 自动设置列的宽度
          bookType: 'xlsx' // 文件类型
        })
      })
    },
    // 关闭弹层
    // 1. 在表单中点击取消(添加成功)
    // 2. 弹层上点击 X
    hCloseDialog() {
      // 相当于是在父组件中去执行子组件中方法
      // 去清空 子组件中的表单数据和校验结果
      // 子组件.resetForm()
      console.log('在父组件中获取子组件的引用', this.$refs.AddEmployee)
      this.$refs.AddEmployee.resetForm()
    },
    formatEmployment(id) {
      // console.log('执行一次formatEmployment')
      // // 对后端返回的类型的编码进行翻译 ,还原成 中文
      // const rs = EmployeeEnum.hireType.find(item => item.id === id)
      // return rs.value
      return hireType[id]
    },
    async doDel(id) {
      try {
        const rs = await delEmployee(id)
        console.log('删除之后的结果是', rs)
        // 1. 提示
        this.$message.success('删除成功')
        if (this.employees.length === 1) {
          this.params.page--
          if (this.params.page === 0) {
            this.params.page = 1
          }
          // this.params.page = (this.params.page - 1) || 1
        }
        // 2. 重新加载数据
        this.loadEmployeeList()
      } catch (err) {
        this.$message.error(err.message)
      }
    }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值