前端开发实现Excel模板数据导入表格想必大家都遇到过,本文将详细介绍如何利用Vue3和Ant Design搭建一个具备此功能的组件。

一、安装所需依赖

在开始编写代码前,需要安装一些必要的依赖包。这些依赖包将帮助我们处理Excel文件的读取、下载以及实现界面上的各种交互功能。使用npm或yarn都可以进行安装,命令如下:

npm install xlsx file-saver @ant-design/icons-vue # 或 yarn add xlsx file-saver @ant-design/icons-vue 

xlsx 用于处理Excel文件的解析和生成;file-saver 能够帮助我们实现文件的下载功能;@ant-design/icons-vue 则提供了Ant Design风格的图标,方便在界面中使用。

二、完整组件的实现过程

(一)模板部分(template)

模板部分主要负责构建组件的用户界面,代码如下:

<template> <div class="excel-import-container"> <a-space direction="vertical" style="width: 100%"> <a-space> <a-upload :before-upload="beforeUpload" :show-upload-list="false" accept=".xlsx,.xls" > <a-button type="primary"> <template #icon><UploadOutlined /></template> 导入Excel </a-button> </a-upload> <a-button @click="downloadTemplate"> <template #icon><DownloadOutlined /></template> 下载模板 </a-button> </a-space> <a-alert v-if="importErrors.length > 0" type="error" message="导入数据存在以下问题:" :description="importErrors.join('n')" show-icon closable @close="() => importErrors = []" /> <a-table :columns="columns" :data-source="importedData" :row-key="(record, index) => index" :scroll="{ x: 2000 }" bordered size="middle" > <template #bodyCell="{ column, text, record }"> <template v-if="column.dataIndex === 'index'"> {{ record.index + 1 }} </template> <template v-else-if="['isImports', 'coreProduct', 'efficient', 'waterSaving', 'environment', 'infoInnovationProduct', 'govService'].includes(column.dataIndex)"> {{ text == 1 ? '是' : '否' }} </template> <template v-else-if="column.dataIndex === 'operation'"> <a-button type="link" danger @click="removeItem(record.index)"> 删除 </a-button> </template> </template> </a-table> <div class="action-buttons" v-if="importedData.length > 0"> <a-button @click="clearData">清空数据</a-button> <a-button type="primary" @click="submitData" :loading="isSubmitting" > 提交数据 </a-button> </div> </a-space> </div> </template> 

在这个模板中:

  • 首先定义了一个包含导入和下载功能按钮的区域。a-upload 组件实现文件上传功能,before-upload 属性绑定了上传前的处理函数 beforeUploadshow-upload-list 设置为 false 表示不显示上传列表,accept 限定了可上传的文件类型为 .xlsx.xls 。旁边的 a-button 用于下载模板,点击时会触发 downloadTemplate 函数。
  • a-alert 组件用于在导入数据出现错误时显示错误信息,错误信息通过 importErrors 数组获取,用户点击关闭按钮时会清空错误信息数组。
  • a-table 组件展示导入的数据表格。columns 定义表格列配置,data-source 绑定导入的数据,row-key 用于指定每行的唯一标识。表格的单元格根据不同的 dataIndex 进行不同的显示处理,如序号列、布尔值列以及操作列等。
  • 最后,当导入数据存在时,会显示清空数据和提交数据的按钮,分别对应 clearDatasubmitData 函数,提交按钮还会根据 isSubmitting 的值显示加载状态。

(二)脚本部分(script setup)

脚本部分负责实现组件的逻辑功能,代码如下:

<script setup> import { ref } from 'vue'; import * as XLSX from 'xlsx'; import { saveAs } from 'file-saver'; import { UploadOutlined, DownloadOutlined } from '@ant-design/icons-vue'; import { message, Modal } from 'ant-design-vue'; // 导入表格列配置 const columns = tempColumns; // 响应式数据 const importedData = ref([]); const importErrors = ref([]); const isSubmitting = ref(false); // 处理文件上传 const beforeUpload = (file) => { const reader = new FileReader(); reader.onload = (e) => { try { const data = e.target.result; const workbook = XLSX.read(data, { type: 'array' }); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); // 处理导入数据 processImportedData(jsonData); } catch (error) { message.error('文件解析失败: ' + error.message); } }; reader.readAsArrayBuffer(file); return false; // 阻止自动上传 }; // 处理导入的数据 const processImportedData = (data) => { const errors = []; data.forEach((row, index) => { // 检查必填字段 if (!row['商品/服务名称']) { errors.push(`第 ${index + 2} 行: 商品/服务名称为必填项`); } if (!row['采购品目名称']) { errors.push(`第 ${index + 2} 行: 采购品目名称为必填项`); } // 检查数字字段 const numberFields = ['总金额/首购费用(元)', '采购数量', '单价(元)']; numberFields.forEach(field => { if (row[field] && isNaN(Number(row[field]))) { errors.push(`第 ${index + 2} 行: ${field} 必须为数字`); } }); // 检查是否类字段(应为0或1) const booleanFields = [ '是否采购进口产品', '是否核心产品', '是否强制采购节能产品', '是否强制采购节水产品', '是否优先采购环保产品', '是否属于政府采购需求标准(2023年版)规范产品', '是否属于政府购买服务' ]; booleanFields.forEach(field => { if (row[field]!== undefined && row[field]!== null && row[field]!== '' && row[field]!= 0 && row[field]!= 1) { errors.push(`第 ${index + 2} 行: ${field} 必须为"是"(1)或"否"(0)`); } }); }); if (errors.length > 0) { importErrors.value = errors; message.error(`发现 ${errors.length} 处错误,请修正后重新导入`); return; } // 转换数据格式 const formattedData = data.map((row, index) => ({ index, goodsName: row['商品/服务名称'], purCatalogName: row['采购品目名称'], totalPrice: row['总金额/首购费用(元)']? Number(row['总金额/首购费用(元)']) : null, num: row['采购数量']? Number(row['采购数量']) : null, unit: row['计量单位'], price: row['单价(元)']? Number(row['单价(元)']) : null, isImports: row['是否采购进口产品'] == 1? 1 : 0, coreProduct: row['是否核心产品'] == 1? 1 : 0, efficient: row['是否强制采购节能产品'] == 1? 1 : 0, waterSaving: row['是否强制采购节水产品'] == 1? 1 : 0, environment: row['是否优先采购环保产品'] == 1? 1 : 0, infoInnovationProduct: row['是否属于政府采购需求标准(2023年版)规范产品'] == 1? 1 : 0, industrialClass: row['采购标的所属国民经济分类'], govService: row['是否属于政府购买服务'] == 1? 1 : 0, govServiceCatalogName: row['政府购买服务指导性目录名称'], spec: row['规格参数/服务要求'] })); importedData.value = formattedData; message.success(`成功导入 ${formattedData.length} 条数据`); }; // 下载模板 const downloadTemplate = () => { // 准备表头 const headers = columns .filter(col => col.dataIndex!== 'index' && col.dataIndex!== 'operation') .map(col => col.title); // 示例数据 const sampleData = [ [ '示例商品', // 商品/服务名称 '办公设备', // 采购品目名称 1000, // 总金额/首购费用(元) 10, // 采购数量 '台', // 计量单位 100, // 单价(元) 0, // 是否采购进口产品 1, // 是否核心产品 0, // 是否强制采购节能产品 0, // 是否强制采购节水产品 1, // 是否优先采购环保产品 0, // 是否属于政府采购需求标准(2023年版)规范产品 'C3910', // 采购标的所属国民经济分类 0, // 是否属于政府购买服务 '', // 政府购买服务指导性目录名称 '规格参数示例' // 规格参数/服务要求 ] ]; // 创建工作表 const ws = XLSX.utils.aoa_to_sheet([headers, ...sampleData]); // 创建工作簿 const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '采购数据'); // 设置列宽 if (!ws['!cols']) ws['!cols'] = []; headers.forEach((_, index) => { ws['!cols'][index] = { wch: 20 }; // 设置每列宽度 }); // 生成Excel文件 const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); saveAs( new Blob([wbout], { type: 'application/octet-stream' }), '采购数据模板.xlsx' ); }; // 删除单条数据 const removeItem = (index) => { importedData.value = importedData.value.filter((_, i) => i!== index); // 重新生成index importedData.value = importedData.value.map((item, i) => ({...item, index: i })); }; // 清空数据 const clearData = () => { Modal.confirm({ title: '确认清空数据吗?', content: '这将清除所有已导入的数据', onOk() { importedData.value = []; importErrors.value = []; } }); }; // 提交数据 const submitData = async () => { isSubmitting.value = true; try { // 这里替换为实际的API调用 // const response = await api.submitImportData(importedData.value); message.success('数据提交成功'); importedData.value = []; } catch (error) { // 这里可添加错误处理逻辑,比如提示用户提交失败原因 } finally { isSubmitting.value = false; } }; </script> 
  • 首先导入了Vue的 ref 函数用于创建响应式数据,XLSX 库用于处理Excel文件,saveAs 用于文件下载,UploadOutlinedDownloadOutlined 是Ant Design的图标,messageModal 用于显示提示信息和确认弹窗。
  • beforeUpload 函数在文件上传前被调用,它使用 FileReader 读取文件内容,然后通过 XLSX 库将文件解析为JSON数据,并调用 processImportedData 函数处理这些数据。如果解析过程中出现错误,会使用 message.error 提示用户。
  • processImportedData 函数主要负责检查导入数据的合法性,如必填字段是否填写、数字字段是否为数字、某些字段是否为0或1等。如果存在错误,将错误信息收集到 errors 数组中,并显示错误提示,阻止数据导入。若数据合法,则对数据进行格式转换,将其存入 importedData 响应式数据中,并提示成功导入的信息。
  • downloadTemplate 函数用于生成并下载Excel模板文件。它先准备好表头和示例数据,然后使用 XLSX 库创建工作表和工作簿,设置好列宽后,将工作簿转换为二进制数据并通过 saveAs 实现文件下载。
  • removeItem 函数用于删除表格中的某一条数据,它通过过滤数组的方式移除指定索引的数据,并重新生成数据的 index
  • clearData 函数通过 Modal.confirm 弹窗询问用户是否确认清空数据,确认后清空 importedDataimportErrors 数组。
  • submitData 函数用于提交数据,在提交过程中设置 isSubmittingtrue 显示加载状态,目前代码中只是模拟了数据提交成功的提示,实际使用时需要替换为真实的API调用,并在 catch 块中添加错误处理逻辑,在 finally 块中将 isSubmitting 设置为 false

通过上述步骤,我们就成功实现了一个基于Vue3和Ant Design的Excel模板数据导入表格的功能组件。这个组件涵盖了文件导入、数据校验、模板下载、数据删除、清空以及提交等一系列功能,满足了常见的业务需求。