当前位置: 首页 > news >正文

vue3 + xlsx 实现导入导出表格,导出动态获取表头和数据

封装 xlsx.ts 文件

npm i xlsx element-plus
import * as XLSX from "xlsx";
import { ElMessageBox, ElMessage } from "element-plus";/*** 导出表格数据为 Excel 文件,自动匹配 el-table 或 vxe-table 表头和数据字段* element-plus 2.7.6 版本支持动态获取列* @param {Object} [tableRef] - 表格组件的 ref 引用(可选)* @param {String} fileName - 导出文件名(默认:export.xlsx)* @param {Object} options - 配置项(可选)* @param {Array} options.headers - 自定义表头(可选)* @param {Array} options.dataKeys - 自定义数据字段(可选)* @param {Array} options.data - 要导出的数据(可选,如果没有 tableRef 则必须提供)* * 使用示例:* exportExcel(tableRef.value, '用户数据.xlsx')* * 自定义表头和数据字段 示例:* exportExcel(tableRef.value, '用户数据.xlsx', {headers: ['姓名', '城市'], // 自定义表头dataKeys: ['name', 'city'] // 自定义数据字段})** 如果没有 tableRef,则需要提供自定义表头和数据:* exportExcel(null, '用户数据.xlsx', {headers: ['姓名', '城市'],data: [{name: '张三', city: '北京'}, {name: '李四', city: '上海'}]})*/
export const exportExcel = (tableRef: any = null,fileName = "export.xlsx",options: any = {}
) => {let headers = options.headers || [];let data = options.data || [];if (tableRef) {if (tableRef.$options.name === "VxeTable") {headers =headers.length > 0? headers: tableRef.getColumns().map((col: any) => col.title);data = data.length > 0 ? data : tableRef.getTableData().fullData;} else if (tableRef.$options.name === "ElTable") {headers =headers.length > 0? headers: tableRef.columns.map((col: any) => col.label);data = data.length > 0 ? data : tableRef.data;} else {throw new Error("不支持的表格组件类型");}if (options.dataKeys) {data = data.map((item: any) =>options.dataKeys.reduce((obj: any, key: any) => ({ ...obj, [key]: item[key] }),{}));}}if (!headers.length) {throw new Error("缺少必要的表头");}// 构建工作表数据const worksheetData = [headers, // 表头行...data.map((item: any) =>headers.map((header: any) => {const key = Object.keys(item).find((k) => k.toLowerCase() === header.toLowerCase());return key ? item[key] : "";})),];// 创建 workbookconst workbook = XLSX.utils.book_new();const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);// 添加样式worksheet["!cols"] = headers.map(() => ({ wch: 10 })); // 列宽// 组合并导出XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");XLSX.writeFile(workbook, fileName);
};/*** 从Excel文件导入数据,并可以选择性地更新指定表格组件的数据。* @param {Object} [tableRef] - 表格组件的 ref 引用(可选)* @param {string[]} [requiredFields] - 必填字段的表头名称数组(可选)* @returns {Promise<any[]>} 解析后的数据数组** 示例:仅导入数据* importExcel().then(data => console.log('导入的数据:', data));** 示例:导入数据并更新指定的表格组件* importExcel(tableRef).then(() => console.log('表格已更新'));** 示例:导入数据并校验必填字段* importExcel(null, ['名称']).then(data => console.log('导入的数据:', data));*/
export const importExcel = (tableRef: any = null,requiredFields: string[] = []
) => {return new Promise((resolve, reject) => {const input = document.createElement("input");input.setAttribute("type", "file");input.setAttribute("accept", ".xlsx"); // 限制只能选择 .xlsx 文件input.click();input.onchange = (e: any) => {const files = e.target.files;if (!files.length) {reject(new Error("没有选择文件"));return;}const file = files[0];// 校验文件类型if (!file.name.endsWith(".xlsx")) {reject(new Error("只能导入 .xlsx 文件"));return;}const reader = new FileReader();reader.onload = (event: any) => {try {const data = new Uint8Array(event.target.result);const workbook = XLSX.read(data, { type: "array" });const firstSheetName = workbook.SheetNames[0];const worksheet = workbook.Sheets[firstSheetName];// 提取并校验表头const headerData: any = XLSX.utils.sheet_to_json(worksheet, {header: 1,})[0]; // 获取第一行作为表头if (!requiredFields.every((field) => headerData.includes(field))) {reject(new Error("导入的表格表头与要求不符,请使用正确的模板!"));return;}// 直接转换数据(自动跳过表头行)const newData = XLSX.utils.sheet_to_json(worksheet, {defval: "", // 将空值默认为空字符串});// 校验必填字段if (requiredFields.length > 0) {const missingFieldsMap = new Map(); // 用于记录缺失必填字段的行号newData.forEach((row: any, index: number) => {requiredFields.forEach((field) => {const fieldValue = row[field]; // 获取字段值// 判断字段值是否为空(允许 0 和 false)if (fieldValue === "" ||fieldValue === null ||fieldValue === undefined) {// 记录缺失字段的行号(index + 2,因为表头占一行,且数组从0开始)const rowNumber = index + 2;if (!missingFieldsMap.has(rowNumber)) {missingFieldsMap.set(rowNumber, []);}missingFieldsMap.get(rowNumber).push(field);}});});// 如果有缺失字段,弹出错误提示if (missingFieldsMap.size > 0) {let errorMessage = "以下行的必填字段缺失:<br>"; // 使用 <br> 换行missingFieldsMap.forEach((fields, rowNumber) => {errorMessage += `${rowNumber} 行缺失字段:${fields.join(", ")}<br>`; // 使用 <br> 换行});reject(new Error(errorMessage)); // 同时 reject Promisereturn;}}// 如果提供了 tableRef,则更新表格数据if (tableRef) {let existingData;if (tableRef.$options.name === "VxeTable") {existingData = tableRef.getTableData().fullData;} else if (tableRef.$options.name === "ElTable") {existingData = tableRef.data;}const combinedData = [...existingData, ...newData];if (tableRef.$options.name === "VxeTable") {tableRef.loadData(combinedData);} else if (tableRef.$options.name === "ElTable") {tableRef.data = combinedData;}}resolve(newData); // 返回不包含表头的数据} catch (error) {reject(error);}};reader.onerror = () => reject(new Error("读取文件时出错"));reader.readAsArrayBuffer(file);};});
};/*** 导入数据并插入到数据库* @param {any[]} data - 导入的数据* @param {Function} insertApi - 插入数据的接口函数* @param {Function} refreshTable - 刷新表格的函数* @param {Function} setLoading - 控制 loading 状态的函数*/
export const importAndInsertData = async (data: any[],insertApi: (item: any) => Promise<any>,refreshTable: () => void,setLoading: (isLoading: boolean) => void
) => {try {setLoading(true);// 存储所有插入操作的 Promiseconst insertPromises = data.map((item) =>insertApi(item).catch((error) => {// 如果插入失败,返回失败的数据和错误信息return { item, error };}));// 等待所有插入操作完成const results = await Promise.all(insertPromises);// 检查是否有插入失败的数据const failedItems = results.filter((result) => result && result.error);if (failedItems.length > 0) {// 如果有插入失败的数据,提示失败的具体信息let errorMessage = "以下数据插入失败:<br>";failedItems.forEach(({ item, error }) => {errorMessage += `数据:${JSON.stringify(item)},错误:${error.message}<br>`;});ElMessageBox.alert(errorMessage, "导入失败", {confirmButtonText: "确定",dangerouslyUseHTMLString: true,});} else {// 如果全部插入成功,提示成功并刷新表格ElMessage.success("导入成功");refreshTable();}} catch (error: any) {// 捕获全局错误ElMessageBox.alert(`导入过程中发生错误:${error.message}`, "导入失败", {confirmButtonText: "确定",});} finally {setLoading(false); // 无论成功或失败,最终关闭 loading}
};/*** importAndInsertData 函数使用示例* 处理导入数据的函数const handleImport = async () => {try {// 必填字段let requiredFields = ['费用类型(ID)', '加成率(ID)', '名称', '拼音码', '规格', '价格', '最小单位', '库房转换率(整数)', '进货单位', '五笔码', '库房地点(ID)']const data: any = await importExcel(null, requiredFields);if (data.length === 0) {ElMessage.warning("导入的数据为空");return;}// 定义插入数据的接口函数const insertApi = async (item: any) => {const form = {code_item_cls: 0,code_item_id: 0,}const response = await api(form);if (response.data[0].success != 'T') {throw new Error(response.message || "插入数据失败");}return response;};// 调用导入并插入数据的函数await importAndInsertData(data, insertApi, handleLoadChargeItemlist, (isLoading) => {importLoading.value = isLoading; // 控制 loading 状态});} catch (error: any) {ElMessageBox.alert(error.message, "导入失败", {confirmButtonText: "确定",dangerouslyUseHTMLString: true,});}};*/

http://www.mrgr.cn/news/93890.html

相关文章:

  • Linux 离线部署Ollama和DeepSeek-r1模型
  • 零基础掌握Linux SCP命令:5分钟实现高效文件传输,小白必看!
  • IO学习---->线程
  • QT系列教程(20) Qt 项目视图便捷类
  • 『PostgreSQL』PGSQL备份与还原实操指南
  • 【测试框架篇】单元测试框架pytest(4):assert断言详解
  • 【Linux内核系列】:深入理解缓冲区
  • 《平面几何强化训练题集》第2章5到9题
  • [GHCTF 2025]SQL??? 【sqlite注入】
  • uniapp+Vue3 开发小程序的下载文件功能
  • 选择排序算法OpenMP并行优化
  • 从新手到专家:嵌入式代码空间优化技巧
  • 【组件安装】Rocky 8.10 安装Local License Server 25.03.0 for Linux
  • C/C++中使用CopyFile、CopyFileEx原理、用法、区别及分别在哪些场景使用
  • 从零构建逻辑回归: sklearn 与自定义实现对比
  • [数据结构]并查集--C++版本的实现代码
  • sparkTTS window 安装
  • 数据集构建与训练前准备
  • OpenHarmony5.0分布式系统源码实现分析—软总线
  • C++蓝桥杯皮亚诺曲线距离求解