最近参与的项目中需要前端做数据导入,在实现过程遇到过一些困难,因此把整个需求实现过程再进行总结一下。
需求:前端读取excel表格数据,将数据发送至后台验证,验证之后需要反馈验证信息,并且需要实时反馈以增强用户体验。
分析:前端读取excel有很多方法,但主要使用的还是js的衍生插件—js-xlsx;由于需要实时反馈,直接提交全部数据至后台会影响用户体验,并且后台也很难给出所有表格数据的错误信息,因此需要将数据逐条提交给后台验证;导入的数据项也需要规定好以免产生excel表格数据读取出错或者失败的情况,因此需要给定一个excel模板供用户下载;除此之外,还需要判断文件的类型、文件是否有数据等等。
实现步骤:
STEP ONE -> 设计导入模板
首先需要跟用户确认需要导入的字段,建立数据表头与数据库字段的对应关系。根据对应关系可以构造一个对象数组,如下图:
上图的required属性是前端判断该字段是否是必须的,可根据业务需要自行添加或删减。根据用户需求自行设计符合业务需求的Excel文件模板,有特殊需求时可在模板文件中说明,但为了让说明文字不污染读取之后的数据,因此需要做数据处理,此处稍候再表。
STEP TWO -> 读取文件
由于目前终端浏览器没有浏览本地文件管理器的权限,因此我们需要一个在页面创建一个文件容器—type为file的input元素。代码如下:
"false" id="uploadFileInput" ref="filElem" type="file" @change="getFile">
其中getFile方法就是读取文件的处理方法。创建完容器之后需要读取容器中的文件,此处需要用到js-xlsx;js-xlsx的引用不再赘述,可通过npm方法也可下载对应文件导入到工程项目中。关键方法getFile的代码如下:
async getFile() { const that = this const inputFile = this.$refs.filElem.files[0] that.importLoading = true that.importDataList = [] this.failCount = 0 if (inputFile) { const fix = inputFile.name.split('.')[1] if (fix === 'xlsx' || fix === 'xls') { const fileReader = new FileReader() fileReader.readAsBinaryString(inputFile) fileReader.onload = await function() { const workbook = XLSX.read(fileReader.result, { type: 'binary' }) let excelData // 遍历每张表读取 for (const sheet in workbook.Sheets) { if (workbook.Sheets.hasOwnProperty(sheet)) { excelData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]) break // 如果要取多张表,就注释这行 } } if (excelData && excelData.length > 0) { excelData.forEach((item, index) => { // 为单条数据初始化导入状态以及错误信息 const singleData = { importStatus: 0, error: '' } // 判断导入数据中多余的表格数据 if (Object.keys(item)[0].indexOf('EMPTY') > -1) { excelData.splice(index, 1) } else { const data = item that.projectMatchData.forEach(temp => { singleData[temp.en] = data[temp.cn] if (temp.required && !data[temp.cn]) { that.failCount++ singleData.importStatus = 1 singleData.error = '缺失必填字段' } }) that.importDataList.push(singleData) if (that.importDataList.length > 0) that.importDataList.sort(that.compare('importStatus')) } that.totalCount = that.importDataList.length }) that.handleImportAdd() } else { that.msgError('excel表格数据为空,请确认后导入!') } } } else { that.msgError('请选择正确格式的excel表格文件!') return } } else { return } },
其中需要注意的有以下几点:
1、读取容器中的文件时,必须指定容器的ref
2、一定要判断导入文件的类型,因为没有限制用户上传文件类型
3、使用fileReader读取并处理文件,并将其转为binary类型
4、获取到文件json格式的数据后,需要判断表头为空的数据
5、根据自定义的数据对应关系可将读取的excel数据各字段信息转换成数据库字段信息,并可校验必填信息。读取方法如下
that.projectMatchData.forEach(temp => {// 此处是建立的中英文对应关系 singleData[temp.en] = data[temp.cn] if (temp.required && !data[temp.cn]) { that.failCount++ singleData.importStatus = 1 singleData.error = '缺失必填字段' }})
6、图片中的数据处理为业务需要,如仅需读取可省略
STEP THREE -> 发送数据验证
将获取的数据逐条发送至后台进行验证,并实时反馈错误信息。具体代码如下:
handleImportAdd() { if (this.importDataList && this.importDataList.length > 0) { this.successCount = 0 this.importDataList.forEach((item, index) => { if (item.importStatus === 0) { // 处理后台必需字段 const statusArr = [] if (item.roomtem) statusArr.push('1') if (item.refrigeration) statusArr.push('2') if (item.freezing) statusArr.push('3') item.sampleStatus = statusArr.join(',') // 发送请求 projectService.addProject(item).then(response => { if (response.code === 200) { this.successCount++ item.importStatus = 2 this.importDataList.sort(this.compare('importStatus')) } else { this.failCount++ item.importStatus = 1 item.error = response.msg // 数组排序方法 this.importDataList.sort(this.compare('importStatus')) } }) this.$forceUpdate() } if (this.importDataList.length === index + 1) this.importLoading = false }) } },
值得注意的是,在界面中实现实时反馈,需要加上
this.$forceUpdate()
此处是实现数据刷新的关键
其他:在选择导入文件的时候,千万记住要将容器清空,不然会出现点击导入按钮之后没有反应的情况,实现代码如下:
// 提前清空上传input中的值const file = document.getElementById('uploadFileInput')file.value = ''// 触发getFile方法this.$refs.filElem.dispatchEvent(new MouseEvent('click'))
在下载导入模板时,有两种方法:一是向后台发送请求下载模板,二是将导入模板作为静态文件保存在工程中;第一种方法很简单,让后台人员写一个接口调用下载即可。第二种方法需要注意以下几点:
1、静态文件需要放在public文件中,这样在工程编译的时候才能够访问
2、需要绝对路径,使用相对路径是访问不到的
实现代码如下:
// 获取当前地址const baseUrl = window.location.href.split('/medical')[0]const elt = document.createElement('a')elt.setAttribute('href', `${baseUrl}/project.xlsx`)elt.setAttribute('download', '项目导入模板.xlsx')elt.style.display = 'none'document.body.appendChild(elt)elt.click()
结语:完成前端导入并不难,注意一些细节比如说清空容器、注意数据的实时刷新、fileReader的特性等等;
(另外祝大家双节快乐!)