在 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];// 轉換為JSONconst 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 事件 --><inputref="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;// 讀取工作薄 workbooklet workbook = xlsx.read(fileData, { type: "array" });// 表格是有序列表,因此可以取多個 Sheet,這里取第一個 Sheetlet 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;}// 將表格對應的文字轉換為 keylet dataList: any[] = [];result.forEach((item) => {let newItem: any = {};Object.keys(item).forEach((key) => {newItem[keyColMap[key]] = item[key];});dataList.push(newItem);});// 返回列表數據return dataList;
}