111 lines
3.3 KiB
TypeScript
111 lines
3.3 KiB
TypeScript
import React, { useEffect, useState, useRef } from 'react';
|
|
import { axiosInstance } from '../../../services/axios';
|
|
import * as XLSX from 'xlsx';
|
|
|
|
interface SpreadsheetThumbnailProps {
|
|
url: string;
|
|
fallback: React.ReactNode;
|
|
}
|
|
|
|
export const SpreadsheetThumbnail: React.FC<SpreadsheetThumbnailProps> = ({ url, fallback }) => {
|
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
const [html, setHtml] = useState<string>('');
|
|
const [scale, setScale] = useState(0.2);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let isMounted = true;
|
|
|
|
axiosInstance.get(url, { responseType: 'arraybuffer' })
|
|
.then(res => {
|
|
if (!isMounted) return;
|
|
try {
|
|
const workbook = XLSX.read(res.data, { type: 'array' });
|
|
const sheetName = workbook.SheetNames[0];
|
|
const worksheet = workbook.Sheets[sheetName];
|
|
const sheetHtml = XLSX.utils.sheet_to_html(worksheet, { header: '', footer: '' });
|
|
|
|
if (isMounted) {
|
|
setHtml(sheetHtml);
|
|
setLoading(false);
|
|
}
|
|
} catch (err) {
|
|
console.error('[SpreadsheetThumbnail] Render error:', err);
|
|
if (isMounted) {
|
|
setError(true);
|
|
setLoading(false);
|
|
}
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('[SpreadsheetThumbnail] Fetch error:', err);
|
|
if (isMounted) {
|
|
setError(true);
|
|
setLoading(false);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, [url]);
|
|
|
|
useEffect(() => {
|
|
if (!parentRef.current) return;
|
|
const updateScale = () => {
|
|
if (parentRef.current) {
|
|
const width = parentRef.current.offsetWidth || 280;
|
|
setScale(width / 900);
|
|
}
|
|
};
|
|
updateScale();
|
|
const observer = new ResizeObserver(updateScale);
|
|
observer.observe(parentRef.current);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
if (error) return <>{fallback}</>;
|
|
|
|
return (
|
|
<div ref={parentRef} className="w-full h-full relative overflow-hidden bg-white select-none pointer-events-none p-1">
|
|
{loading && (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-slate-50 z-10">
|
|
<div className="w-5 h-5 rounded-full border-2 border-emerald-600 border-t-transparent animate-spin" />
|
|
</div>
|
|
)}
|
|
<div
|
|
className="excel-thumbnail-container absolute top-0 left-0 origin-top-left"
|
|
style={{
|
|
width: '900px',
|
|
height: '600px',
|
|
transform: `scale(${scale})`,
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
<style>{`
|
|
.excel-thumbnail-container table {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
font-size: 10px;
|
|
font-family: monospace;
|
|
color: #0f172a;
|
|
}
|
|
.excel-thumbnail-container td {
|
|
border: 1px solid #cbd5e1;
|
|
padding: 4px 6px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
color: #0f172a;
|
|
}
|
|
.excel-thumbnail-container tr:nth-child(even) {
|
|
background-color: #f8fafc;
|
|
}
|
|
`}</style>
|
|
<div dangerouslySetInnerHTML={{ __html: html }} />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|