Vue3+TypeScript+ Element Plus 从Excel文件导入数据,无后端(点击按钮,选择Excel文件,由前端解析数据)
点击按钮,选择Excel文件,由前端解析数据,实现从Excel文件导入数据。
在 Vue 3 + TypeScript + Element Plus 中实现文件导入功能,可以通过以下步骤完成:
1. 安装依赖
bash
复制
下载
npm install xlsx # 用于解析Excel文件 npm install @types/xlsx -D # TypeScript类型声明
2. 组件实现
vue
复制
下载
<template>
<div>
<!-- 隐藏的原生文件输入 -->
<input
type="file"
ref="fileInput"
@change="handleFileChange"
accept=".xlsx, .xls, .csv"
style="display: none"
>
<!-- Element Plus 按钮触发文件选择 -->
<el-button
type="primary"
@click="triggerFileInput"
>
<el-icon><upload /></el-icon>
导入文件
</el-button>
<!-- 显示导入数据 -->
<el-table :data="tableData" v-if="tableData.length">
<el-table-column
v-for="(header, index) in tableHeaders"
:key="index"
:prop="header"
:label="header"
/>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import * as XLSX from 'xlsx';
import type { UploadInstance } from 'element-plus';
import { ElMessage } from 'element-plus';
// 类型定义
interface TableData {
[key: string]: any;
}
// 响应式数据
const fileInput = ref<HTMLInputElement | null>(null);
const tableData = ref<TableData[]>([]);
const tableHeaders = ref<string[]>([]);
// 触发文件选择
const triggerFileInput = () => {
if (fileInput.value) {
fileInput.value.value = ''; // 重置以允许重复选择相同文件
fileInput.value.click();
}
};
// 处理文件选择
const handleFileChange = (e: Event) => {
const input = e.target as HTMLInputElement;
if (!input.files?.length) return;
const file = input.files[0];
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = e.target?.result as ArrayBuffer;
processExcel(data);
} catch (error) {
console.error('文件解析失败:', error);
ElMessage.error('文件解析失败');
}
};
reader.readAsArrayBuffer(file);
};
// 处理Excel数据
const processExcel = (data: ArrayBuffer) => {
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// 转换为JSON
const jsonData: TableData[] = XLSX.utils.sheet_to_json(worksheet);
if (jsonData.length > 0) {
// 获取表头
tableHeaders.value = Object.keys(jsonData[0]);
tableData.value = jsonData;
ElMessage.success(`成功导入 ${jsonData.length} 条数据`);
} else {
ElMessage.warning('未找到有效数据');
}
};
</script>
3. 功能说明
-
文件选择触发:
-
隐藏原生
<input type="file">元素 -
通过Element Plus按钮触发文件选择
-
-
文件处理流程:
-
使用
FileReader读取文件内容 -
通过
xlsx库解析Excel数据 -
将工作表转换为JSON格式
-
提取表头和数据
-
-
数据展示:
-
使用Element Plus的
<el-table>动态渲染数据 -
自动识别表头生成列
-
4. 扩展功能建议
-
文件类型验证:
ts
复制
下载
// 在handleFileChange中添加
const validTypes = [
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/csv'
];
if (!validTypes.includes(file.type)) {
ElMessage.error('请上传Excel或CSV文件');
return;
}
-
数据清洗:
ts
复制
下载
// 在processExcel后添加数据清洗逻辑
const cleanData = jsonData.map(item => {
// 示例:移除空值
return Object.fromEntries(
Object.entries(item).filter(([_, value]) => value !== null)
);
});
-
上传到服务器:
ts
复制
下载
import axios from 'axios';
const uploadData = async () => {
try {
const response = await axios.post('/api/import', {
data: tableData.value,
headers: tableHeaders.value
});
ElMessage.success(`服务器导入成功: ${response.data.message}`);
} catch (error) {
ElMessage.error('服务器导入失败');
}
};
5. 注意事项
-
大文件处理:
-
添加文件大小限制
-
使用Web Worker防止界面卡顿
-
分片读取处理
-
-
安全性:
-
验证文件内容格式
-
防止XSS攻击(特别是CSV文件)
-
服务器端二次验证
-
-
用户体验:
-
添加加载状态
-
显示解析进度
-
错误文件类型提示
-
vue
复制
下载
<!-- 加载状态示例 --> <el-button :loading="isLoading" @click="triggerFileInput" > 导入文件 </el-button>
这个实现方案提供了完整的文件导入流程,包括前端解析和展示,可根据实际需求扩展服务器上传功能。
6.实例代码
点击按钮,选择Excel文件,由前端解析数据,实现从Excel文件导入数据
1、导入的黄金搭档【按钮 + 输入框】,按钮显示充门面,输入框隐藏干实事
2、导入核心功能封装成工具
在组件中使用
ReagentInDialog.vue
<script setup lang="ts" name="ReagentInDialog">
import { importExcelFileByClient } from "@/utils/excelUtils";
// 文件输入实例对象
const fileInputRef = ref<HTMLInputElement | null>(null);
// 导入
const onImportClick = () => {
// 模拟点击元素
if (fileInputRef.value) {
// 重置以允许重复选择相同文件
fileInputRef.value.value = "";
fileInputRef.value.click();
}
};
// 点击【导入】触发
const handleImportByClient = async (e: Event) => {
// 获取文件对象
const input = e.target as HTMLInputElement;
if (!input.files?.length) return;
const file = input.files[0];
// 键值列名映射表
const keyColMap: Record<string, string> = {
编号: "materialNo",
试剂编号: "reagentNo",
试剂名称: "reagentName",
规格型号: "reagentSpec",
单位: "reagentUnit",
批号: "batchNo",
有效期至: "validityDate",
入库数量: "amount",
入库金额: "total"
};
// 导入文件,由前端解析文件,获取数据
const dataList = <IReagentInByCkDetail[]>await importExcelFileByClient(file, keyColMap);
// 加载数据
dataList.forEach((item) => {
tableData.value.push({
id: -(tableData.value.length + 1),
materialNo: (tableData.value.length + 1).toString(),
reagentNo: item.reagentNo,
reagentName: item.reagentName
});
});
// 等待 DOM 渲染完毕
await nextTick();
// 全选
tableRef.value?.toggleAllSelection();
};
</script>
<template>
<el-button class="in-btn" type="primary" plain @click="onImportClick">导入</el-button>
<!-- 文件输入元素,不显示,通过点击按钮【导入】执行 onImportClick,模拟点击该元素,从而触发 handleImportByClient 事件 -->
<input
ref="fileInputRef"
type="file"
accept=".xls, .xlsx"
style="display: none"
@change="handleImportByClient" />
</template>
导入工具
excelUtils.ts
import { convertFileSize } from "@/utils/pubUtils";
import { ElMessage } from "element-plus";
import * as xlsx from "xlsx";
/**
* 从Excel文件导入数据,由前端解析文件,获取数据
* @param file 导入文件
* @param colKeyMap 列名键值映射,key --> value,如:excel中列名为【样品编号】,其键值设置对应为【sampleNo】
* @returns 列表数据
*/
export async function importExcelFileByClient(file: any, keyColMap: Record<string, string>) {
// 定义及初始化需要返回的列表数据
let dataList: any[] = [];
// 文件校验
// 校验文件名后缀
if (!/\.(xls|xlsx)$/.test(file.name)) {
ElMessage.warning("请导入excel文件!");
return dataList;
}
// 校验文件格式
// application/vnd.ms-excel 为 .xls文件
// application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 为 .xlsx文件
else if (
file.type !== "application/vnd.ms-excel" &&
file.type !== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
) {
ElMessage.warning("excel文件已损坏,请检查!");
return dataList;
}
// 校验文件大小
else if (convertFileSize(file.size, "B", "MB") > 1) {
ElMessage.warning("文件大小不能超过1MB!");
return dataList;
}
// 文件读取
let fileReader = new FileReader();
// 以二进制的方式读取文件内容
fileReader.readAsArrayBuffer(file);
// 等待打开加载完成文件,其实就是执行 fileReader.onloadend = () => {},返回 true 表示成功,false 表示失败
let result = await loadedFile(fileReader);
if (result) {
// 获取文件数据
let fileData = fileReader.result;
// 读取工作薄 workbook
let workbook = xlsx.read(fileData, { type: "array" });
// 表格是有序列表,因此可以取多个 Sheet,这里取第一个 Sheet
let sheet = workbook.SheetNames[0];
// 将表格内容生成 json 数据
let sheetJson = xlsx.utils.sheet_to_json(workbook.Sheets[sheet]);
// 限制最多只能导入1000条数据,预防恶意操作导入超大量数据
if (sheetJson.length > 1000) {
ElMessage.warning("一次最多只能导入1000条数据!");
return dataList;
}
// 格式化表格json数据 sheetJson,转换成在excel表中看到的那种直观数据
dataList = formatSheetJson(sheetJson, keyColMap);
}
// 返回列表数据
return dataList;
}
/**
* 加载文件
* 是否打开加载了文件,因为 fileReader.onloadend 是异步任务,程序执行时,不会执行完 onloadend 内部的代码再往下执行,
* 而是执行到 onloadend 内部时,又跳出 onloadend,执行 onloadend 外部的代码
* 故将 fileReader.onloadend 用 Promise<boolean> 返回对象包裹,程序执行时用await loadedFile,这样就会执行完 onloadend 内部的代码再往下执行
* 【要让 异步任务 不异步执行,可以用一个方法将其包裹,并且该方法返回Promise对象,执行该方法时用 await】
* @param fileReader 文件读取器
* @returns 响应结果
*/
function loadedFile(fileReader: FileReader): Promise<boolean> {
return new Promise((resolve, reject) => {
// 读取文件,文件读取完成触发该事件
fileReader.onloadend = () => {
try {
// 成功打开加载完文件数据
resolve(true);
} catch (error) {
// 失败
reject(false);
}
};
});
}
/**
* 将表格json数据 sheetJson 转换成列表数据
* @param sheetJson 表格json数据
* @param colKeyMap 列名键值映射,key --> value,如:excel中列名为【样品编号】,其键值设置对应为【sampleNo】
* @returns 列表数据
*/
function formatSheetJson(sheetJson: any[], keyColMap: Record<string, string>): any[] {
// 无内容,返回空数据
if (!sheetJson.length) return [];
let result = sheetJson;
// 判断是否有表头,有表头的话,sheetJson对象必然有__EMPTY属性
let hasTableHead = !!sheetJson[0]["__EMPTY"];
// 拥有表头的数据,重新转换列标题
if (hasTableHead) {
// 获取对象中所有属性的名称
let header = sheetJson.shift();
// 数据
let data: any[] = [];
// 遍历对象所有属性(列信息)
Object.keys(header).forEach((key) => {
// 遍历数据(行信息)
sheetJson.forEach((item, index) => {
// 构建对象内容
let obj = data[index] || {};
// 对象增加属性,并给属性赋值数据(行列信息)
obj[header[key]] = item[key];
// 最终给数据行数据赋值对象内容
data[index] = obj;
});
});
result = data;
}
// 将表格对应的文字转换为 key
let dataList: any[] = [];
result.forEach((item) => {
let newItem: any = {};
Object.keys(item).forEach((key) => {
newItem[keyColMap[key]] = item[key];
});
dataList.push(newItem);
});
// 返回列表数据
return dataList;
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)