vue封装组件读取excel数据

表格使用的avue

<template>
  <basic-container>
    <avue-crud
      ref="crud"
      v-model="form"
      :page="page"
      :data="tableData"
      :table-loading="tableLoading"
      :option="table"
    >
      <template slot="menuLeft">
        <el-upload
          class="upload-demo"
          ref="upload"
          action=""
          :on-change="handleChange"
          :on-exceed="onExceed"
          :on-remove="handleRemove"
          :http-request="uploadFile"
          :limit="1"
          :file-list="fileList"
          :accept="accept"
          :auto-upload="false"
        >
          <el-button size="small" type="primary">点击上传</el-button>
          <div slot="tip" class="el-upload__tip">只 能 上 传 Excel 文 件</div>
        </el-upload>
      </template>

      <template slot="menuRight">
        <el-button type="primary" size="small" @click="submitUpload">
          导入
        </el-button>
        <el-button type="primary" size="small" @click="handleDownModel">
          模板下载
        </el-button>
        <el-button type="primary" size="small" @click="exportData">
          失败导出
        </el-button>
      </template>
    </avue-crud>
  </basic-container>
</template>

<script>
import { mapGetters } from 'vuex'
import XLSX from 'xlsx'
export default {
  name: 'ImportExam',
  components: {},
  props: {
    /** 下载模板名称 */
    modelPath: {
      type: String,
      default: ''
    },
    /** 表头字段 */
    headers: {
      type: Object,
      default: () => {
        return { value: '值' }
      }
    },
    crudAction: {
      type: Object,
      default: () => {
        return {}
      }
    },
    tableOption: {
      type: Object
    },
    closeDialog: {
      type: Function,
      default: null
    }
  },
  data() {
    return {
      tableData: [],
      searchForm: {},
      form: {},
      page: {
        // 分页参数
        total: 0, // 总页数
        currentPage: 1, // 当前页数
        pageSize: 10 // 每页显示多少条
      },
      query: {
        // 查询参数
      },
      tableLoading: false,
      fileList: [],
      accept: '.xlsx, .xls, .csv',
      excelData: {
        header: null,
        results: null
      },
      fileData: [],
      failData: []
    }
  },
  computed: {
    ...mapGetters(['permissions', 'roles']),
    table() {
      return this.tableOption
    }
  },
  created() {
    this.clearData()
  },
  methods: {
    handleDownModel() {
      window.open(`./model/${this.modelPath}`)
    },
    clearData() {
      this.tableData = []
      this.$refs.upload.clearFiles()
    },
    // 导入
    submitUpload() {
      if (this.fileData.length === 0) {
        this.$message.error('请选择文件!')
        return
      }
      this.tableData = []
      this.crudAction.importData(this.fileData).then(res => {
        this.tableData = res.data
        for (let i = 0; i < res.data.length; i++) {
          if (res.data[i].flag === true) {
            this.failData = res.data
            this.$message.error('导入失败')
            return
          }
        }
        this.$message.success('导入成功')
        this.$refs.upload.clearFiles()
        this.closeDialog()
      })
    },
    // 导出
    exportData() {
      if (this.failData.length === 0) {
        this.$message.warning('只允许失败的时候导出!')
        this.closeDialog()
        return
      }
      const hea = Object.values(this.headers)
      const arr = []
      if (
        hea.length &&
        this.failData &&
        Object.values(this.headers).some(item => hea.includes(item))
      ) {
        this.failData.forEach(item => {
          const row = {}
          for (const key in this.headers) {
            const element = this.headers[key]
            row[element] = item[key]
          }
          arr.push(row)
        })
      }
      this.exportExcel(arr, this.modelPath)
    },
    exportExcel(data, fileName) {
      const ws = XLSX.utils.json_to_sheet(data)
      const wb = XLSX.utils.book_new()
      XLSX.utils.book_append_sheet(wb, ws, 'sheet1')
      XLSX.writeFile(wb, fileName)
    },
    uploadFile(item) {},

    handleChange(file, fileList) {
      if (file.name !== this.modelPath) {
        this.$message.error(`请选择使用模板${this.modelPath}`)
        this.$refs.upload.clearFiles()
        return
      }
      this.readerData(file)
      this.$refs.upload.clearFiles()
    },
    // 获取标题
    getHeaderRow(sheet) {
      const headers = []
      const ref = sheet['!ref']
      if (ref) {
        const range = XLSX.utils.decode_range(ref)
        let C
        const R = range.s.r
        for (C = range.s.c; C <= range.e.c; ++C) {
          const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
          let hdr = 'UNKNOWN ' + C
          if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
          headers.push(hdr)
        }
      }
      return headers
    },
    // 字段名替换
    generateData({ header, results }) {
      this.excelData.header = header
      this.excelData.results = results
      this.fileData = []
      if (
        header.length &&
        results &&
        Object.values(this.headers).some(item => header.includes(item))
      ) {
        results.forEach(item => {
          const row = {}
          for (const key in this.headers) {
            const element = this.headers[key]
            row[key] = item[element]
          }
          this.fileData.push(row)
          this.tableData.push(row)
        })
      }
    },
    // 读取excel数据
    readerData(file) {
      const th = this
      return new Promise(function (resolve) {
        const reader = new FileReader()
        reader.onload = function (e) {
          const data = e.target.result
          this.wb = XLSX.read(data, {
            type: 'binary'
          })
          const firstSheetName = this.wb.SheetNames[0]
          const worksheet = this.wb.Sheets[firstSheetName]
          const header = th.getHeaderRow(worksheet)
          const results = XLSX.utils.sheet_to_json(worksheet)
          th.generateData({ header, results })
          resolve()
        }
        reader.readAsBinaryString(file.raw)
        // reader.readAsBinaryString(file) // 传统input方法
      })
    },
    handleRemove(file, fileList) {},
    // 文件超出个数限制时的钩子
    onExceed(files, fileList) {
      this.$message.warning(`文件不允许多选`)
      this.$refs.upload.clearFiles()
    }
  }
}
</script>

<style scoped>
.choice {
  margin-left: 30px;
}

.import {
  margin-left: 200px;
}
</style>

defaultOption

const propsHttp = {
  name: 'filename',
  url: 'uuid'
}

export { propsHttp }

export default {
  propsHttp: {
    ...propsHttp
  },
  height: 'auto',
  // header 50
  // margin-top 15
  // pagination 55
  // padding-bottom 15
  // margin-bottom 15
  calcHeight: 50 + 15 + 55 + 15 + 15,
  bottomOffset: 55 + 15 + 15,

  align: 'center', // default left 表格列齐方式
  border: true, // default false 表格边框
  // defaultSort 表格的排序字段 {prop: 'date', order: 'descending'} prop默认排序字段,order排序方式
  // dicData: {}, // 传入本次需要的静态字典(在column中dicData写对象key值即可加载)
  dicUrl: `/admin/dict/type/{{key}}`, // 字典的网络请求接口(例如配置/xxx/xx/{{key}},这样的格式,在column中dicData写加载的字典,自动替换key挂载请求)
  dic: '',
  expand: false, // default false 是否展开折叠行
  dialogClickModal: false, // default true
  // dialogWidth: '50%', // default 50% 弹出表单的弹窗宽度
  // formWidth: '100%', // default 100% 弹出表单宽度
  index: true, // default false 是否显示表格序号
  indexLabel: '序号', // default # 序号的标题
  labelWidth: 110, // default 110 弹出表单标题宽度
  size: 'small', // default medium 按钮大小
  tableSize: 'medium', // default 跟随 size 表格大小
  searchSize: 'small', // default small 搜索控件大小
  // min: 3 个字 55px,最少三个字,因为两个中文字会自动补充空格变成三个字
  // default: 4 个字 70px
  // 5 个字 85px
  // 6 个字 100px
  // 7 个字 110px
  searchLabelWidth: 70,
  // size: 'small',
  search: true, // default true 首次打开是否展开搜索栏目
  searchInline: false, // default true 行内表单模式
  selection: true, // default false 行可勾选
  showHeader: true, // default true 是否显示表格的表头
  stripe: true, // default false 是否显示表格的斑马条纹
  page: true, // default true 是否显示分页
  menu: true, // default true 是否显示操作菜单栏
  // menuWidth: 300, // default 240 操作菜单栏的宽度
  menuAlign: 'center', // default left 菜单栏对齐方式

  addBtn: false, // default true 添加按钮
  delBtn: false, // default true 删除按钮
  cellBtn: false, // default true 表格单元格可编辑(当 column 中有搜索的属性中有 cell 为 true 的属性启用,只对 type 为 select 和 input 有作用)
  columnBtn: false, // default true 列显隐按钮
  editBtn: false, // default true 编辑按钮
  selectClearBtn: true, // default true 清空选中按钮(当selection为true起作用)
  searchBtn: true, // default true 搜索显隐按钮(当column中有搜索的属性,或则searchsolt为true时自定义搜索启动起作用)
  refreshBtn: true, // default true 刷新按钮
  tip: false,
  // 'defaultSort': {
  //   prop: 'username',
  //   order: 'descending'
  // },

  // TODO: 配置优化,增加一个调用接口前是否需要赋值的配置
  column: [
    // {
    //   //! 两个字时是否自动填充空格变成三个字
    //   labelAutoFormatter: true
    // }
  ]
}

tableOption

import defaultOption from '@/const/crud/default'

export const tableOption = Object.assign(
  JSON.parse(JSON.stringify(defaultOption)),
  {
    dialogDrag: true,
    align: 'center',
    index: false, // default false 是否显示表格序号
    selection: false, // default false 行可勾选
    refreshBtn: false,
    searchBtn: false,
    dialogWidth: '75%',
    searchShowBtn: false,
    searchShow: false,
    emptyBtn: false,
    addBtn: false,
    menu: false,
    excelBtn: true,
    column: [
      {
        label: '考试名称',
        prop: 'exam_nm'
      },
      {
        label: '考试开始时间',
        prop: 'exam_beg_dt'
      },
      {
        label: '考试结束时间',
        prop: 'exam_end_dt'
      },
      {
        label: '科目',
        prop: 'subject'
      },
      {
        label: '考试说明',
        prop: 'exam_desc'
      }
    ]
  }
)

组件调用

<template>
  <basic-container>
    <avue-crud
      ref="crud"
      v-model="form"
      :before-open="boxhandleOpen"
      :page="page"
      :data="tableData"
      :table-loading="tableLoading"
      :option="tableOption"
      @current-change="currentChange"
      @row-update="rowUpdate"
      @row-save="rowSave"
      @row-del="rowDel"
      @refresh-change="refreshChange"
      @search-change="searchChange"
      @size-change="sizeChange"
    >
      <template slot="menuLeft">
        <el-button
          v-permission="'edu_exam_add'"
          type="primary"
          @click="handleAdd"
          size="small"
        >
          新 增
        </el-button>
        <el-button type="primary" @click="importExamVisible" size="small">
          导入
        </el-button>
      </template>
      <template slot-scope="scope" slot="menu">
        <!-- icon="el-icon-check" plain
          icon="el-icon-delete"-->
        <el-button
          v-permission="'edu_exam_upd'"
          type="primary"
          size="small"
          @click="handleEdit(scope.row, scope.index)"
        >
          编 辑
        </el-button>
        <el-button
          v-permission="'edu_exam_del'"
          type="danger"
          size="small"
          @click="handleDel(scope.row, scope.index)"
        >
          删 除
        </el-button>
      </template>
    </avue-crud>
    <el-dialog title="导入" width="900px" :visible.sync="dialogImportVisible">
      <UploadData
        ref="importExam"
        :headers="headers"
        model-path="考试信息导入模板.xlsx"
        :table-option="importTableOption"
        :crud-action="importDataAction"
        :close-dialog="closeDialog"
      ></UploadData>
    </el-dialog>
  </basic-container>
</template>

<script>
import {
  getPage,
  postObj,
  putObj,
  deleteById,
  importData
} from '@/api/edu/exam.js'
import { tableOption } from '@/const/crud/edu/examOption.js'
import UploadData from '@/components/exam/import-exam'
import { tableOption as importTableOption } from '@/const/crud/edu/examImportOption'
export default {
  name: 'EduExam',
  components: {
    UploadData
  },
  data() {
    return {
      tableOption,
      importTableOption,
      tableData: [],
      form: {},
      page: {
        // 分页参数
        total: 0, // 总页数
        currentPage: 1, // 当前页数
        pageSize: 10 // 每页显示多少条
      },
      query: {
        // 查询参数
      },
      tableLoading: false,
      dialogImportVisible: false,
      headers: {
        exam_nm: '考试名称',
        exam_beg_dt: '考试开始时间',
        exam_end_dt: '考试结束时间',
        subject: '科目',
        exam_desc: '考试说明'
      },
      importDataAction: {
        getPage,
        postObj,
        putObj,
        deleteById,
        importData
      },
      fileData: []
    }
  },
  created() {
    this.refreshChange()
  },
  computed: {},
  methods: {
    // 导入
    importExamVisible() {
      this.dialogImportVisible = true
      this.$nextTick(() => {
        this.$refs.importExam.clearData()
      })
    },
    uploadSuccess({ header, results }) {
      this.fileData = []
      if (
        header.length &&
        results &&
        Object.values(this.headers).some(item => header.includes(item))
      ) {
        results.forEach(item => {
          const row = {}
          for (const key in this.headers) {
            const element = this.headers[key]
            row[key] = item[element]
          }
          this.fileData.push(row)
        })
      }
    },
    closeDialog() {
      this.dialogImportVisible = false
      this.refreshChange()
    },
    handleAdd() {
      this.tableData = []
      this.refreshChange()
      this.$refs.crud.rowAdd()
    },
    handleEdit(row, index) {
      this.$refs.crud.rowEdit(row, index)
    },
    handleDel(row, index) {
      this.$refs.crud.rowDel(row, index)
    },
    // 导入
    importExam() {},
    /**
     * @title 每页多少条回调函数
     * @param val 显示多少条值
     */
    sizeChange(val, searchForm) {
      this.page.pageSize = val
      this.refreshChange({ searchForm })
    },
    /**
     * @title 表单打开前执行函数
     * @param show 展示方法
     */
    boxhandleOpen(show) {
      show()
    },
    searchChange(searchForm) {
      this.page.currentPage = 1
      this.refreshChange({ searchForm })
    },
    /**
     * @title 首次加载获取列表
     * @param val 当前第几页
     */
    refreshChange({ searchForm } = {}) {
      this.tableLoading = true
      getPage({
        // 查询参数
        current: this.page.currentPage,
        size: this.page.pageSize,
        // oFields: ['id'], // 排序字段
        // oTypes: ['desc'], // asc desc
        ...searchForm
      })
        .then(({ data: { records, total } }) => {
          this.tableData = records
          this.page.total = total
        })
        .finally(() => {
          this.tableLoading = false
        })
    },
    /**
     * @title 页码回调
     * @param val 当前第几页
     */
    currentChange(val, searchForm) {
      this.page.currentPage = val
      this.refreshChange({ searchForm })
    },
    /**
     * @title 数据删除回调
     * @param row 为当前的数据
     * @param index 为当前的数据第几行
     */
    rowDel({ id, version }, index) {
      // ID 为 row["id"]
      this.$confirm('是否确认删除?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        await deleteById(id, { version })
        this.$message.success('删除成功')
        this.refreshChange()
      })
    },
    /**
     * @title 数据更新回调
     * @param row 为当前的数据
     * @param index 为当前的数据第几行
     * @param done 为表单关闭函数
     * @param loading 为取消按钮加载中函数
     */
    rowUpdate(row, index, done, loading) {
      putObj(row)
        .then(() => {
          this.$message({
            showClose: true,
            message: '修改成功',
            type: 'success'
          })
          this.refreshChange()
          done()
        })
        .finally(() => {
          loading()
        })
    },
    /**
     * @title 数据添加回调
     * @param row 为当前的数据
     * @param done 为表单关闭函数
     * @param loading 为取消按钮加载中函数
     */
    rowSave(row, done, loading) {
      postObj(row)
        .then(() => {
          this.$message.success('新增成功')
          this.refreshChange()
          done()
        })
        .finally(() => {
          loading()
        })
    }
  }
}
</script>

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

௸྄ིོུ倾心ღ᭄ᝰꫛꫀꪝ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值