Excel 导入处理方案
# excel 导入原理与实现分析
业务流程:
- 点击 excel 导入 按钮进入 excel 导入页面
- 页面提供了两种导入形式
- 点击按钮上传
excel
- 把
excel
拖入指定区域
- 点击按钮上传
- 选中文件,进行两步操作
- 解析
excel
数据 - 上传解析之后的数据
- 解析
- 上传成功之后,返回 员工管理(用户列表) 页面,进行数据展示
核心原理: 选中文件之后,上传成功之前 的操作,即:
- 解析
excel
数据(最重要) - 上传解析之后的数据
实现流程:
- 创建
excel
导入页面 - 点击
excel
导入按钮,进入该页面 - 该页面提供两种文件导入形式
- 选中文件之后,解析
excel
数据(核心) - 上传解析之后的数据
- 返回 员工管理(用户列表) 页面
# 业务落地:提供两种文件导入形式
导入 excel
页面是 views/import/index
,在 user-manage
中的按钮处完成页面跳转:
<el-button type="primary" @click="onImportExcelClick">
{{ $t('msg.excel.importExcel') }}</el-button
>
const router = useRouter()
/**
* excel 导入点击事件
*/
const onImportExcelClick = () => {
router.push('/user/import')
}
2
3
4
5
6
7
8
9
10
11
实现 提供两种文件导入形式
创建
components/UploadExcel
组件,用于处理上传excel
相关的问题在
import
中导入该组件<template> <upload-excel></upload-excel> </template> <script setup> import UploadExcel from '@/components/UploadExcel' </script>
1
2
3
4
5
6
7整个
UploadExcel
组件的内容可以分成两部分:- 样式
- 逻辑
那么首先我们先处理样式内容
<template> <div class="upload-excel"> <div class="btn-upload"> <el-button :loading="loading" type="primary" @click="handleUpload"> {{ $t('msg.uploadExcel.upload') }} </el-button> </div> <--选择文件--> <input ref="excelUploadInput" class="excel-upload-input" type="file" accept=".xlsx, .xls" <--选择的文件类型--> @change="handleChange" /> <--拖拽文件--> <!-- https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API --> <div class="drop" @drop.stop.prevent="handleDrop" @dragover.stop.prevent="handleDragover" @dragenter.stop.prevent="handleDragover" > <i class="el-icon-upload" /> <span>{{ $t('msg.uploadExcel.drop') }}</span> </div> </div> </template> <script setup> import {} from 'vue' </script> <style lang="scss" scoped> .upload-excel { display: flex; justify-content: center; margin-top: 100px; .excel-upload-input { display: none; z-index: -9999; } .btn-upload, .drop { border: 1px dashed #bbb; width: 350px; height: 160px; text-align: center; line-height: 160px; } .drop { line-height: 60px; display: flex; flex-direction: column; justify-content: center; color: #bbb; i { font-size: 60px; display: block; } } } </style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 业务落地:文件选择之后的数据解析处理
最核心内容: 选中文件之后,解析 excel
数据
导入形式
- 文件选择(选择隐藏域)导入
- 文件拖拽导入
# 文件选择导入
解析
excel
数据我们需要使用 xlsx (opens new window) ,所以我们需要先下载它npm i [email protected]
1
xlsx (opens new window) 安装完成之后,接下来我们就可以来去实现对应代码了:
<script setup>
import XLSX from 'xlsx'
import { defineProps, ref } from 'vue'
import { getHeaderRow } from './utils'
const props = defineProps({
// 上传前回调
beforeUpload: Function,
// 上传成功回调
onSuccess: Function
})
/**
* 点击上传触发
*/
const loading = ref(false)
const excelUploadInput = ref(null)
const handleUpload = () => {
// 触发选择文件功能
excelUploadInput.value.click()
}
// 选择文件后触发的事件
const handleChange = e => {
// 获取到一个event对象,通过e.target.files形式获取用户选中的文件
const files = e.target.files
const rawFile = files[0] // only use files[0] 只能选中一个文件
// 如果当前的文件不存在的话 直接返回
if (!rawFile) return
// 触发上传事件
upload(rawFile)
}
/**
* 触发上传事件
* rawFile -- 得到的文件
*/
const upload = rawFile => {
// 先将这文件置空
excelUploadInput.value.value = null
// 如果没有指定上传前回调的话
if (!props.beforeUpload) {
readerData(rawFile)
return
}
// 如果指定了上传前回调,那么只有返回 true 才会执行后续操作
const before = props.beforeUpload(rawFile)
if (before) {
// 解析文件
readerData(rawFile)
}
}
/**
* 读取数据(异步) 解析文件
*/
const readerData = rawFile => {
loading.value = true
return new Promise((resolve, reject) => {
// https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
// 首先读取文件
const reader = new FileReader()
// 该事件在读取操作完成时触发(已经读取)
// https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader/onload
reader.onload = e => {
// 1. 获取解析到的数据
const data = e.target.result
// 2. 利用 XLSX 对数据进行解析(传递配置对象:解析类型)
const workbook = XLSX.read(data, { type: 'array' })
// 3. 获取第一张表格(工作簿)名称
const firstSheetName = workbook.SheetNames[0]
// 4. 只读取 Sheet1(第一张表格)的数据(传入名称)
const worksheet = workbook.Sheets[firstSheetName]
// 5. 解析数据表头(通用方式)
const header = getHeaderRow(worksheet)
// 6. 解析数据体(XLSX提供的方法)
const results = XLSX.utils.sheet_to_json(worksheet)
// 7. 传入解析之后的数据(将数据头和数据体 传递到props中的onSuccess里面)
generateData({ header, results })
// 8. loading 处理
loading.value = false
// 9. 异步完成(在import文件中获取第7步传递的方法)(子组件的方法,父组件获取)
resolve()
}
// 用 reader 对象中的方法读取传递过来的文件
// 启动读取指定的 Blob 或 File 内容
reader.readAsArrayBuffer(rawFile)
})
}
/**
* 根据导入内容,生成数据
*/
const generateData = excelData => {
props.onSuccess && props.onSuccess(excelData)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
在UploadExcel/utils
中,getHeaderRow
为 xlsx
解析表头数据的通用方法,直接使用即可
import XLSX from 'xlsx'
/**
* 获取表头(通用方式)
*/
export const 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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在 import
组件中传入 onSuccess
事件,获取解析成功之后的 excel
数据
<template>
<upload-excel :onSuccess="onSuccess"></upload-excel>
</template>
<script setup>
import UploadExcel from '@/components/UploadExcel'
/**
* 数据解析成功之后的回调
*/
const onSuccess = excelData => {
console.log(excelData)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 业务落地:文件拖入之后的数据解析处理
想要了解 文件拖入,那么我们就必须要先能够了解 HTML_Drag_and_Drop(HTML 拖放 API) (opens new window) 事件,我们这里主要使用到其中三个事件:
- drop (en-US) (opens new window):当元素或选中的文本在可释放目标上被释放时触发
- dragover (en-US) (opens new window):当元素或选中的文本被拖到一个可释放目标上时触发
- dragenter (en-US) (opens new window):当拖拽元素或选中的文本到一个可释放目标时触发
笔记
drop:把Excel拖拽到文件区域,松开鼠标的时候触发
dragover:把Excel拖拽到文件区域的时候就会触发(每 100 毫秒触发一次)。
dragenter:把Excel拖拽到文件区域的时候就会触发
<script setup>
...
import { getHeaderRow, isExcel } from './utils'
import { ElMessage } from 'element-plus'
...
/**
* 拖拽文本释放时触发
*/
const handleDrop = e => {
// 上传中跳过
if (loading.value) return
const files = e.dataTransfer.files
// 判断有没有文件
if (files.length !== 1) {
ElMessage.error('必须要有一个文件')
return
}
const rawFile = files[0]
// 判断文件类型
if (!isExcel(rawFile)) {
ElMessage.error('文件必须是 .xlsx, .xls, .csv 格式')
return false
}
// 触发上传事件
upload(rawFile)
}
/**
* 拖拽悬停时触发
*/
const handleDragover = e => {
// https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer/dropEffect
// 在新位置生成源项的副本
e.dataTransfer.dropEffect = 'copy'
}
。。。
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
在 UploadExcel/utils
中生成 isExcel
方法
export const isExcel = file => {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
2
3
# 业务落地:传递解析后的 excel 数据
接下来就可以实现对应的数据上传,完成 excel
导入功能了
定义
api/user-manage
上传接口/** * 批量导入 */ export const userBatchImport = (data) => { return request({ url: '/user-manage/batch/import', method: 'POST', data }) }
1
2
3
4
5
6
7
8
9
10在
onSuccess
中调用接口上传数据,但是此处大家要注意两点内容:header
头不需要上传results
中key
为中文,我们必须要按照接口要求进行上传(接口的key是英文)
所以我们需要处理
results
中的数据结构创建
import/utils
文件/** * 导入数据对应表 */ export const USER_RELATIONS = { 姓名: 'username', 联系方式: 'mobile', 角色: 'role', 开通时间: 'openTime' }
1
2
3
4
5
6
7
8
9在
import/index.vue
中,创建数据解析方法,生成新数组import { USER_RELATIONS } from '@/views/import/utils' /** * 筛选数据 */ const generateData = results => { const arr = [] results.forEach(item => { const userInfo = {} Object.keys(item).forEach(key => { // 通过USER_RELATIONS[key]得到'username'(即英文的key值),再通过userInfo[USER_RELATIONS[key]]获得item的中文key对应的value userInfo[USER_RELATIONS[key]] = item[key] }) arr.push(userInfo) }) return arr }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17在
import/index.vue
中,完成数据上传即可import { userBatchImport } from '@/api/user-manage' import { ElMessage } from 'element-plus' import { useI18n } from 'vue-i18n' import { useRouter } from 'vue-router' const i18n = useI18n() const router = useRouter() /** * 数据解析成功之后的回调 */ const onSuccess = async ({ header, results }) => { // 调用generateData(results)将results传递进来,得到的是可以更新的数据 const updateData = generateData(results) // 调用上传的方法,将解析后的数据传递过去 await userBatchImport(updateData) ElMessage.success({ // 代表有多少员工导入成功 message: results.length + i18n.t('msg.excel.importSuccess'), type: 'success' }) router.push('/user/manage') }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 业务落地:处理剩余 bug
截止到目前整个 excel
上传我们就已经处理完成了,只不过目前还存在两个小 bug 需要处理:
- 上传之后的时间解析错误
- 返回用户列表之后,数据不会自动刷新
上传之后的时间解析错误:
问题原因: excel 导入解析时间会出现错误
处理方案:是一个固定方案,只需要进行固定的时间解析处理即可
在
import/utils
中新增事件处理方法(固定方式直接使用即可)/** * 解析 excel 导入的时间格式 */ export const formatDate = (numb) => { 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 + '' return ( year + '-' + (month < 10 ? '0' + month : month) + '-' + (date < 10 ? '0' + date : date) ) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17在
import/index.vue
中的generateData
中针对openTime
进行单独处理/** * 筛选数据 */ const generateData = results => { ... Object.keys(item).forEach(key => { if (USER_RELATIONS[key] === 'openTime') { userInfo[USER_RELATIONS[key]] = formatDate(item[key]) return } userInfo[USER_RELATIONS[key]] = item[key] }) ... }) return arr }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
返回用户列表之后,数据不会自动刷新:
问题原因:appmain
中使用 keepAlive
进行了组件缓存。
解决方案:监听 onActivated (opens new window) 事件,重新获取数据即可
在 user-manage
中:
import { ref, onActivated } from 'vue'
// 处理导入用户后数据不重新加载的问题
// 当user-manage被重新激活的时候,重新获取
onActivated(getListData)
2
3
4
5
# excel 导入功能总结
我们整体的流程:
- 创建
excel
导入页面 - 点击
excel
导入按钮,进入该页面 - 该页面提供两种文件导入形式
- 选中文件之后,解析
excel
数据(核心) - 上传解析之后的数据
- 返回 员工管理(用户列表) 页面
游离于这些流程之外的,还包括额外的两个小 bug 的处理,特别是 excel
的时间格式问题, 大家要格外注意,因为这是一个必然会出现的错误,当然处理方案也是固定的。
# 辅助业务之用户删除
在
api/user-manage
中指定删除接口/** * 删除指定数据 */ export const deleteUser = (id) => { return request({ url: `/user-manage/detele/${id}` }) }
1
2
3
4
5
6
7
8在
views/user-manage
中调用删除接口接口<el-button type="danger" size="mini" @click="onRemoveClick(row)">{{ $t('msg.excel.remove') }}</el-button>
1
2
3/** * 删除按钮点击事件 */ const i18n = useI18n() const onRemoveClick = row => { ElMessageBox.confirm( i18n.t('msg.excel.dialogTitle1') + row.username + i18n.t('msg.excel.dialogTitle2'), { type: 'warning' } ).then(async () => { await deleteUser(row._id) ElMessage.success(i18n.t('msg.excel.removeSuccess')) // 重新渲染数据 getListData() }) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# excel 导出原理与实现分析
业务逻辑:
- 点击
excel
导出按钮 - 展示
dialog
弹出层 - 确定导出的
excel
文件名称 - 点击导出按钮
- 获取 所有用户列表数据
- 将
json
结构数据转化为excel
数据,并下载
将 json
结构数据转化为 excel
数据,并下载
实现方案:
- 创建
excel
导出弹出层 - 处理弹出层相关的业务
- 点击导出按钮,将
json
结构数据转化为excel
数据,并下载(核心)
# 业务落地:Export2Excel 组件
创建
excel
弹出层组件views/user-manage/components/Export2Excel
<template> <el-dialog :title="$t('msg.excel.title')" :model-value="modelValue" @close="closed" width="30%" > <el-input :placeholder="$t('msg.excel.placeholder')" ></el-input> <template #footer> <span class="dialog-footer"> <el-button @click="closed">{{ $t('msg.excel.close') }}</el-button> <el-button type="primary" @click="onConfirm">{{ $t('msg.excel.confirm') }}</el-button> </span> </template> </el-dialog> </template> <script setup> import { defineProps, defineEmits } from 'vue' defineProps({ modelValue: { type: Boolean, required: true } }) const emits = defineEmits(['update:modelValue']) /** * 导出按钮点击事件 */ const onConfirm = async () => { closed() } /** * 关闭 */ const closed = () => { emits('update:modelValue', false) } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46在
user-manage
中进行导入dialog
组件指定
excel
按钮 点击事件<el-button type="success" @click="onToExcelClick"> {{ $t('msg.excel.exportExcel') }} </el-button>
1
2
3导入
ExportToExcel
组件<export-to-excel v-model="exportToExcelVisible"></export-to-excel> import ExportToExcel from './components/Export2Excel.vue'
1
2点击事件处理函数
/** * excel 导出点击事件 */ const exportToExcelVisible = ref(false) const onToExcelClick = () => { exportToExcelVisible.value = true }
1
2
3
4
5
6
7
# 业务落地:导出前置业务处理
那么这一小节我们来处理一些实现 excel
导出时的前置任务,具体有:
- 指定
input
默认导出文件名称 - 定义 获取全部用户 列表接口,并调用
那么下面我们先来处理第一步:指定 input
默认导出文件名称
在
Export2Excel
中,指定input
的双向绑定<el-input v-model="excelName" :placeholder="$t('msg.excel.placeholder')" ></el-input>
1
2
3
4在
Export2Excel
中,指定默认文件名const i18n = useI18n() let exportDefaultName = i18n.t('msg.excel.defaultName') const excelName = ref('') excelName.value = exportDefaultName watchSwitchLang(() => { exportDefaultName = i18n.t('msg.excel.defaultName') excelName.value = exportDefaultName })
1
2
3
4
5
6
7
8
定义获取全部用户列表接口,并调用:
在
user-manage
中定义获取全部数据接口/** * 获取所有用户列表数据 */ export const getUserManageAllList = () => { return request({ url: '/user-manage/all-list' }) }
1
2
3
4
5
6
7
8在
Export2Excel
中,调用接口数据,并指定loading
<el-button type="primary" @click="onConfirm" :loading="loading">{{ $t('msg.excel.confirm') }}</el-button>
1
2
3import { getUserManageAllList } from '@/api/user-manage' /** * 导出按钮点击事件 */ const loading = ref(false) const onConfirm = async () => { loading.value = true const allUser = (await getUserManageAllList()).list closed() } /** * 关闭 */ const closed = () => { loading.value = false emits('update:modelValue', false) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 业务落地:实现 excel 导出逻辑
业务逻辑:
- 将
json
结构数据转化为excel
数据 - 下载对应的
excel
数据
对于这两步的逻辑而言,最复杂的莫过于 将
json
结构数据转化为excel
数据 这一步的功能,不过万幸的是对于该操作的逻辑是 通用处理逻辑,搜索 Export2Excel 我们可以得到巨多的解决方案,所以此处我们 没有必要 手写对应的转换逻辑
该转化逻辑我已经把它放置到 课程资料/Export2Excel.js
文件中,大家可以直接把该代码复制到 utils
文件夹下
PS:如果大家想要了解该代码的话,那么对应的业务逻辑我们也已经全部标出,大家可以直接查看
点击查看
/* eslint-disable */
import { saveAs } from 'file-saver'
import XLSX from 'xlsx'
function datenum(v, date1904) {
if (date1904) v += 1462
var epoch = Date.parse(v)
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {}
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
}
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R
if (range.s.c > C) range.s.c = C
if (range.e.r < R) range.e.r = R
if (range.e.c < C) range.e.c = C
var cell = {
v: data[R][C]
}
if (cell.v == null) continue
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
})
if (typeof cell.v === 'number') cell.t = 'n'
else if (typeof cell.v === 'boolean') cell.t = 'b'
else if (cell.v instanceof Date) {
cell.t = 'n'
cell.z = XLSX.SSF._table[14]
cell.v = datenum(cell.v)
} else cell.t = 's'
ws[cell_ref] = cell
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)
return ws
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook()
this.SheetNames = []
this.Sheets = {}
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
return buf
}
export const export_json_to_excel = ({
multiHeader = [],
header,
data,
filename,
merges = [],
autoWidth = true,
bookType = 'xlsx'
} = {}) => {
// 1. 设置文件名称
filename = filename || 'excel-list'
// 2. 把数据解析为数组,并把表头添加到数组的头部
data = [...data]
data.unshift(header)
// 3. 解析多表头,把多表头的数据添加到数组头部(二维数组)
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i])
}
// 4. 设置 Excel 表工作簿(第一张表格)名称
var ws_name = 'SheetJS'
// 5. 生成工作簿对象
var wb = new Workbook()
// 6. 将 data 数组(json格式)转化为 Excel 数据格式
var ws = sheet_from_array_of_arrays(data)
// 7. 合并单元格相关(['A1:A2', 'B1:D1', 'E1:E2'])
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = []
merges.forEach((item) => {
ws['!merges'].push(XLSX.utils.decode_range(item))
})
}
// 8. 单元格宽度相关
if (autoWidth) {
/*设置 worksheet 每列的最大宽度*/
const colWidth = data.map((row) =>
row.map((val) => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
wch: 10
}
} else if (val.toString().charCodeAt(0) > 255) {
/*再判断是否为中文*/
return {
wch: val.toString().length * 2
}
} else {
return {
wch: val.toString().length
}
}
})
)
/*以第一行为初始值*/
let result = colWidth[0]
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch']
}
}
}
ws['!cols'] = result
}
// 9. 添加工作表(解析后的 excel 数据)到工作簿
wb.SheetNames.push(ws_name)
wb.Sheets[ws_name] = ws
// 10. 写入数据
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
})
// 11. 下载数据
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
}),
`${filename}.${bookType}`
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
那么有了 Export2Excel.js
的代码之后 ,还需要导入两个依赖库:
xlsx (opens new window) (已下载):
excel
解析器和编译器file-saver (opens new window):文件下载工具,通过
npm i [email protected]
下载动态导入
Export2Excel.js
// 导入工具包 const excel = await import('@/utils/Export2Excel')
1
2因为从服务端获取到的为
json 数组对象
结构,但是导出时的数据需要为 二维数组,所以我们需要有一个方法来把json
结构转化为 二维数组创建转化方法
创建
views/user-manage/components/Export2ExcelConstants.js
中英文对照表/** * 导入数据对应表 */ export const USER_RELATIONS = { 姓名: 'username', 联系方式: 'mobile', 角色: 'role', 开通时间: 'openTime' }
1
2
3
4
5
6
7
8
9在
Export2Excel
中,创建数据解析方法// 该方法负责将数组转化成二维数组 const formatJson = (headers, rows) => { // 首先遍历数组 // [{ username: '张三'},{},{}] => [[’张三'],[],[]] // 首先遍历一维数组 return rows.map(item => { // 再遍历二维的,和对照表headers return Object.keys(headers).map(key => { // 角色特殊处理 if (headers[key] === 'role') { const roles = item[headers[key]] return JSON.stringify(roles.map(role => role.title)) } // item里面的headers对应的key return item[headers[key]] }) }) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在
Export2Excel
中,调用该方法,获取导出的二维数组数据import { USER_RELATIONS } from './Export2ExcelConstants' const data = formatJson(USER_RELATIONS, allUser)
1
2
3在
Export2Excel
中,调用export_json_to_excel
方法,完成excel
导出excel.export_json_to_excel({ // excel 表头 header: Object.keys(USER_RELATIONS), // excel 数据(二维数组结构) data, // 文件名称 filename: excelName.value || exportDefaultName, // 是否自动列宽 autoWidth: true, // 文件类型 bookType: 'xlsx' })
1
2
3
4
5
6
7
8
9
10
11
12
# 业务落地:excel 导出时的时间逻辑处理
因为服务端返回的 openTime
格式问题,所以我们需要在 excel
导出时对时间格式进行单独处理
导入时间格式处理工具
import { dateFormat } from '@/filters'
1对时间格式进行单独处理
// 时间特殊处理 if (headers[key] === 'openTime') { return dateFormat(item[headers[key]]) }
1
2
3
4
# excel 导出功能总结
整个 excel
导出遵循以下业务逻辑:
- 创建
excel
导出弹出层 - 处理弹出层相关的业务
- 点击导出按钮,将
json
结构数据转化为excel
数据json
数据转化为 二维数组- 时间处理
- 角色数组处理
- 下载
excel
数据
将 json
结构数据转化为 excel
数据 部分因为有通用的实现方式