import React, { useEffect, useState } from 'react' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' export interface CsvPreviewProps { /** CSV 文件的远程 URL */ url?: string /** 直接传入的 CSV 内容 */ content?: string } function parseCsv(text: string): string[][] { const lines = text.split(/\r?\n/) return lines .filter(line => line.trim() !== '') .map(line => { const row: string[] = [] let inQuotes = false let cell = '' for (let i = 0; i < line.length; i++) { const ch = line[i] if (ch === '"') { if (inQuotes && line[i + 1] === '"') { cell += '"' i++ } else { inQuotes = !inQuotes } } else if (ch === ',' && !inQuotes) { row.push(cell) cell = '' } else { cell += ch } } row.push(cell) return row }) } export function CsvPreview({ url, content }: CsvPreviewProps) { const [rows, setRows] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { let cancelled = false // 在异步回调里更新本地 state,避免在 effect 体内同步 setState queueMicrotask(() => { if (cancelled) return setLoading(true) setError(null) }) if (content != null) { queueMicrotask(() => { if (cancelled) return setRows(parseCsv(content)) setLoading(false) }) return () => { cancelled = true } } if (!url) { queueMicrotask(() => { if (cancelled) return setRows([]) setLoading(false) }) return () => { cancelled = true } } fetch(url) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.text() }) .then(text => { if (cancelled) return setRows(parseCsv(text)) }) .catch(err => { if (cancelled) return setError(err.message || '加载失败') }) .finally(() => { if (cancelled) return setLoading(false) }) return () => { cancelled = true } }, [content, url]) if (loading) { return (
加载中...
) } if (error) { return (
加载失败:{error}
) } if (rows.length === 0) { return (
文件为空
) } const [header, ...body] = rows const colCount = Math.max(...rows.map(r => r.length)) const paddedHeader = header.concat(Array(colCount - header.length).fill('')) return (
{paddedHeader.map((col, i) => ( {col || `列 ${i + 1}`} ))} {body.map((row, ri) => { const paddedRow = row.concat(Array(colCount - row.length).fill('')) return ( {paddedRow.map((cell, ci) => ( {cell} ))} ) })}
) }