页面中经常会遇到【导入和模板下载】的需求。下面是针对此类问题的一种实现方式
主要使用的库是 xlsx

安装包

npm i xlsx

构造一个 excel.js 的配置文件

import XLSX from 'xlsx'function downloadData(data, filename) {const link = document.createElement('a')document.body.appendChild(link)link.href = URL.createObjectURL(data)link.download = filenamelink.click()setTimeout(() => {document.body.removeChild(link)}, 1000)
}// 字符串转ArrayBuffer
function string2arraybuffer(s) {const buf = new ArrayBuffer(s.length)const view = new Uint8Array(buf)for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xffreturn buf
}/*** 列的枚举类型定义*/
class ColumnEnum {/**** @param label* @param {any} value*/constructor(label, value) {this.label = labelthis.value = value}
}/*** 列定义*/
class ColumnDef {/**** @param {string} name 列的标题* @param {string} field 列的字段* @param {object} [options] 列选项* @param {bool} [options.required=true] 列是否必填的。当其为 true 时,其值不能为空* @param {string} [options.type='string'] 列的类型,可选值见 ColumnDef.types* @param {ColumnEnum[]} [options.enums] 列的枚举定义* @param {function({value: any, column: ColumnDef})} [options.writeParser] 列的写处理函数* @param {function({value: any, column: ColumnDef})} [options.readParser] 列的读处理函数*/constructor(name, field, options) {this.name = namethis.field = fieldthis.options = Object.assign({required: true,type: ColumnDef.types.STRING,enums: []},options)}/*** 处理导入时的数据* @param value* @returns {string|number|*}*/parseReadValue(value) {if (this.options.readParser) {value = this.options.readParser({value,column: this})}const isEmpty = value === undefined || value === null || value === ''if (this.options.required && isEmpty) {throw new Error('值不能为空')}if (!isEmpty) {switch (this.options.type) {case ColumnDef.types.DATE:case ColumnDef.types.DATETIME:value = this._parseReadDate(value)breakcase ColumnDef.types.NUMBER:value = parseInt(value)breakdefault:break}if (this.options.enums.length && [ColumnDef.types.DATE, ColumnDef.types.DATETIME].indexOf(this.options.type) === -1) {value = this._parseReadEnum()}}return value}/*** 处理导出时的数据* @param value* @returns {string|number|*}*/parseWriteValue(value) {if (this.options.writeParser) {value = this.options.writeParser({value,column: this})}const isEmpty = value === undefined || value === null || value === ''if (!isEmpty) {switch (this.options.type) {case ColumnDef.types.DATE:case ColumnDef.types.DATETIME:value = this._parseWriteDate(value)breakdefault:break}if (this.options.enums.length && [ColumnDef.types.DATE, ColumnDef.types.DATETIME].indexOf(this.options.type) === -1) {value = this._parseWriteEnum(value)}}return value}/**** @param {number} value* @returns {string}* @private*/_parseReadDate(value) {const d = value - 1const t = Math.round((d - Math.floor(d)) * 24 * 60 * 60)const date = new Date(1900, 0, d, 8, 0, t)const dp = `${date.getFullYear()}-${this._pad(date.getMonth() + 1)}-${this._pad(date.getDate())}`const tp = `${this._pad(date.getHours())}:${this._pad(date.getMinutes())}:${this._pad(date.getSeconds())}`return this.options.type === ColumnDef.types.DATETIME ? `${dp} ${tp}` : dp}_parseReadEnum(value) {for (const e of this.options.enums) {if (e.label === value) {return e.value}}throw new Error(`无效的枚举值 "${value}"`)}_parseWriteEnum(value) {for (const e of this.options.enums) {if (e.value === value) {return e.label}}throw new Error(`无效的枚举值 "${value}"`)}/**** @param {string} value* @returns {string}* @private*/_parseWriteDate(value) {return this.options.type === ColumnDef.types.DATETIME ? value : value.split(' ')[0]}_pad(num) {return num.toString().padStart(2, '0')}
}/*** 列数据的类型* @type {{DATE: string, NUMBER: string, STRING: string}}*  如果表中有字段为 时间 类型,需要指定为 date 类型*/
ColumnDef.types = {STRING: 'string',NUMBER: 'number',DATE: 'date',DATETIME: 'datetime'
}/*** 表定义*/
class SheetDef {/**** @param {string} name Sheet名称* @param {ColumnDef[]} columns 列声明* @param {function({data: {}, index: number, raw: {}}): boolean | {}} [rowHandler] 行的值处理器。返回 false 表示值无效* @param {number} [maxRowCount] 最多读取的数据行数*/constructor(name, columns, rowHandler, maxRowCount) {this.name = name/**** @type {Map<string, ColumnDef>}*/this.columns = new Map()this.rowHandler = rowHandlerthis.maxRowCount = maxRowCountcolumns.forEach(column => {this.columns.set(column.name, column)})}/**** @param {WorkSheet} sheet*/read(sheet) {const rows = XLSX.utils.sheet_to_json(sheet, {defval: null})const data = []for (let index = 0; index < rows.length; index++) {if (this.maxRowCount && index === this.maxRowCount) {break}const row = rows[index]const rowData = Object.create(null)// 先检查是否行的所有值都为空// 要是都为空,跳过此行if (Object.values(row).every(value => {return value === undefined || value === null || value === ''})) {continue}this.columns.forEach((column, name) => {if (!row.hasOwnProperty(name)) {throw new Error(`在表 "${this.name}" 中找不到列 "${name}"`)}try {rowData[column.field] = column.parseReadValue(row[name])} catch (e) {throw new Error(`表 "${this.name}" 第 ${index + 2} 行 "${name}" ${e.message}`)}})if (this.rowHandler) {const result = this.rowHandler({data: rowData,raw: row,index: index})if (result === false) {throw new Error(`表 "${this.name}" 第  ${index + 2} 行值无效`)}if (result !== undefined) {const type = /^\[object ([^[]+)]$/.exec(Object.prototype.toString.call(result))[1]if (type !== 'Object') {throw new Error(`表 "${this.name} 的行处理函数返回值类型 "${type}" 无效:仅支持返回 object/false 类型`)}}}data.push(rowData)}return data}/*** 将数据写入 Sheet* @param {object[]} data* @returns {WorkSheet}*/write(data) {const header = []this.columns.forEach((col, colName) => {header.push(colName)})const rows = [header]data.forEach((row, index) => {const rowData = []this.columns.forEach(columnDef => {try {rowData.push(columnDef.parseWriteValue(row[columnDef.field]))} catch (e) {throw new Error(`表 "${this.name}" 的数据第 ${index} 行 "${name}" ${e.message}`)}})rows.push(rowData)})return XLSX.utils.aoa_to_sheet(rows)}
}/*** 工作薄定义*/
class WorkbookDef {/**** @param {SheetDef[]} defs 此表格中要使用的表定义*/constructor(defs) {/**** @type {Map<string, SheetDef>}*/this.sheets = new Map()defs.forEach(def => {this.sheets.set(def.name, def)})}/*** 读取文件内容* @param file* @returns {Promise<unknown>}*/async readFile(file) {const reader = new FileReader()const promise = new Promise((resolve, reject) => {reader.onload = function () {resolve(reader.result)}reader.onerror = function (e) {reader.abort()reject(e)}})console.log(file)console.log(reader)reader.readAsArrayBuffer(file)return promise}/*** 从 文件对象 读取数据* @param {File} file*/async read(file) {let dataBuffer = await this.readFile(file)const workbook = XLSX.read(dataBuffer, {type: 'array'})const result = []this.sheets.forEach((def, sheetName) => {if (workbook.SheetNames.indexOf(sheetName) === -1) {throw new Error(`找不到名称为 "${sheetName}" 的表`)}const sheet = workbook.Sheets[sheetName]result.push({name: sheetName,rows: def.read(sheet)})})return result}/*** 将数据写入文件* @param {[{name: string, rows: array}]} [data]* @param {string} saveAs 另存为文件名(会自动添加 xlsx 扩展名)*/write(data, saveAs) {const workbook = {SheetNames: [],Sheets: Object.create(null)}const tempData = {}data.forEach(item => {tempData[item.name] = item.rows})this.sheets.forEach(sheetDef => {const name = sheetDef.nameworkbook.SheetNames.push(name)const rows = tempData[name] || []workbook.Sheets[name] = this.sheets.get(name).write(rows)})const output = XLSX.write(workbook, {bookType: 'xlsx', // 要生成的文件类型bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性type: 'binary'})const blobData = new Blob([string2arraybuffer(output)], {type: 'application/octet-stream'})if (!saveAs) {return blobData}downloadData(blobData, saveAs + '.xlsx')}
}export {WorkbookDef, SheetDef, ColumnDef, ColumnEnum}

构造一个 import.js 文件

import { WorkbookDef, SheetDef, ColumnDef } from './excel'
/*** @returns WorkbookDef* data: 数组对象,对象里面包含数据data和sheet表名dataName* data 是一个数组对象,如 [{ label: '编号', field: 'no', required: true }, {...}],label 用于 excel 的表头字段名,field 用于 传给后台的字段名,required 表示是否一定为空。* dataName 是生成 excel 表名的一部分*/
function getWbDef(data, rowHandler) {let sheet = []data.forEach(ele => {sheet.push(new SheetDef(dataName,data.map(item => {return new ColumnDef(item.label, item.field, {required: item.required})}),rowHandler))})return new WorkbookDef(sheet)
}/*** 资产数据的导入和导入模板下载*/
export default {/*** 下载导入的模板文件* @param {string} dataName 多级名称写法: 服务器/国产化服务器*/downloadTemplate(data, dataName) {getWbDef(data).write([], dataName.replace('/', '_') + '-数据导入模板')},/**** @param {string} data 数组对象* @param {File} file 选择的文件对象*/async readData(data, file, rowHandler) {return getWbDef(data, rowHandler).read(file)},/*** 导出excel* @param {Array} data 写入excel的数据* { name: dataName, rows: data }:数据格式* 传入excel表的数据格式必须是 [ { name: dataName, rows: data }, { name: dataName, rows: data }, ... ],数组里面有几个对象就有几个sheet表*/export(data, dataName) {getDef(dataName).write(data, dataName)}
}

如何在 Vue 页面中使用

模板内容

<el-button type="primary" size="small" @click="downloadTpl">模板下载</el-button>
<el-button type="success" size="small" @click="importExcel">导入</el-button>
<!-- 导入 -->
<el-dialogtitle="导入数据"@close="closeUploadFile"width="650px"top="30vh":visible.sync="uploadVisible"><el-row><el-col :span="6">选择要导入的文件</el-col><el-col :span="12"><form ref="importForm"><input type="file" class="el-input" @change="onFileChange" /></form></el-col><el-col :span="6"><button @click="doImport">开始导入</button></el-col></el-row><div class="tip">请选择 xlsx 格式的资产文件</div>
</el-dialog>

js 部分

import InfoImport from '@/assets/scripts/import'export default {name: 'TableList',components: {DeptForm, DeptDetail},props: {term: {type: Array,default: () => []}},data() {return {uploadVisible: false,selectedFile: null,data:[],tableData: [ // 这个一般不是前端写死,从后台配置文件返回的接口中获取{ label: '编号', field: 'no', required: true },{ label: '负责人', field: 'name', required: true },{ label: '联系电话', field: 'phone', required: true },{ label: '邮箱', field: 'email', required: true },{ label: '备注', field: 'remark', required: false }]}},methods: {downloadTpl() {let res = {data: this.tableData,dataName: '人员信息'}this.data.push(res)InfoImport.downloadTemplate(this.data, '人员信息')},onFileChange(e) {this.selectedFile = e.target.files[0]},async doImport() {if (!this.selectedFile) {this.$message.warning('请选择要导入的文件')return}let datatry {data = await InfoImport.readData(this.data, this.selectedFile, e => {// 可以对表中字段做额外处理,如// if (!nos.hasOwnProperty(row.no)) {// throw new Error(`第 ${e.index + 1} 行数据 "编号" 值 "${row.no}" 无效`)// }// row.no= depts[row.no]})} catch (e) {this.$error(e)return}if (!data || !data.length) {this.$message.warning('选择的文件中没有数据')return}data = data[0].rows // json 格式this.$API.post('/api/person', {data}).then(() => {this.closeUploadFile()this.$message.success('导入成功')this.getList()}).catch(e => {this.$error(e)})},importExcel() {this.uploadVisible = true},closeUploadFile() {this.uploadVisible = falsethis.$refs.importForm.reset()this.selectedFile = null}    }

vue中使用xlsx实现excel表的导入和导出相关推荐

  1. Excel表的导入、导出

    文章目录 一.Excel表的两种导入导出方式(POI和JXL) 二.使用两种不同方式的概念和Excel的关系 三.实现两种不同方式的简单实例 一.Excel表的两种导入导出方式(POI和JXL) 注: ...

  2. Vue中使用xlsx下载Excel表格

    首先安装 npm i file-saver -S npm i xlsx -S <button @click='downTable'>下载表格</button> <el-t ...

  3. vue 使用js XLSX读取 excel 转换日期格式

    前言 大家好! 今天遇到了vue 使用js XLSX读取 excel 转换日期格式的问题,做个记录 问题 今天写excel文件上传时,遇到了时间格式没有正确转换的问题 解决方式 借用了 项目中读取 e ...

  4. 解决在Vue中使用axios用form表单出现的问题

    vue中使用Axios第三方库,采用形式提交,参数格式为multipart /格式数据 ,请求参数变为对象格式的解决办法.(推荐第二种方法) 提交数据的四种编码方式 一,应用/ X WWW的窗体-ur ...

  5. TP6框架--EasyAdmin学习笔记:Excel表单导入数据库

    这是我写的学习EasyAdmin的第四章,这一章我给大家分享下Excel表单导入数据库的全流程需要怎么处理并提供案例 首先给大家看下这个功能的原理,下面是PHP连接打印机的代码 public func ...

  6. 使用PHPExcel实现Excel文件的导入和导出(模板导出)

    在之前有写过一篇文章讲述了使用PHP快速生成excel表格文件并下载,这种方式生成Excel文件,生成速度很快,但是有缺点是: 1.单纯的生成Excel文件,生成的文件没有样式,单元格属性(填充色,宽 ...

  7. 基于SpringBoot+EasyExcel+vue3实现excel表格的导入和导出

    目录 基于SpringBoot+EasyExcel+vue3实现excel表格的导入和导出 一.导入和导出 二.导出数据为excel实现过程 三.将excel中的数据导入到数据库中 基于SpringB ...

  8. 【JavaWeb开发】使用java实现简单的Excel文件的导入与导出(POI)

    前言:在实际的开发中,我们经常需要用户在界面中输入大量重复且有规律的数据,但是一个表单一个表单的填写效率过慢,而且过多的表单也会给JavaWeb的业务逻辑开发带来不小的困扰,所以我们可以使用一个Exc ...

  9. Java解析xml文件dom4j篇(基于xml配置文件完成Excel数据的导入、导出功能完整实现)

    DOM4J解析XML文件 dom4j是一个Java的XML API,是jdom的升级产品,用来读写XML文件.另外对比其他API读写XML文件,dom4j是一个十分优秀的JavaXML API,具有性 ...

最新文章

  1. kibana使用_手把手教你使用Nginx实现Kibana的安全认证
  2. Jquery 将后台返回的字符串转换成Json格式的数据
  3. python 列表生成式_python 列表生成式 List Comprehensions
  4. 基于注解的AOP实现事务控制及问题分析
  5. python logging之multi-module
  6. 前端学习(2925):vue改变样式1
  7. sqlalchemy in查询优化_SQL高级:代码实战 SQL 单表优化
  8. Codeforces Ilya and Queries
  9. java编写个倒计时_怎么编写一个倒计时java程序?求具体步骤!
  10. c语言病毒分析(转)
  11. 《东周列国志》第十一回 宋庄公贪赂构兵 郑祭足杀婿逐主
  12. 手机1520 win8.1升级win10
  13. ffmpeg linux 升级_linux系统部署ffmpeg视频转码环境及使用方法 | linux系统运维
  14. 闲置电脑挂机赚钱-Traffmonetizer,支持windows,linux,Android,MacOS多平台
  15. 实用系列1 —— 视频中的语音转换成文字
  16. html中的定位及其定位方式
  17. Sentinel SuperPro/UltraPro Monitor v2.01
  18. SVD法坐标系转换原理
  19. matlab 获得子矩阵,matlab – 获取所有子矩阵
  20. 数据结构 第5章 树和二叉树 课后答案

热门文章

  1. “不差钱”的蔚来,为何着急回港上市?
  2. 【阿里云天池大赛赛题解析】 刷书笔记 Lesson 1 数据探索基础知识
  3. Mysql 的自增主键达到最大值,怎么办
  4. sublime text3 镜像下载_Sublime Text 3
  5. NULL Pointer Dereference(转)
  6. 关于Xcode设置网络代理
  7. ipqc的工作流程图_品质部各人员工作流程图
  8. HEVC代码学习35:xEncodeCU函数
  9. 你绝对没见过的船新版本,利用Python代码制作过年春联。
  10. 可观测性-Metrics-存储-InfluxDb连续查询(CQ)Continuous Queries