知识点回顾:
Object.keys(item) 取出对象所有的属性名组成一个数组
Object.values(item) 取出对象所有的属性的值组成一个数组
const obj={ 'a' :1 , 'b'=2}
Object.keys(obj) = ['a' , 'b' ]
-----------------------------------
Object.values(obj) = [ 1 , 2 ]
一.导入
1.操作步骤
- 后端提供一个excel模板文件
- 软件的使用者填写excel文件
- 通过上传这个文件,实现批量导入功能
2.启动vue-admin-element中引入现成方案,并安装依赖包 yarn add XLSX -s
子组件 模板 vue-element-admin/src/components/UploadExcel/index.vue 中
子组件完整代码
<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">
把excel拖动到这里,或者
<el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">
上传
</el-button>
</div>
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
props: {
beforeUpload: Function, // eslint-disable-line
onSuccess: Function// eslint-disable-line
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData)
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error('Only support uploading one file!')
return
}
const rawFile = files[0] // only use files[0]
if (!this.isExcel(rawFile)) {
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleDragover(e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<style scoped>
.excel-upload-input{
display: none;
z-index: -9999;
}
.drop{
border: 2px dashed #bbb;
width: 600px;
height: 160px;
line-height: 160px;
margin: 0 auto;
font-size: 24px;
border-radius: 5px;
text-align: center;
color: #bbb;
position: relative;
}
</style>
父组件 src\views\excel\upload-excel.vue ,这个页面是引用了上边定义的组件,并传入两个属性:
父传子传递一个导入成功之后的方法,on-success
UploadExcel 为组件标签 , onSuccess为定义的方法,传递到子组件中
<upload-excel
:on-success="handleSuccess"
:before-upload="beforeUpload"
/>
父组件核心代码
methods: {
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (isLt1M) {
return true
}
this.$message({
message: 'Please do not upload files larger than 1m in size.',
type: 'warning'
})
return false
},
handleSuccess({ results, header }) { // 成功调用之后的函数,成功之后导入操作
console.log(results) // 上传之后的各个字段信息
其中参数results 为 上传的各个字段的信息结果
header 为表头
然后得到results 传入数据转换的方法中,
调用接口,
路由跳转
const arr = this.transExcel(results) //进行数据转换
this.getImportEmployee(arr) //调用接口传入参数
注意: getImportEmployee() 方法是在methods中单独声明的
})
3.引入UploadExcel组件并注册为全局组件 从框架 拷贝出文件夹 ---> 全局引入
复制到我们自己的项目 src/components/UploadExcel
下,同时在src/components/index.js将它注册成全局组件
import PageTools from './PageTools'
import UploadExcel from './UploadExcel'
export default {
// 插件的初始化, 插件给你提供的全局的功能, 都可以在这里配置
install(Vue) {
// 进行组件的全局注册
Vue.component('PageTools', PageTools) // 注册工具栏组件
Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件
}
}
4.建立新建一个公共的导入页面,即import路由组件 src/views/import/index.vue
配置路由 不需要根据权限控制,直接定义为静态路由,在src/router/index.js
下的静态路由中添加一个路由
{
path: '/import',
component: Layout,
hidden: true, // 不显示到左侧 不参与遍历
children: [{
path: '',
component: () => import('@/views/import')
}]
}
测试路由结果 浏览器手动输入/import
//点击导入 $router.push('/路由页面')
<el-button type="warning" size="small" @click="$router.push('/import')">导入Excel</el-button>
5.配置上传成功的回调函数 配置成功函数(excel解析成功之后自动调用)
调用接口进行excel上传的重点其实是数据的处理,我们需要按照接口的要求,把excel表格中经过插件处理好的数据处理成后端接口要求的格式,主要包括两个方面
1接口字段的key需要从中文转成英文(字段中文转英文。excel中读入的是姓名
,而后端需要的是username
)
2涉及到时间的字段需要进行严格的时间格式化处理(日期处理。从excel中读入的时间一个number值,而后端需要的是标准日期。)
5.1 中文转英文 views/excel/upload-excel.vue
transExcelHander(results) {
const userRelations = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName',
'聘用形式': 'formOfEmployment'
}
return results.map(item => {
// 取出对象所有的属性名组成一个数组
// Object.keys(item) ===> ['入职日期', '姓名']
const rs = {}
Object.keys(item).forEach(key => {
const enKey = userRelations[key]
rs[enKey] = item[key]
})
return rs
})
}
5.2 日期处理 (utils /index.js)
// 把excel文件中的日期格式的内容转回成标准时间
export function formatExcelDate(numb, format = '/') {
const time = new Date((numb - 1) * 24 * 3600000 + 1)
time.setYear(time.getFullYear() - 70)
const year = time.getFullYear() + ''
const month = time.getMonth() + 1 + ''
const date = time.getDate() - 1 + ''
if (format && format.length === 1) {
return year + format + (month < 10 ? '0' + month : month) + format + (date < 10 ? '0' + date : date)
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}
5.3 调用接口 (完成按钮跳转,导入完成(接口调用)之后,再跳回到原来的页面) api/employment
export function importEmployee(data) {
return request({
url: '/sys/user/batch',
method: 'post',
data
})
}
5.4 引入
import { formatExcelDate } from '@/utils/index' // 日期转换
import { importEmployee } from '@/api/employees' //把excel数据传走
export default {
name: 'Import',
methods: {
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (isLt1M) {
return true
}
this.$message({
message: 'Please do not upload files larger than 1m in size.',
type: 'warning'
})
return false
},
-------------------------这里变了-----------------------------
async handleSuccess({ header, results }) {
try {
console.log('从当前excel文件中读出的内容是', results)
console.log('从当前excel文件 头部', header)
// results [{姓名:xxx}] 变为 results [{ username:xxx}] 转英文
const arr = this.transExcelHander(results)
console.log('转换之后的格式是', arr)
// 调用上传的接口,
const rs = await importEmployee(arr)
console.log('调用上传的接口', rs)
// 上传成功之后,回去刚才的页面
this.$router.back()
this.$message.success('操作成功')
} catch (err) {
this.$message.error(err.message)
}
},
transExcelHander(results) {
const userRelations = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName',
'聘用形式': 'formOfEmployment'
}
-------------------------------这里变了---------------------------------------------
return results.map(item => {
const rs = {}
Object.keys(item).forEach(key => {
const enkey = userRelations[key]
if (enkey === 'timeOfEntry' || enkey === 'correctionTime') {
rs[enkey] = new Date(formatExcelDate(item[key]))
} else {
rs[enkey] = item[key]
}
})
return rs
})
}
}
}
</script>
二. 导出Excel
( js数据 ----> excel文件 )
1.在vue-element-admin中有现成的功能参考 代码核心
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.list
const data = this.formatJson(filterVal, list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: this.filename,
autoWidth: this.autoWidth,
bookType: this.bookType
})
this.downloadLoading = false
})
2.由于js-xlsx
体积还是很大的,导出功能也不是一个非常常用的功能,所以上面在使用的时候建议使用懒加载技术
2.1.插件包位于src/vendor/export2Excel
中,采用的是按需引入的方式
import ( ' @/vender/Export2Excel') .then (excel = >{ })
2.2 Export2Excel
依赖的包有
npm install file-saver script-loader --s
3.给导出按钮添加点击事件
从后台重新获取数据(这样才能确保是最新的)
把数据的格式进行转换(后端给的数据字段名都是英文的),以用来做导出
要实现业务导出功能主要有以下关键点
-
本质:把后端接口的数据导出到excel表格中
-
准备好业务需要的表头数据header
-
准备好业务需要的表格数据data(二维数组)
3.1 表头header
const map = {
'id': '编号',
'password': '密码',
'mobile': '手机号',
'username': '姓名',
'timeOfEntry': '入职日期',
'formOfEmployment': '聘用形式',
'correctionTime': '转正日期',
'workNumber': '工号',
'departmentName': '部门',
'staffPhoto': '头像地址'
}
3.2 data数据
具体的表格数据我们需要通过接口从后端获取回来,难点在于如何把后端返回的数据处理成Export2Excel
插件需求的二维数组格式
代码完成
method:{
transData(arr) {
// 把formOfEmployment中的1改成正式, 2改成非正式
arr.forEach(item => {
if (item.formOfEmployment) {
// 1 --> 正式
// 2 --> 非正式
item.formOfEmployment = hireType[item.formOfEmployment]
}
})
const map = {
'id': '编号',
'password': '密码',
'mobile': '手机号',
'username': '姓名',
'timeOfEntry': '入职日期',
'formOfEmployment': '聘用形式',
'correctionTime': '转正日期',
'workNumber': '工号',
'departmentName': '部门',
'staffPhoto': '头像地址'
}
// 取出数组中第一个元素,来根据key生成对应的中文
const first = arr[0]
const header = Object.keys(first).map(item => map[item])
console.log('表头是', header)
const data = arr.map(obj => {
return Object.values(obj)
})
console.log('数据是', data)
return { header, data }
},
// 把当前页的数据导出来,保存成excel文件
// 有一个现成的工具:/vendor/Export2Excel
hExportExcel() {
// 懒加载的写法
import('@/vendor/Export2Excel').then(async excel => {
// 1. 发请求,获取当前页的数据
const rs = await reqGetEmployeeList(this.params)
console.log('从后端获取的员工列表是', rs.data.rows)
// 2. 修改一下数据格式,以便导出
const { header, data } = this.transData(rs.data.rows)
// 3. 导出
excel.export_json_to_excel({
header, // 表头 必填 ["姓名", "手机号"]
data, // 数据 [ ["小王", "13797979782"], ["小李", "13798779782"]]
filename: 'test-export-excel', // 文件名
autoWidth: true, // 自动设置列的宽度
bookType: 'xlsx' // 文件类型
})
})
},
// 关闭弹层
// 1. 在表单中点击取消(添加成功)
// 2. 弹层上点击 X
hCloseDialog() {
// 相当于是在父组件中去执行子组件中方法
// 去清空 子组件中的表单数据和校验结果
// 子组件.resetForm()
console.log('在父组件中获取子组件的引用', this.$refs.AddEmployee)
this.$refs.AddEmployee.resetForm()
},
formatEmployment(id) {
// console.log('执行一次formatEmployment')
// // 对后端返回的类型的编码进行翻译 ,还原成 中文
// const rs = EmployeeEnum.hireType.find(item => item.id === id)
// return rs.value
return hireType[id]
},
async doDel(id) {
try {
const rs = await delEmployee(id)
console.log('删除之后的结果是', rs)
// 1. 提示
this.$message.success('删除成功')
if (this.employees.length === 1) {
this.params.page--
if (this.params.page === 0) {
this.params.page = 1
}
// this.params.page = (this.params.page - 1) || 1
}
// 2. 重新加载数据
this.loadEmployeeList()
} catch (err) {
this.$message.error(err.message)
}
}
}