当前位置: 首页 > news >正文

Vue - 使用Element UI Upload / importExcelJs进行文件导入

1 情景一

需求背景后端配合,点击"导入"按钮,弹出“导入”弹窗,将电脑本地Excel表格数据导入到页面中表格位置(需要调用后端接口),而页面中表格通过后端接口获取最新数据。
实现思路:弹窗嵌入 Element UI Upload 上传组件,获取到文件后调后端接口。

action: 上传的地址
file-list: 上传的文件列表, 例如: [{name: ‘food.jpg’, url: ‘https://xxx.cdn.com/xxx.jpg’}]
headers: 设置上传的请求头部(上传的文件可能是有固定格式的(表头),这个是在后端设定好了的,所以如果上传的文件格式不对可能会引起报错。)
before-upload: 上传文件之前的钩子,可作上传之前校验
on-error: 文件上传失败时的钩子
on-exceed: 文件超出个数限制时的钩子
on-success: 文件上传成功时的钩子
on-change: 文件状态改变时的钩子
this.$refs.fileUpload.submit(): 手动上传文件列表(fileUpload是自定义的名字)

  • 组件代码
<template><span><el-buttonplainicon="el-icon-upload2"type="primary"size="mini":disabled="disabled"@click="importFile">{{ $t('import') }}</el-button><el-dialogv-if="open":title="importTitle":visible.sync="open"width="500px"append-to-body:close-on-click-modal="false":show-close="false"@close="cancel"><div v-if="isShowRecord" class="record-btn"><el-button icon="el-icon-time" type="primary" size="mini" @click="recordBtnClick"><span>{{ recordText }}</span></el-button></div><div style="text-align: center;"><el-uploadref="fileUpload"multiple:auto-upload="false":action="uploadFileUrl":before-upload="handleBeforeUpload":file-list="fileList":limit="limit":on-error="handleUploadError":on-exceed="handleExceed":on-success="handleUploadSuccess":on-change="handleChange":headers="headers"class="upload-file-uploader"><div style="border:1px dashed #e9e9e9"><i class="el-icon-plus avatar-uploader-icon" style="margin:80px;font-size: 25px;" /></div><!-- 上传提示 --><div v-if="showTip" slot="tip" class="el-upload__tip">{{ $t("pleaseUpload") }}<template v-if="fileSize">{{ $t("sizeLess") }}<b style="color: #f56c6c">{{ fileSize }}MB</b></template><template v-if="fileType">{{ $t("formatIs") }}<b style="color: #f56c6c">{{ fileType.join("/") }}</b></template>{{ $t("file") }}</div></el-upload></div><div class="template"><!-- <form v-show="false" id="templateForm" :action="templateUrl" method="get" /> --><span @click="downloadFile">{{ $t("downloadThisTemplate") }}</span></div><div slot="footer" class="dialog-footer"><el-button type="primary" @click="submitForm">{{ $t('confirm') }}</el-button><el-button @click="cancel">{{ $t('cancel') }}</el-button></div></el-dialog></span>
</template>
<script>
import { getToken } from '@/utils/auth'
import { downloadGet } from '@/utils/request'
import moment from 'moment'
import $ from '@/i18n/index'export default {name: 'FileImport',props: {// 是否禁用导入按钮disabled: {type: Boolean,default: false},// 导入弹窗标题importTitle: {type: String,default: () => {return $.t('fileImport')}},// 数量限制limit: {type: Number,default: 1},// 大小限制(MB)fileSize: {type: Number,default: 20},// 文件类型, 例如['png', 'jpg', 'jpeg']fileType: {type: Array,default: () => ['csv']},// 是否显示提示isShowTip: {type: Boolean,default: true},// 下载模板路径downLoadUrl: { type: String, default: null },// 下载的全拼路径fullPathUrl: { type: String, default: null },// 模板名称fileName: { type: String, default: moment().format('yyyyMMDDHHmmss') },// 导入数据路径importUrl: { type: String, default: null },// 是否展示导入导出记录按钮isShowRecord: {type: Boolean,default: false},// 导入导出记录文字recordText: {type: String,default: () => {return $.t('importAndExportRecord')}}},data() {return {open: false,number: 0,baseUrl: process.env.VUE_APP_BASE_API,// templateUrl://   this.fullPathUrl || process.env.VUE_APP_BASE_API + this.downLoadUrl,fileList: []}},computed: {headers() {return { Authorization: getToken() }},// 是否显示提示showTip() {return this.isShowTip && (this.fileType || this.fileSize)},// 上传文件服务器地址uploadFileUrl() {return process.env.VUE_APP_BASE_API + this.importUrl}// templateUrl() {//   return this.fullPathUrl || process.env.VUE_APP_BASE_API + this.downLoadUrl// }},methods: {importFile() {this.open = true},submitForm() {if (this.fileList.length === 0) {this.$modal.msgError(this.$t('pleaseImportFile'))return}this.$refs.fileUpload.submit()},cancel() {this.$refs.fileUpload.clearFiles()this.open = falsethis.$emit('importSuccess')},// 上传前校检格式和大小handleBeforeUpload(file) {// 校检文件类型if (this.fileType) {const fileName = file.name.split('.')const fileExt = fileName[fileName.length - 1]const isTypeOk = this.fileType.indexOf(fileExt) >= 0if (!isTypeOk) {this.$modal.msgError(`${this.$t('fileTypeErrorUpload')}${this.fileType.join('/')}${this.$t('file')}`)return false}}// 校检文件大小if (this.fileSize) {const isLt = file.size / 1024 / 1024 < this.fileSizeif (!isLt) {this.$modal.msgError(`${this.$t('uploadFileSizeLess')} ${this.fileSize} MB!`)return false}}this.$modal.loading(this.$t('waitForUpload'))this.number++return true},// 文件个数超出handleExceed() {this.$modal.msgError(`${this.$t('uploadFileNumberLess')} ${this.limit}`)},// 上传失败handleUploadError() {this.$modal.msgError(this.$t('uploadFail'))this.$modal.closeLoading()},// 上传成功回调handleUploadSuccess(res, file) {if (res.code === 0) {this.$message({ type: 'success', message: this.$t('uploadSuccess') })this.cancel()this.$emit('handleUploadSuccess')} else {this.number--this.$modal.msgError(res.msg)this.$refs.fileUpload.handleRemove(file)}this.$modal.closeLoading()},downloadFile() {// document.getElementById('templateForm').submit()let dowloadUrl = this.downLoadUrlif (this.fullPathUrl) {dowloadUrl = this.fullPathUrl}downloadGet(dowloadUrl, `${this.fileName}.${this.fileType}`, {})},recordBtnClick() {this.$emit('recordBtnClick')},handleChange(file, fileList) {this.fileList = fileList}}
}
</script>
<style lang="scss" scoped>
.template {text-align: center;color: #1890ff;padding: 10px;span:hover {cursor: pointer;border-bottom: 1px solid #1890ff;}
}.record-btn {position: absolute;right: 18px;top: 18px;
}
</style>
// 通用下载方法,blob形式
export function downloadGet(url, filename, config) {downloadLoadingInstance = Loading.service({text: '正在下载数据,请稍候',spinner: 'el-icon-loading',background: 'rgba(0, 0, 0, 0.7)'})return service.get(url, {headers: { 'Content-Type': 'application/x-www-form-urlencoded' },responseType: 'blob',...config}).then(async(data) => {const isLogin = await blobValidate(data)if (isLogin) {const blob = new Blob([data])saveAs(blob, filename)} else {const resText = await data.text()const rspObj = JSON.parse(resText)const errMsg =errorCode[rspObj.code] || rspObj.msg || errorCode['default']Message.error(errMsg)}downloadLoadingInstance.close()}).catch((r) => {console.error(r)Message.error('下载文件出现错误,请联系管理员!')downloadLoadingInstance.close()})
}
import Cookies from 'js-cookie'const TokenKey = 'Admin-Token'export function getToken() {return Cookies.get(TokenKey)
}

在这里插入图片描述

  • 使用组件
 <FileImportdown-load-url="/client/template/export_template/sec_type"import-url="/client/sec_type/import"file-name="导入持仓"
/>

在这里插入图片描述

2 情景二

需求背景纯前端实现文件导入的情景。例如,一个常规excel文件填写模板,在用户的电脑上,用户上传完后,还可以在预览展示时,在线修改,改完可以下载,也可以将数据给到服务端,但这时,比如这模板数据通常不多,比如是一个团队成员这样的数据,通过文件流的形式传给后端,可能不是很理想,倒不如前端解析传那几行数据就行。
实现思路importExcel.js 导入excel文件

  1. 使用html支持上传标签从本地获取文件,例如type为file的input,el-upload等。
  2. 利用FileReader将文件读取为二进制字符串。
  3. 使用XLSX插件XLSX.read()方法,将二进制字符串转换成excel文件的工作蒲对象workbook(简写成wb)。
  4. 通过XLSX.utils.sheet_to_json()方法,从wb中获取第一张 Sheets表格数据并将其转换为json数据。
  5. 重组json数据生成数组,即是根据自己的定义的列字段名,重新组成符合自己需求的json数据。因为从excel中提取的数据是没有字段名或字段名不符合要求的,而我们需要渲染在页面表格中又确实需要合适的字段名。
/* eslint-disable */
/* 导入excel文件 */
/*** @param file 文件流* @param tableTemplate 要导入的表格模板,一个数组,如:* tableTemplate: ['userCode', 'userName', 'department', 'major', 'position'],其中的值* 为表格的字段名,注意字段的顺序应与实际的导入excel一致。*/
export default function importExcel (file, tableTemplate) {return new Promise((resolve, reject) => {let f = file.raw // 获取文件内容// 通过DOM取文件数据let rABS = false // 是否将文件读取为二进制字符串let reader = new FileReader()FileReader.prototype.readAsBinaryString = function (f) {let binary = ''let rABS = false // 是否将文件读取为二进制字符串let wb // 读取完成的数据let outdatalet reader = new FileReader()reader.onload = function (e) {let bytes = new Uint8Array(reader.result)let length = bytes.byteLengthfor (let i = 0; i < length; i++) {binary += String.fromCharCode(bytes[i])}let XLSX = require('xlsx')if (rABS) {wb = XLSX.read(btoa(binary), { // 手动转化type: 'base64'})} else {wb = XLSX.read(binary, {type: 'binary'})}outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]) // outdata就是表格中的值let arr = []// 下面是数据解析提取逻辑if (tableTemplate.length > 0) {let tempArr = Object.keys(outdata[0])let tempArrNew = []for (let i in tempArr) {for (let k in tableTemplate) {if (i === k) {tempArrNew.push({fieldE: tableTemplate[k], fieldC: tempArr[i]})}}}tempArr = tempArrNewoutdata.map(item => {let obj = {}tempArr.map(temp2 => {obj[temp2.fieldE] = item[temp2.fieldC]})arr.push(obj)})}resolve(arr)}reader.readAsArrayBuffer(f)}if (rABS) {reader.readAsArrayBuffer(f)} else {reader.readAsBinaryString(f)}})
}

相关文章:

  • 【C语言学习疑难杂症】第12期:如何从汇编角度深入理解y = (*--p)++这行代码(易懂版)
  • php的laravel权限问题
  • 【开源】基于Vue+SpringBoot的二手车交易系统
  • MySQL一些常用命令
  • Unity UnityWebRequest 在Mac上使用报CommectionError
  • 不给病毒留空子:保护您的数据免受.mallox勒索病毒威胁
  • 饥荒Mod 开发(二三):显示物品栏详细信息
  • 嵌入式开发——ADC开发
  • Cisco模拟器-企业网络部署
  • hadoop hive spark flink 安装
  • STL——排序算法
  • pytest pytest.ini 设置目录路径,不执行该目录下的case
  • 前端框架前置学习(4) AJAX
  • 基于Unity Editor开发一个技能编辑器可能涉及到的内容
  • C++设计模式:单例模式(饿汉式、懒汉式)
  • [译] React v16.8: 含有Hooks的版本
  • [译]前端离线指南(上)
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【EOS】Cleos基础
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • 77. Combinations
  • cookie和session
  • laravel 用artisan创建自己的模板
  • MobX
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Python_网络编程
  • Redis学习笔记 - pipline(流水线、管道)
  • redis学习笔记(三):列表、集合、有序集合
  • Vue ES6 Jade Scss Webpack Gulp
  • Vue2.0 实现互斥
  • vue-router的history模式发布配置
  • Zsh 开发指南(第十四篇 文件读写)
  • 大快搜索数据爬虫技术实例安装教学篇
  • 关于使用markdown的方法(引自CSDN教程)
  • 力扣(LeetCode)21
  • 前端路由实现-history
  • 提醒我喝水chrome插件开发指南
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 用简单代码看卷积组块发展
  • puppet连载22:define用法
  • Spring Batch JSON 支持
  • 第二十章:异步和文件I/O.(二十三)
  • 积累各种好的链接
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​低代码平台的核心价值与优势
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • # 学号 2017-2018-20172309 《程序设计与数据结构》实验三报告
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (2)Java 简介
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (二)PySpark3:SparkSQL编程
  • (附源码)spring boot建达集团公司平台 毕业设计 141538