Excel下载模板文件和导入文件的步骤

一、配置api

javascript 复制代码
// 题目导入模板下载接口
export function exportTemplate() {
  return request({
    url: '/edu/question/exportTemplate',
    method: 'get',
    //必加
    header: {
      headers: {
        'Content-Type': 'application/x-download'
      },
    },
    responseType: "blob",//必加
  })
}
// 题目模板数据导入接口
export function importTemplate(file) {
  const data = new FormData()//必加
  data.append('file', file)//必加
  return request({
    url: '/edu/question/import',
    method: 'post',
    data
  })
}

二、vue中引用Api

TypeScript 复制代码
import {
  exportTemplate,
  importTemplate,
} from "@/api/edu/question";

三、引用下载文件的方法

TypeScript 复制代码
//引用下载文件的方法
import { download } from "@/utils/excel";
方法:/utils/excel
javascript 复制代码
/**
 * 通用js方法封装处理
 * Copyright (c) 2019
 */

/**
 * 参数处理
 * @param {*} params  参数
 */

// 封装exceljs
import ExcelJS from 'exceljs'
import FileSaver from 'file-saver'

export function tansParams(params) {
  let result = ''
  for (const propName of Object.keys(params)) {
    const value = params[propName]
    let part = `${encodeURIComponent(propName)}=`
    if (value !== null && value !== '' && typeof value !== 'undefined') {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (
            value[key] !== null &&
            value[key] !== '' &&
            typeof value[key] !== 'undefined'
          ) {
            let params = `${propName}[${key}]`
            let subPart = `${encodeURIComponent(params)}=`
            result += `${subPart + encodeURIComponent(value[key])}&`
          }
        }
      } else {
        result += `${part + encodeURIComponent(value)}&`
      }
    }
  }
  return result
}

// 验证是否为blob格式
export function blobValidate(data) {
  return data.type !== 'application/json'
}

// export function getSrc(url) {
//   return `https://${url}`
// }

// 得到字典某个值的内容
export function getDictLabel(dict, value) {
  let res = dict.filter((item) => item.value === value)
  return res[0]
}

// promise 只执行一次
export function oncePromise(fn, p = null) {
  return function (...arg) {
    // eslint-disable-next-line no-return-assign
    return p || (p = fn(...arg).finally(() => (p = null)))
  }
}

/**
 * 导出数据到Excel方法
 * @param {Array[Object]} config.data 表格数据
 * @param {Array[String]} config.fields 字段列表
 * @param {Array[String]} config.headers excel表头列表[[]],可以是多级表头[['A1','B1'],['A2','B2']]
 * @param {Array[Object]} config.merges 需要合并的单元格,需要考虑表头的行数[{row:1, col:1, rowspan: 1, colspan: 2}]
 * @param {Array[Object]} config.attrs 单元格样式配置
 * @param {Array[Object]} config.views 工作表视图配置
 * @param {Array[Number]} columnsWidth 每个字段列对应的宽度
 * @param {Object} config.protect 工作表保护【此配置会保护全表,一般推荐只针对单元格进行保护配置】
 * @param {String} sheetName 工作表名称,默认从sheet1开始
 * @param {String} fileName excel文件名称
 */
export function exportDataToExcel(config, fileName) {
  if (!config) return
  const options = {
    fileName: fileName || `导出excel文件【${Date.now()}】.xlsx`,
    worksheets: [],
  }
  if (!Array.isArray(config)) {
    config = [config]
  }
  config.forEach((item) => {
    // 深拷贝data【JSON.stringify有缺陷,可自行换成_.cloneDeep】
    const data = JSON.parse(JSON.stringify(item.data))
    const results = data.map((obj) => item.fields.map((key) => obj[key])) // 生成完整excel数据
    let excelData = []
    excelData = excelData.concat(item.headers).concat(results) // 单元格合并处理【excel数据的第一行/列是从1开始】
    let excelMerges = []
    excelMerges = item.merges.map((m) => [
      m.row + 1,
      m.col + 1,
      m.row + m.rowspan,
      m.col + m.colspan,
    ]) // 单元格配置处理【excel数据的第一行/列是从1开始】
    let excelAttrs = []
    excelAttrs = item.attrs.map((attr) => {
      attr.rowStart += 1
      attr.rowEnd += 1
      attr.colStart += 1
      attr.colEnd += 1
      return attr
    })
    options.worksheets.push({
      data: excelData,
      merges: excelMerges,
      attrs: excelAttrs,
      views: item.views,
      columnsWidth: item.columnsWidth,
      protect: item.protect,
      sheetName: item.sheetName,
    })
  })
  createExcel(options)
}

// 创建Excel文件方法
async function createExcel(options) {
  if (!options.worksheets.length) return // 创建工作簿
  const workbook = new ExcelJS.Workbook()
  for (let i = 0; i < options.worksheets.length; i++) {
    const sheetOption = options.worksheets[i] // 创建工作表
    const sheet = workbook.addWorksheet(
      sheetOption.sheetName || `sheet${i + 1}`
    ) // 添加数据行
    sheet.addRows(sheetOption.data) // 配置视图
    sheet.views = sheetOption.views // 单元格合并处理【开始行,开始列,结束行,结束列】
    if (sheetOption.merges) {
      sheetOption.merges.forEach((item) => {
        sheet.mergeCells(item)
      })
    } // 工作表保护
    if (sheetOption.protect) {
      const res = await sheet.protect(
        sheetOption.protect.password,
        sheetOption.protect.options
      )
    } // 单元格样式处理
    if (sheetOption.attrs.length) {
      sheetOption.attrs.forEach((item) => {
        const attr = item.attr || {} // 获取开始行-结束行; 开始列-结束列
        const { rowStart } = item
        const { rowEnd } = item
        const { colStart } = item
        const { colEnd } = item
        if (rowStart) {
          // 设置行
          for (let r = rowStart; r <= rowEnd; r++) {
            // 获取当前行
            const row = sheet.getRow(r)
            if (colStart) {
              // 列设置
              for (let c = colStart; c <= colEnd; c++) {
                // 获取当前单元格
                const cell = row.getCell(c)
                Object.keys(attr).forEach((key) => {
                  // 给当前单元格设置定义的样式
                  cell[key] = attr[key]
                })
              }
            } else {
              // 未设置列,整行设置【大纲级别】
              Object.keys(attr).forEach((key) => {
                row[key] = attr[key]
              })
            }
          }
        } else if (colStart) {
          // 未设置行,只设置了列
          for (let c = colStart; c <= colEnd; c++) {
            // 获取当前列,整列设置【大纲级别】
            const column = sheet.getColumn(c)
            Object.keys(attr).forEach((key) => {
              column[key] = attr[key]
            })
          }
        } else {
          // 没有设置具体的行列,则为整表设置
          Object.keys(attr).forEach((key) => {
            sheet[key] = attr[key]
          })
        }
      })
    } // 列宽设置
    if (sheetOption.columnsWidth) {
      for (let j = 0; j < sheet.columns.length; j++) {
        sheet.columns[j].width = sheetOption.columnsWidth[j]
      }
    }
  } // 生成excel文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    // application/octet-stream 二进制数据
    FileSaver.saveAs(
      new Blob([buffer], { type: 'application/octet-stream' }),
      options.fileName
    )
  })
}

// 下载文件流文件
export const download = (res, type, filename) => {
  // 创建blob对象,解析流数据
  const blob = new Blob([res], {
    // 设置返回的文件类型
    // type: 'application/pdf;charset=UTF-8' 表示下载文档为pdf,如果是word则设置为msword,excel为'application/vnd.ms-excel'
    type,
  }) // 这里就是创建一个a标签,等下用来模拟点击事件
  const a = document.createElement('a') // 兼容webkix浏览器,处理webkit浏览器中href自动添加blob前缀,默认在浏览器打开而不是下载
  const URL = window.URL || window.webkitURL // 根据解析后的blob对象创建URL 对象
  const href = URL.createObjectURL(blob) // 下载链接
  a.href = href // 下载文件名,如果后端没有返回,可以自己写a.download = '文件.pdf'
  a.download = filename
  document.body.appendChild(a) // 点击a标签,进行下载
  a.click() // 收尾工作,在内存中移除URL 对象
  document.body.removeChild(a)
  window.URL.revokeObjectURL(href)
}

三、添加点击事件下载

HTML:

html 复制代码
<template>
  <!-- 导入对话框 -->
  <el-dialog
    :title="title"
    :visible.sync="localImportShow"
    width="600px"
    @close="localImportClose"
    append-to-body
  >
    <el-form ref="form" :model="form" :rules="rules" label-width="80px">
      <el-row>
        <el-col :span="24">
          <el-form-item label="下载模板" prop="downloadTemplate">
            <el-button type="primary" @click="downloadCClick">
              下载
              <i class="el-icon-download"></i>
            </el-button>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="导入数据" prop="file">
            <el-upload
              ref="uploadExcel"
              class="upload-btn"
              action="#"
              :auto-upload="false"
              accept=".xls, .xlsx"
              :show-file-list="true"
              :file-list="form.file"
              :on-change="change"
              :on-remove="handleRemove"
              :limit="1"
            >
              <el-button slot="trigger" size="small" type="primary">
                选取文件
              </el-button>
            </el-upload>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="submitForm">确 定</el-button>
      <el-button @click="cancel">取 消</el-button>
    </div>
  </el-dialog>
</template>

VUEJS:

javascript 复制代码
<script>
import { exportTemplate, importTemplate } from "@/api/edu/question";
import { download } from "@/utils/excel";

export default {
  props: {
    //弹出框是否展示
    importShow: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      title: "导入试题数据",
      form: {
        file: [],
      },
      // 弹出框是否展示
      localImportShow: this.importShow,
      rules: {
        file: [{ required: true, message: "请导入数据", trigger: "change" }],
      },
    };
  },
  watch: {
    importShow(newVal) {
      this.localImportShow = newVal;
    },
    "form.file": {
      handler(nval) {
        if (nval.length) {
          this.$refs.form.clearValidate(["file"]);
        }
      },
      deep: true,
    },
  },
  methods: {
    localImportClose() {
      this.$emit("update:importShow", false);
      this.reset();
    },
    // 点击下载模板按钮
    downloadCClick() {
      exportTemplate().then((res) => {
        download(res, "application/vnd.ms-excel", "试题模板.xlsx");
      });
    },
    // 更换上传文件
    change({ raw }) {
      //使用set watch才能监测到有值
      this.$set(this.form.file, 0, raw);
    },
    handleRemove(file, fileList) {
      // 移除上传文件
      this.form.file = [];
    },
    // 提交事件
    submitForm() {
      this.$refs["form"].validate((valid) => {
        if (valid) {
          importTemplate(this.form.file[0])
            .then((res) => {
              if (res.code === 200) {
                this.localImportShow = false;
                this.$modal.msgSuccess("导入成功");
                this.$emit("getList");
              }
            })
            .catch((err) => {
              this.localImportShow = false;
            });
          this.$nextTick(() => {
            this.reset();
          });
        }
      });
    },
    // 取消按钮
    cancel() {
      this.localImportShow = false;
      this.reset();
    },
    // 重置数据
    reset() {
      this.$refs.form.clearValidate();
      this.form = {
        // parentId: null,
        file: [],
      };
    },
  },
};
</script>
相关推荐
Cachel wood9 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端11 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8515 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特38 分钟前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
旭久2 小时前
SpringBoot的Thymeleaf做一个可自定义合并td的pdf表格
pdf·html·springboot
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js