表格导出,填写数据,导入表格
- 需求:表格导出,填写数据,导入数据
- 表格文件存储在前端
- 表格文件不存储
 
需求:表格导出,填写数据,导入数据
分析需求:
 (1)关于表格导出
       在前端页面导出表格,有三种情况:①表格文件存储在后端,后端返给前端文件流,前端调用后端接口,下载文件;②表格文件存储在前端,前端下载文件;③表格文件不存储,前端根据tableData的某些字段导出相应表格。
 (2)填写数据
       Excel里填写数据,便不再多说;
 (3)导入数据
       读取Excel表格,得到所需的数据,可能需要进行数据处理,总之,导入数据是给后端交一个对象数组
表格文件存储在前端
如图,表格文件template.xlsx存储在public下的static文件夹下
 
 页面布局和用户需填写的excel如下
 
 注意: 涉及到金额的保留2位不是四舍五入,而是直接保留两位,其他去掉。如:200.098保留2位,得到的结果为200.09。因此,在导入数据时,需要对数值进行处理,处理办法如下:
num=Math.floor(num*100)/100
代码实现
 1.template
<div class="buttons">
      <el-button type="primary" size="small"
                 @click="DownloadTemplate"
      >
        下载模板
      </el-button>
      <el-upload
        action=""
        class="upload"
        accept=".xlsx,.xls"
        :auto-upload="false"
        :show-file-list="false"
        :on-change="importData"
      >
        <el-button type="primary" size="small">
          导入数据
        </el-button>
      </el-upload>
    </div>
    <el-table
      :data="newData"
      style="width: 100%"
    >
      <el-table-column
        prop="name"
        label="姓名"
        width="180"
      />
      <el-table-column
        prop="date"
        label="出生日期"
      />
      <el-table-column
        prop="last"
        label="上月"
      />
      <el-table-column
        prop="next"
        label="下月"
      />
      <el-table-column
        prop="sum"
        label="合计"
      />
    </el-table>
  </div>
2.js
import * as XLSX from 'xlsx'
//相当于一个字典对应关系
const tempObj = {
  姓名: 'name',
  出生日期: 'date',
  上月: 'last',
  下月: 'next'
}
data () {
  return {
    newData: []
  }
},
methods: {
  // 点击按钮触发a下载文件
  DownloadTemplate () {
    let a = document.createElement('a')
    a.href = './static/template.xlsx'
    a.download = '近期消费统计表.xlsx'
    a.style.display = 'none'
    document.body.appendChild(a)
    a.click()
    a.remove()
  },
  
  ImportData (file) { // 参数为file,表示要上传的文件
    const types = file.name.slice(file.name.lastIndexOf('.')) // 获取文件后缀名
    const fileType = ['.xlsx', '.xls'].some(item => item === types) // 判断文件类型是否为xlsx或xls
    if (!fileType) { // 如果文件类型不正确,提示用户重新上传
      this.$message.warning('文件格式错误!请重新上传')
      return // 如果文件类型不正确 结束函数
    }
    this.newData = [] // 初始化newData,用于存储读取到的数据
    const reader = new FileReader() // 创建文件读取器
    reader.readAsBinaryString(file.raw) // 将文件内容读取成二进制字符串
    reader.onload = () => { // 当文件读取成功时
      const binary = reader.result // 获取文件内容的二进制字符串
      const wb = XLSX.read(binary, { // 将二进制字符串转换成workbook对象
        type: 'binary'
      })
      // 将workbook对象中的第一个sheet转换成JSON格式(跳过前两行)
      const outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { range: 2 })
      console.log(outdata)
      outdata.map((item, index) => {
        // 对读取的字段进行必要的判断
        this.JudgeEmpty(item['出生日期'], '出生日期', index)
        this.JudgeEmpty(item['姓名'], '姓名', index)
        this.JudgeEmptyAndNumber(item['上月'], '上月', index)
        this.JudgeEmptyAndNumber(item['下月'], '下月', index)
        let obj = {}
        for (let key in tempObj) {
          if (!item.hasOwnProperty(key)) { continue }
          if (key === '上月' || key === '下月') {
            obj[tempObj[key]] = Math.floor(item[key] * 100) / 100
          } else if (key === '出生日期') {
            obj[tempObj[key]] = this.$util.formatExcelDate(item[key])
          } else { obj[tempObj[key]] = item[key] }
        }
        this.newData.push(obj)
      })
      this.DataProcess()
    }
  },
  DataProcess () {
    // 涉及需要系统自动计算处理的
    this.newData.forEach(ele => {
      this.$set(ele, 'sum', Math.floor((ele.last + ele.next) * 100) / 100)
    })
    console.log(this.newData)
    // 处理完后,接下来调用接口,将newData提交给后端
  },
  
// Excel里字段判空
  JudgeEmpty (a, str, i) {
    i++
    if (typeof a === 'undefined') {
      this.$message.error('Excel中第' + i + '行<' + str + '>列数据未填写完整,请补充!')
      this.newData = []
      throw Error
    }
  },
  // Excel里字段判空且填写类型必须是数字
  JudgeEmptyAndNumber (a, str, i) {
    i++
    if (typeof a === 'undefined') {
      this.$message.error('Excel中第' + i + '行<' + str + '>列数据未填写完整,请补充!')
      this.newData = []
      throw Error
    }
    if (isNaN(a)) {
      this.$message.error('Excel中第' + i + '行<' + str + '>列数据类型不正确,请修正!')
      this.newData = []
      throw Error
    }
  }
}  
表格文件不存储
看图:
 
 excel的一些填写注意点与前端存储excel模板时一样
依赖下载:
npm install moment
npm install vue-json-excel?
npm install xlsx?
代码实现
 1.template
<download-excel
  class="export-excel-wrapper"
  :data="tableData"
  :fields="json_fields"
  header="近期消费统计模板表"
  type="xls"
  worksheet="My Worksheet"
  name="近期消费统计模板表"
>
  <el-button type="primary" size="small">
    导出模板
  </el-button>
</download-excel>
<!-- 导入数据按钮同前端存储excel -->
<el-table
  :data="tableData"
  style="width: 100%"
>
  <el-table-column
    prop="name"
    label="姓名"
    width="180"
  />
  <el-table-column
    prop="gender"
    label="性別"
    width="180"
  />
  <el-table-column
    prop="date"
    label="出生日期"
  />
  <el-table-column
    prop="last"
    label="上月"
  />
  <el-table-column
    prop="next"
    label="下月"
  />
  <el-table-column
    prop="sum"
    label="合计"
  />
  <el-table-column
    prop="remarks"
    label="备注"
  />
</el-table>
2.js
import * as XLSX from 'xlsx'
let tempObj = {
  出生日期: 'date',
  上月: 'last',
  下月: 'next',
  备注: 'remarks'
}
data () {
  return {
     // 导出的字段(前两个字段有值,后4个无值)
     json_fields: {
       姓名: 'name',
       性别: 'gender',
       出生日期: 'xxx',
       上月: 'xxx',
       下月: 'xxx',
       备注: 'xxx'
     },
     // 后端返给前端的初始假数据
     tableData: [
       {
         name: 'yxx',
         gender: '女'
       },
       {
         name: 'wx',
         gender: '男'
       }
     ],
     // 新导入的数据
     newData: []
   }
 },
methods: {
	ImportData (file) {
	  const types = file.name.slice(file.name.lastIndexOf('.')) // 获取文件后缀名
      const fileType = ['.xlsx', '.xls'].some(item => item === types) // 判断文件类型是否为xlsx或xls
      if (!fileType) { // 如果文件类型不正确,提示用户重新上传
        this.$message.warning('文件格式错误!请重新上传')
        return // 如果文件类型不正确 结束函数
      }
      this.newData = [] // 初始化newData,用于存储读取到的数据
      const reader = new FileReader() // 创建文件读取器
      reader.readAsBinaryString(file.raw) // 将文件内容读取成二进制字符串
      reader.onload = () => { // 当文件读取成功时执行以下操作
        const binary = reader.result // 获取文件内容的二进制字符串
        const wb = XLSX.read(binary, { // 将二进制字符串转换成workbook对象
          type: 'binary'
        })
        // 将workbook对象中的第一个sheet转换成JSON格式,跳过1行(标题行)
        const outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { range: 1 })
        console.log(outdata)
        outdata.map((item, index) => {
          this.JudgeEmpty(item['出生日期'], '出生日期', index)
          this.JudgeEmptyAndNumber(item['上月'], '上月', index)
          this.JudgeEmptyAndNumber(item['下月'], '下月', index)
          this.JudgeEmpty(item['备注'], '备注', index)
          let obj = {}
          // eslint-disable-next-line guard-for-in
          for (let key in tempObj) {
            // eslint-disable-next-line no-prototype-builtins
            if (!item.hasOwnProperty(key)) { return }
            if (key === '上月' || key === '下月') {
              obj[tempObj[key]] = Math.floor(item[key] * 100) / 100
            } else if (key === '出生日期') {
              obj[tempObj[key]] = this.$util.formatExcelDate(item[key])
            } else { obj[tempObj[key]] = item[key] }
          }
          this.newData.push(obj)
        })
        this.DataProcess()
      }
	},
	DataProcess () {
      this.tableData.forEach((ele, i) => {
        // eslint-disable-next-line guard-for-in
        for (let key in this.newData[i]) { // 将新旧数据对象拼接起来
          this.$set(ele, key, this.newData[i][key])
        }
        // 涉及需要系统自动计算处理的
        this.$set(ele, 'sum', Math.floor((ele.last + ele.next) * 100) / 100)
      })
      // 处理完后,接下来调用接口,将newData提交给后端
    }
}
处理excel输入为日期时的方法,放在了公共代码util.js里,如下:
import * as moment from 'moment'
const START_TIME = '1900/01/01'
const FORMAT = 'YYYY/MM/DD'
export default {
 // 日期转换:将1999年8月27日或1999/8/27 统一转为1996/08/27
  formatExcelDate (num) {
    let duration = num - 1
    // 1900/2/29的num为60
    if (num > 60) {
      // 对于num大于60的需解析日期,要减去多的1900/2/29日的那一天
      duration = num - 2
    }
    return moment(START_TIME).add(moment.duration({ days: duration }))
      .format(FORMAT)
  },
  install (innerVue) {
    innerVue.prototype.$util = this
  }
}

















