React-ts页面Web处理Excel导入、导出

React-ts页面处理Excel, 环境React+Ant Design

1.引用xlsx和file-saver

import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';

2.具体导入逻辑

export const readExcelFile = (file: File): Promise<any[]> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    
    // 根据文件扩展名选择处理方式
    const isCSV = file.name.toLowerCase().endsWith('.csv') || 
                  file.name.toLowerCase().endsWith('.txt');
    
    if (isCSV) {
      // 处理 CSV/TXT 文件
      reader.onload = (e) => {
        try {
          const text = e.target?.result as string;
          
          // 检测分隔符
          const delimiter = detectDelimiter(text);
          console.log(`检测到的分隔符: "${delimiter}" (ASCII: ${delimiter.charCodeAt(0)})`);
          
          // 解析 CSV
          const rawData = parseCSV(text, delimiter);
          
          if (rawData.length === 0) {
            resolve([]);
            return;
          }
          
          // 提取标题行
          const headers = rawData[0].map((cell: any, index: number) => {
            if (cell === null || cell === undefined || cell === '') {
              return `Column_${index + 1}`;
            }
            return cell.toString().trim();
          });
          
          // 处理可能的重复标题
          const uniqueHeaders = makeUniqueHeaders(headers);
          
          // 提取数据行(跳过标题行)
          let dataRows = rawData.slice(1);
          
          // 过滤空行和无效行
          dataRows = filterValidRows(dataRows);
          
          // 转换为 JSON 对象数组
          const jsonData = dataRows.map(row => {
            const obj: Record<string, any> = {};
            uniqueHeaders.forEach((header, index) => {
              // 只处理存在的列
              if (index < row.length) {
                const value = row[index];
                // 处理空值
                obj[header] = (value === null || value === undefined || value === '') 
                  ? null 
                  : value;
              } else {
                obj[header] = null;
              }
            });
            return obj;
          });
          
          resolve(jsonData);
        } catch (error) {
          reject(error);
        }
      };
      
      // 以文本形式读取 CSV
      reader.readAsText(file, 'UTF-8');
    } else {
      // 处理 Excel 文件(xlsx, xls)
      reader.onload = (e) => {
        try {
          const arrayBuffer = e.target?.result as ArrayBuffer;
          const workbook = XLSX.read(arrayBuffer, { 
            type: 'array',
            cellDates: true,
            cellStyles: true
          });
          
          const sheetName = workbook.SheetNames[0];
          const worksheet = workbook.Sheets[sheetName];
          
          // 获取原始行数据(二维数组格式)
          const rawData = XLSX.utils.sheet_to_json(worksheet, { 
            header: 1, 
            defval: null,
            raw: false
          }) as any[][];
          

          // 提取标题行
          const headers = rawData[0].map((cell: any) => 
            cell?.toString().trim() || `Column_${Math.random().toString(36).slice(2, 7)}`
          );
          
          // 处理可能的重复标题
          const uniqueHeaders = makeUniqueHeaders(headers);
          
          // 提取数据行(跳过标题行)
          let dataRows = rawData.slice(1);
          
          // 过滤空行和无效行
          dataRows = filterValidRows(dataRows);
          
          // 转换为 JSON 对象数组
          const jsonData = dataRows.map(row => {
            const obj: Record<string, any> = {};
            uniqueHeaders.forEach((header, index) => {
              // 只处理存在的列
              if (index < row.length) {
                const value = row[index];
                // 处理空值
                obj[header] = (value === null || value === undefined || value === '') 
                  ? null 
                  : value;
              } else {
                obj[header] = null;
              }
            });
            return obj;
          });
          
          resolve(jsonData);
        } catch (error) {
          reject(error);
        }
      };
      
      reader.onerror = reject;
      reader.readAsArrayBuffer(file);
    }
  });
};
// 检测文件分隔符的函数
function detectDelimiter(text: string): string {
  const delimiters = [',', '\t', ';', '|', ' '];
  const lines = text.split('\n').slice(0, 10); // 检查前10行
  
  let bestDelimiter = ',';
  let maxScore = 0;
  
  for (const delimiter of delimiters) {
    let score = 0;
    
    for (const line of lines) {
      if (line.trim() === '') continue;
      
      const parts = line.split(delimiter);
      // 忽略空行和只有空格的列
      const nonEmptyParts = parts.filter(part => part.trim() !== '');
      
      if (nonEmptyParts.length > 1) {
        // 分数基于列数和非空列的比例
        score += nonEmptyParts.length;
      }
    }
    
    if (score > maxScore) {
      maxScore = score;
      bestDelimiter = delimiter;
    }
  }
  
  return bestDelimiter;
}
// 解析 CSV 字符串
function parseCSV(text: string, delimiter: string): any[][] {
  const lines = text.split('\n');
  const result: any[][] = [];
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    if (line === '') continue;
    
    // 处理引号内的分隔符
    const row: any[] = [];
    let inQuotes = false;
    let currentCell = '';
    
    for (let j = 0; j < line.length; j++) {
      const char = line[j];
      const nextChar = line[j + 1];
      
      if (char === '"' && !inQuotes) {
        inQuotes = true;
      } else if (char === '"' && inQuotes && nextChar === '"') {
        // 双引号转义
        currentCell += '"';
        j++; // 跳过下一个引号
      } else if (char === '"' && inQuotes) {
        inQuotes = false;
      } else if (char === delimiter && !inQuotes) {
        row.push(currentCell);
        currentCell = '';
      } else {
        currentCell += char;
      }
    }
    
    // 添加最后一个单元格
    row.push(currentCell);
    
    // 清理单元格:去除首尾空格
    const cleanedRow = row.map(cell => {
      if (cell === null || cell === undefined) return '';
      const str = String(cell).trim();
      // 尝试转换为数字
      if (str !== '' && !isNaN(Number(str))) {
        return Number(str);
      }
      return str;
    });
    
    result.push(cleanedRow);
  }
  
  return result;
}
// 处理重复标题
function makeUniqueHeaders(headers: string[]): string[] {
  const seen = new Map<string, number>();
  return headers.map(header => {
    if (!header || header.trim() === '') {
      return `Unnamed_Column_${Math.random().toString(36).slice(2, 7)}`;
    }
    
    let finalHeader = header.trim();
    let count = seen.get(finalHeader) || 0;
    
    while (seen.has(finalHeader) && seen.get(finalHeader)! > 0) {
      count++;
      finalHeader = `${header.trim()}_${count}`;
    }
    
    seen.set(header.trim(), count + 1);
    seen.set(finalHeader, 1);
    return finalHeader;
  });
}
// 过滤空行和无效行
function filterValidRows(rows: any[][]): any[][] {
  return rows.filter(row => {
    // 检查行是否有效(至少有一个非空单元格)
    return row.some(cell => 
      cell !== null && 
      cell !== undefined && 
      cell !== '' &&
      !(typeof cell === 'string' && cell.trim() === '')
    );
  });
}

// 检查单元格是否有效
function isValidCell(cell: any): boolean {
  return cell !== null && 
         cell !== undefined && 
         cell !== '' &&
         !(typeof cell === 'string' && cell.trim() === '');
}

包含了csv、txt、xlsx, xls等格式的处理

3.具体导出逻辑

导出就比较简单了


export const exportToExcel = (data: any[], filename: string) => {
  const worksheet = XLSX.utils.json_to_sheet(data);
  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
  
  const excelBuffer = XLSX.write(workbook, { 
    bookType: 'xlsx', 
    type: 'array' 
  });
  
  const blob = new Blob([excelBuffer], { 
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
  });
  
  saveAs(blob, `${filename}.xlsx`);
};
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇