110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
|
|
import { useState, useEffect } from "react";
|
||
|
|
import { useQuery } from "@tanstack/react-query";
|
||
|
|
import axios from "axios";
|
||
|
|
import * as XLSX from "xlsx";
|
||
|
|
|
||
|
|
type Props = {
|
||
|
|
url?: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export function ExcelPreview({ url }: Props) {
|
||
|
|
const [sheets, setSheets] = useState<{ name: string; data: any[][] }[]>([]);
|
||
|
|
const [activeSheet, setActiveSheet] = useState(0);
|
||
|
|
|
||
|
|
const { data, isLoading, error, isError } = useQuery({
|
||
|
|
queryKey: ["excel", url],
|
||
|
|
queryFn: () => axios.get(url!, { responseType: "arraybuffer" }),
|
||
|
|
enabled: !!url,
|
||
|
|
});
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (data?.data) {
|
||
|
|
try {
|
||
|
|
const workbook = XLSX.read(data.data, { type: "array" });
|
||
|
|
const sheetData = workbook.SheetNames.map((name) => {
|
||
|
|
const worksheet = workbook.Sheets[name];
|
||
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||
|
|
return { name, data: jsonData as any[][] };
|
||
|
|
});
|
||
|
|
setSheets(sheetData);
|
||
|
|
} catch (err) {
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, [data]);
|
||
|
|
|
||
|
|
if (!url) {
|
||
|
|
return (
|
||
|
|
<div className="w-full h-full flex items-center justify-center text-muted-foreground">
|
||
|
|
Excel 文件地址无效
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isLoading) {
|
||
|
|
return (
|
||
|
|
<div className="flex items-center justify-center h-full">
|
||
|
|
<div>加载中...</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isError) {
|
||
|
|
return (
|
||
|
|
<div className="flex items-center justify-center h-full text-red-500">
|
||
|
|
{error?.message || "加载失败"}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sheets.length === 0) {
|
||
|
|
return (
|
||
|
|
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||
|
|
Excel 文件为空或格式不正确
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const currentSheet = sheets[activeSheet];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="h-full flex flex-col">
|
||
|
|
{sheets.length > 1 && (
|
||
|
|
<div className="flex-none border-b bg-slate-50 dark:bg-slate-900 p-2 flex gap-2 overflow-x-auto">
|
||
|
|
{sheets.map((sheet, index) => (
|
||
|
|
<button
|
||
|
|
key={index}
|
||
|
|
onClick={() => setActiveSheet(index)}
|
||
|
|
className={`px-4 py-2 rounded text-sm whitespace-nowrap transition-colors ${
|
||
|
|
activeSheet === index
|
||
|
|
? "bg-primary text-primary-foreground"
|
||
|
|
: "bg-white dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{sheet.name}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<div className="flex-auto overflow-auto">
|
||
|
|
<table className="w-full border-collapse text-sm">
|
||
|
|
<tbody>
|
||
|
|
{currentSheet.data.map((row, rowIndex) => (
|
||
|
|
<tr key={rowIndex} className="border-b">
|
||
|
|
{row.map((cell, cellIndex) => (
|
||
|
|
<td
|
||
|
|
key={cellIndex}
|
||
|
|
className="border-r px-3 py-2 whitespace-nowrap"
|
||
|
|
>
|
||
|
|
{cell !== null && cell !== undefined ? String(cell) : ""}
|
||
|
|
</td>
|
||
|
|
))}
|
||
|
|
</tr>
|
||
|
|
))}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|