前几天因为业务需求,所维护的而后台中出现了大量关于上传下载Excel的操作。因为我们的后台是基于Vue,并且是在 vue-element-admin 的基础上结合实际需求开发而来。vue-element-admin 中也有一些相关操作 Excel 的示例,都十分清晰明了,很快就能上手。而我们当然首要参考了 vue-element-admin 的操作方式,如上传 Excel:
<template><div><input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick"><div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">Drop excel file here or<el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">Browse</el-button></div></div></template><script>import XLSX from 'xlsx'///</script>
在上传 Excel 中,vue-element-admin 的做法是,点击上传按钮时触发事先放在组件内的 input 的 click ,在通过监听 input 的 change 事件,获取读取到的 Excel 文档。事实上,对文件的处理也只能这样了,读取到 file 后通过 xlsx 工具库,对 file 进行 JSON 化处理再发给后端。(不要问我为什么这些事情要前端来做,问就是我乐意)。
刚刚说到这样做没得啥子问题,但是在实际项目中,尤其是后台管理系统,。几乎很多页面几乎都是表格、查询、批量操作等。最开始的时候,我就是直接把 input + 按钮 放在业务页面,但是随着项目慢慢变大,这样就显得有些臃肿了。不仅增加了代码量,也不利于维护。于是我把这个功能封装成了一个组将,就像 vue-element-admin 就类似那样。但是后来随着项目越来越来,越来越多的页面需要 Excel 操作,我对这种频繁引入此组件的方式也开始不厌其法。这个时候其实就有两种选择了:将组件注册为全局组件,或者使用自定义指令达到相同的效果。正如标题写的那样,我选择了后者。
因为 Excel 这个需求,体现上无非就是:点击了某个按钮,弹出文件选择,用户选择 Excel 后直接读取。因此,直接参与在业务中的只有按钮,至于用户在选择 Excel 后,我需要把这部操作封装一下,因为逐步操作和业务没有直接关系。因此,我需要实现一个针对选择 Excel 按钮的自定义指令:
// 注册全局自定义快速读取 excel `v-read-excel`Vue.directive('read-excel', {inserted: (el, {value }) => {const id = Date.now()const input = document.createElement('input')el['read-excel-id'] = idinput.id = idinput.type = 'file'input.accept = '.xlsx, .xls'input.onchange = ({target: {files: [excel] }}) => {if(!excel) return const XLSX = require('xlsx')const reader = new FileReader()reader.onload = async({target: {result }}) => {const workbook = XLSX.read(result, {type: 'array' })value && value(XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]))}reader.readAsArrayBuffer(excel)}input.style.display = 'none'document.body.appendChild(input)el.addEventListener('click', () => document.getElementById(id).click())},unbind: el => document.getElementById(el['read-excel-id']).remove()})
使用起来无需引入组件,更无需 input ,只需要这样:
<template><div class="PageUploadExcel"><el-button v-read-excel="upload" type="primary">上传Excel</el-button></div></template><script>export default {name: 'PageUploadExcel',data() {return {list: []}},methods: {upload(list) {this.list = list}}}</script>
原理很简单:在被绑定按钮插入文档后,给这个按钮配套一个 input 放在 body 里,点击按钮就会触发 input ... 在被绑定按钮被移除文档同时也删除掉自己所对应的 input。
这就是关于上传 Excel 的自定义指令封装操作。
至于下载,也是参考 vue-element-admin 的做法,不过也是为了使用简便,就直接把方法挂在 Vue 原型上了:
Vue.prototype.$excel = function(list, name) {!list.length ? list = [{'暂无数据': '' }] : ''import('@/utils/Export2Excel').then(excel => {excel.export_json_to_excel({header: Object.keys(list[0]),data: list.map(listItem => Object.keys(list[0]).map(j => listItem[j])),filename: name || '下载Excel',bookType: 'xlsx'})})}
这个用起来更简单:
<template><div class="PageDownloadExcel"><el-button type="primary" @click="download">下载Excel</el-button></div></template><script>export default {name: 'PageDownloadExcel',data() {return {list: [{'姓名': '张三', '年龄': 18, '爱好': '旅游' },{'姓名': '李四', '年龄': 19, '爱好': '游泳' },{'姓名': '王五', '年龄': 20, '爱好': '吃鸡' }]}},methods: {download() {this.$excel(this.list, '数据表格')}}}</script>
可能你也注意到了,我在这里使用的数据是:
[{'姓名': '张三', '年龄': 18, '爱好': '旅游' },{'姓名': '李四', '年龄': 19, '爱好': '游泳' },{'姓名': '王五', '年龄': 20, '爱好': '吃鸡' }]
是的,key - value 都是直接用来展示的汉字。这样做,除了方便外,也可以实现后端实时控制导出的字段,我司目前使用的就是这种方式。当然,这个要看具体的业务需求了。