Merge branch 'feature/tool_yjp' into release/v0.2.8
This commit is contained in:
@@ -4,10 +4,10 @@
|
|||||||
* @Author: yujiangping
|
* @Author: yujiangping
|
||||||
* @Date: 2026-03-16 19:01:12
|
* @Date: 2026-03-16 19:01:12
|
||||||
* @LastEditors: yujiangping
|
* @LastEditors: yujiangping
|
||||||
* @LastEditTime: 2026-03-16 19:17:47
|
* @LastEditTime: 2026-03-17 16:19:45
|
||||||
*/
|
*/
|
||||||
import { useState, useEffect, useRef, useCallback, type FC } from 'react';
|
import { useState, useEffect, useRef, useCallback, type FC } from 'react';
|
||||||
import { Spin, Alert, Button, Table, InputNumber } from 'antd';
|
import { Spin, Alert, Button, Table, InputNumber, Image } from 'antd';
|
||||||
import {
|
import {
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
@@ -21,12 +21,10 @@ import { cookieUtils } from '@/utils/request';
|
|||||||
import mammoth from 'mammoth';
|
import mammoth from 'mammoth';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
|
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
|
|
||||||
// 设置 pdf.js worker
|
// 设置 pdf.js worker
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
|
||||||
'pdfjs-dist/build/pdf.worker.mjs',
|
|
||||||
import.meta.url,
|
|
||||||
).toString();
|
|
||||||
|
|
||||||
interface DocumentPreviewProps {
|
interface DocumentPreviewProps {
|
||||||
fileUrl: string;
|
fileUrl: string;
|
||||||
@@ -65,9 +63,12 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
|||||||
const [pptCurrentPage, setPptCurrentPage] = useState(1);
|
const [pptCurrentPage, setPptCurrentPage] = useState(1);
|
||||||
const [pptTotalPages, setPptTotalPages] = useState(0);
|
const [pptTotalPages, setPptTotalPages] = useState(0);
|
||||||
|
|
||||||
|
// 图片状态
|
||||||
|
const [imageBlobUrl, setImageBlobUrl] = useState<string>('');
|
||||||
|
|
||||||
// 支持预览的文件类型
|
// 支持预览的文件类型
|
||||||
const previewableTypes = [
|
const previewableTypes = [
|
||||||
'.pdf', '.txt', '.md',
|
'.pdf', '.txt', '.md', '.csv',
|
||||||
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp',
|
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp',
|
||||||
'.doc', '.docx', '.xls', '.xlsx',
|
'.doc', '.docx', '.xls', '.xlsx',
|
||||||
'.ppt', '.pptx',
|
'.ppt', '.pptx',
|
||||||
@@ -90,7 +91,7 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
|||||||
};
|
};
|
||||||
const isPdfFile = () => getFileExtension() === '.pdf';
|
const isPdfFile = () => getFileExtension() === '.pdf';
|
||||||
const isWordFile = () => ['.doc', '.docx'].includes(getFileExtension());
|
const isWordFile = () => ['.doc', '.docx'].includes(getFileExtension());
|
||||||
const isExcelFile = () => ['.xls', '.xlsx'].includes(getFileExtension());
|
const isExcelFile = () => ['.xls', '.xlsx', '.csv'].includes(getFileExtension());
|
||||||
const isPptFile = () => ['.ppt', '.pptx'].includes(getFileExtension());
|
const isPptFile = () => ['.ppt', '.pptx'].includes(getFileExtension());
|
||||||
const isPreviewable = () => previewableTypes.includes(getFileExtension());
|
const isPreviewable = () => previewableTypes.includes(getFileExtension());
|
||||||
|
|
||||||
@@ -227,6 +228,28 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
|||||||
}
|
}
|
||||||
}, [fileUrl]);
|
}, [fileUrl]);
|
||||||
|
|
||||||
|
// ========== 图片加载逻辑 ==========
|
||||||
|
const loadImageFile = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
setErrorMessage('');
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await fetchFileBuffer(fileUrl);
|
||||||
|
const ext = getFileExtension().replace('.', '');
|
||||||
|
const mimeMap: Record<string, string> = {
|
||||||
|
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
||||||
|
gif: 'image/gif', bmp: 'image/bmp', webp: 'image/webp', svg: 'image/svg+xml',
|
||||||
|
};
|
||||||
|
const blob = new Blob([arrayBuffer], { type: mimeMap[ext] || 'image/png' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
setImageBlobUrl(url);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('加载图片文件失败:', err);
|
||||||
|
handleError(err.message || '图片加载失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ========== 文本/Word/Excel 加载逻辑 ==========
|
// ========== 文本/Word/Excel 加载逻辑 ==========
|
||||||
const loadTextFile = async () => {
|
const loadTextFile = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -274,12 +297,42 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCsvFile = () => getFileExtension() === '.csv';
|
||||||
|
|
||||||
const loadExcelFile = async () => {
|
const loadExcelFile = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
try {
|
try {
|
||||||
const arrayBuffer = await fetchFileBuffer(fileUrl);
|
const arrayBuffer = await fetchFileBuffer(fileUrl);
|
||||||
|
|
||||||
|
// CSV 文件需要处理编码问题(可能是 GBK/GB2312)
|
||||||
|
if (isCsvFile()) {
|
||||||
|
let csvText: string;
|
||||||
|
// 先尝试 UTF-8 解码
|
||||||
|
const utf8Text = new TextDecoder('utf-8').decode(arrayBuffer);
|
||||||
|
// 检测是否有乱码特征(常见的 GBK 被错误解析为 UTF-8 的替换字符)
|
||||||
|
if (utf8Text.includes('\uFFFD') || /[\x80-\xff]/.test(utf8Text.slice(0, 200))) {
|
||||||
|
// 尝试 GBK 解码
|
||||||
|
try {
|
||||||
|
csvText = new TextDecoder('gbk').decode(arrayBuffer);
|
||||||
|
} catch {
|
||||||
|
csvText = utf8Text;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
csvText = utf8Text;
|
||||||
|
}
|
||||||
|
const workbook = XLSX.read(csvText, { type: 'string' });
|
||||||
|
const sheets = workbook.SheetNames.map(sheetName => {
|
||||||
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
|
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 }) as any[][];
|
||||||
|
return { sheetName, data };
|
||||||
|
});
|
||||||
|
setExcelData(sheets);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
||||||
const sheets = workbook.SheetNames.map(sheetName => {
|
const sheets = workbook.SheetNames.map(sheetName => {
|
||||||
const worksheet = workbook.Sheets[sheetName];
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
@@ -311,7 +364,7 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
|||||||
else if (isExcelFile()) loadExcelFile();
|
else if (isExcelFile()) loadExcelFile();
|
||||||
else if (isPdfFile()) loadPdfFile();
|
else if (isPdfFile()) loadPdfFile();
|
||||||
else if (isPptFile()) loadPptFile();
|
else if (isPptFile()) loadPptFile();
|
||||||
else if (isImageFile()) setLoading(false);
|
else if (isImageFile()) loadImageFile();
|
||||||
}, [fileUrl]);
|
}, [fileUrl]);
|
||||||
|
|
||||||
// PDF 翻页/缩放后重新渲染
|
// PDF 翻页/缩放后重新渲染
|
||||||
@@ -412,11 +465,11 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
|||||||
{/* 图片预览 */}
|
{/* 图片预览 */}
|
||||||
{isImageFile() && !error && !loading && (
|
{isImageFile() && !error && !loading && (
|
||||||
<div className="rb:w-full rb:flex-1 rb:overflow-auto rb:bg-gray-50 rb:flex rb:items-center rb:justify-center">
|
<div className="rb:w-full rb:flex-1 rb:overflow-auto rb:bg-gray-50 rb:flex rb:items-center rb:justify-center">
|
||||||
<img
|
<Image
|
||||||
src={fileUrl}
|
src={imageBlobUrl}
|
||||||
alt={fileName || '图片预览'}
|
alt={fileName || '图片预览'}
|
||||||
className="rb:max-w-full rb:max-h-full rb:object-contain"
|
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
|
||||||
onError={() => handleError('图片加载失败')}
|
onError={() => handleError('图片渲染失败')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user