Excel 导入原理与实现
安装
bash
npm i xlsx@0.17.0 -S
业务原理
注意
- 点击
excel
导入 按钮进入excel 导入页面
- 页面提供了两种导入形式
- 点击按钮上传
excel
- 把
excel
拖入指定区域
- 点击按钮上传
- 选中文件,进行两步操作
- 解析
excel
数据 - 上传解析之后的数据
- 解析
- 上传成功之后,返回
员工管理(用户列表)
页面,进行数据展示
所以根据这个业务我们可以看出,整个 excel
导入核心的原理部分在于 选中文件之后,上传成功之前 的操作,即:
- 解析
excel
数据(最重要) - 上传解析之后的数据
- 根据上面所说,整个的实现流程我们也可以很轻松得出:
- 创建
excel
导入页面 - 点击
excel
导入按钮,进入该页面 - 该页面提供两种文件导入形式
- 选中文件之后,解析
excel
数据(核心) - 上传解析之后的数据
- 返回 员工管理(用户列表) 页面
1. 创建 excel
导入页面
- src/pages/import 页面
创建导入组件
src/components/UploadExcel.vue
en.js
bash
uploadExcel: {
upload: 'upload',
drop: 'drop'
}
- cn.js
bash
uploadExcel: {
upload: '上传',
drop: '拖拽'
}
封装 excel.js
- src/utils/excel.js
js
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;
};
export const isExcel = (file) => {
return /\.(xlsx|xls|csv)$/.test(file.name);
};
封装 hooks
- src/hooks/admin/useButtonExcel.js
js
/**
* @Author: jsopy
* @Date: 2025-01-19 17:05:32
* @LastEditTime: 2025-01-19 17:11:37
* @FilePath: /adminvue/src/hooks/admin/useButtonExcel.js
* @Description:
* @
*/
import XLSX from "xlsx";
import { getHeaderRow } from "@/utils/excel";
export const useButtonExcel = (props) => {
/**
* 点击上传触发
*/
const loading = ref(false);
const excelUploadInput = ref(null);
const handleUpload = () => {
excelUploadInput.value.click();
};
const handleChange = (e) => {
const files = e.target.files;
const rawFile = files[0]; // only use files[0]
if (!rawFile) return;
upload(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. 解析数据体
const results = XLSX.utils.sheet_to_json(worksheet);
// 7. 传入解析之后的数据
generateData({ header, results });
// 8. loading 处理
loading.value = false;
// 9. 异步完成
resolve();
};
// 启动读取指定的 Blob 或 File 内容
reader.readAsArrayBuffer(rawFile);
});
};
/**
* 根据导入内容,生成数据
*/
const generateData = (excelData) => {
props.onSuccess && props.onSuccess(excelData);
};
return {
loading,
excelUploadInput,
handleUpload,
handleChange,
upload,
readerData,
generateData,
};
};
- src/hooks/admin/useDrag.js
js
import { getHeaderRow, isExcel } from "@/utils/excel";
import { ElMessage } from "element-plus";
export const useDrag = (loading, upload) => {
/**
* 拖拽文本释放时触发
*/
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";
};
return {
handleDrop,
handleDragover,
};
};
封装组件
vue
<template>
<div class="upload-excel">
<div class="btn-upload">
<el-button
:loading="loading"
type="primary"
@click="handleUpload"
style="padding-left: 15px; padding-right: 15px"
>
{{ $t("message.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"
>
<el-icon size="24" color="black" style="margin-left: 160px">
<Upload />
</el-icon>
<span>{{ $t("message.uploadExcel.drop") }}</span>
</div>
</div>
</template>
<script setup>
import { useButtonExcel } from "@/hooks/admin/useButtonExcel";
import { useDrag } from "@/hooks/admin/useDrag.js";
const props = defineProps({
// 上传前回调
beforeUpload: Function,
// 成功回调
onSuccess: Function,
});
const {
loading,
excelUploadInput,
handleUpload,
handleChange,
upload,
readerData,
generateData,
} = useButtonExcel(props);
const { handleDrop, handleDragover } = useDrag(loading, upload);
</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>
调用
vue
<template>
<div>
<UploadExcel :onSuccess="onSuccess"></UploadExcel>
</div>
</template>
<script setup>
/**
* 数据解析成功之后的回调
*/
const onSuccess = (excelData) => {
console.log(excelData);
};
</script>
<style lang="scss" scoped></style>