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-button
plain
icon="el-icon-upload2"
type="primary"
size="mini"
:disabled="disabled"
@click="importFile"
>{{ $t('import') }}</el-button>
<el-dialog
v-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-upload
ref="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 = false
this.$emit('importSuccess')
},
// 上传前校检格式和大小
handleBeforeUpload(file) {
// 校检文件类型
if (this.fileType) {
const fileName = file.name.split('.')
const fileExt = fileName[fileName.length - 1]
const isTypeOk = this.fileType.indexOf(fileExt) >= 0
if (!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.fileSize
if (!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.downLoadUrl
if (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)
}
- 使用组件
<FileImport
down-load-url="/client/template/export_template/sec_type"
import-url="/client/sec_type/import"
file-name="导入持仓"
/>
2 情景二
需求背景:纯前端
实现文件导入的情景。例如,一个常规excel文件填写模板,在用户的电脑上,用户上传完后,还可以在预览展示时,在线修改,改完可以下载,也可以将数据给到服务端,但这时,比如这模板数据通常不多,比如是一个团队成员这样的数据,通过文件流的形式传给后端,可能不是很理想,倒不如前端解析传那几行数据就行。
实现思路:importExcel.js
导入excel文件
- 使用html支持上传标签从本地获取文件,例如type为file的input,el-upload等。
- 利用
FileReader
将文件读取为二进制字符串。 - 使用
XLSX插件
的XLSX.read()
方法,将二进制字符串转换成excel文件的工作蒲对象workbook(简写成wb)。 - 通过
XLSX.utils.sheet_to_json()
方法,从wb中获取第一张 Sheets表格数据并将其转换为json数据。 - 重组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 outdata
let reader = new FileReader()
reader.onload = function (e) {
let bytes = new Uint8Array(reader.result)
let length = bytes.byteLength
for (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 = tempArrNew
outdata.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)
}
})
}