fenghuo/apps/web/components/AdvancedFileDownload.tsx

235 lines
6.8 KiB
TypeScript
Raw Normal View History

2025-05-28 20:00:36 +08:00
import React, { useState } from 'react';
import { useFileDownload } from '../hooks/useFileDownload';
import { useTusUpload } from '../hooks/useTusUpload';
export function AdvancedFileDownload() {
const { getFileInfo } = useTusUpload();
const {
downloadProgress,
isDownloading,
downloadError,
downloadFile,
downloadFileWithProgress,
previewFile,
copyFileLink,
canPreview,
getFileIcon,
} = useFileDownload();
const [fileId, setFileId] = useState('');
const [fileInfo, setFileInfo] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 获取文件信息
const handleGetFileInfo = async () => {
if (!fileId.trim()) {
setError('请输入文件ID');
return;
}
setLoading(true);
setError(null);
try {
const info = await getFileInfo(fileId);
if (info) {
setFileInfo(info);
} else {
setError('文件不存在或未准备好');
}
} catch (err) {
setError('获取文件信息失败');
} finally {
setLoading(false);
}
};
// 简单下载
const handleSimpleDownload = () => {
downloadFile(fileId, fileInfo?.title);
};
// 带进度的下载
const handleProgressDownload = async () => {
try {
await downloadFileWithProgress(fileId, fileInfo?.title);
} catch (error) {
console.error('Download with progress failed:', error);
}
};
// 预览文件
const handlePreview = () => {
previewFile(fileId);
};
// 复制链接
const handleCopyLink = async () => {
try {
await copyFileLink(fileId);
alert('链接已复制到剪贴板!');
} catch (error) {
alert('复制失败');
}
};
return (
<div className="p-6 bg-white rounded-lg shadow-md">
<h3 className="text-lg font-semibold mb-4"></h3>
{/* 文件ID输入 */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">ID</label>
<div className="flex gap-2">
<input
type="text"
value={fileId}
onChange={(e) => setFileId(e.target.value)}
placeholder="输入文件ID"
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleGetFileInfo}
disabled={loading}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? '查询中...' : '查询'}
</button>
</div>
</div>
{/* 错误信息 */}
{(error || downloadError) && (
<div className="mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-md">
{error || downloadError}
</div>
)}
{/* 下载进度 */}
{isDownloading && downloadProgress && (
<div className="mb-4 p-4 bg-blue-50 rounded-md">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-blue-900"></span>
<span className="text-sm text-blue-600">{downloadProgress.percentage}%</span>
</div>
<div className="w-full bg-blue-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${downloadProgress.percentage}%` }}
/>
</div>
<div className="mt-1 text-xs text-blue-600">
{formatFileSize(downloadProgress.loaded)} / {formatFileSize(downloadProgress.total)}
</div>
</div>
)}
{/* 文件信息 */}
{fileInfo && (
<div className="mb-6 p-4 bg-gray-50 rounded-md">
<div className="flex items-center gap-3 mb-3">
<span className="text-2xl">{getFileIcon(fileInfo.type || '')}</span>
<div>
<h4 className="font-medium">{fileInfo.title || '未知文件'}</h4>
<p className="text-sm text-gray-600">{fileInfo.type || '未知类型'}</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="font-medium">:</span>
<span
className={`ml-2 px-2 py-1 rounded text-xs ${
fileInfo.status === 'UPLOADED' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
}`}
>
{fileInfo.status || '未知'}
</span>
</div>
{fileInfo.meta?.size && (
<div>
<span className="font-medium">:</span> {formatFileSize(fileInfo.meta.size)}
</div>
)}
<div>
<span className="font-medium">:</span> {new Date(fileInfo.createdAt).toLocaleString()}
</div>
<div>
<span className="font-medium">:</span> {fileInfo.storageType || '未知'}
</div>
</div>
</div>
)}
{/* 操作按钮 */}
{fileInfo && (
<div className="space-y-3">
<div className="flex gap-2 flex-wrap">
<button
onClick={handleSimpleDownload}
disabled={isDownloading}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50"
>
</button>
<button
onClick={handleProgressDownload}
disabled={isDownloading}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
>
</button>
{canPreview(fileInfo.type || '') && (
<button
onClick={handlePreview}
className="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700"
>
</button>
)}
<button onClick={handleCopyLink} className="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700">
</button>
</div>
{/* 文件预览提示 */}
{canPreview(fileInfo.type || '') && (
<div className="p-3 bg-purple-50 border border-purple-200 rounded-md">
<p className="text-sm text-purple-700">💡 线"预览文件"</p>
</div>
)}
</div>
)}
{/* 使用说明 */}
<div className="mt-6 p-4 bg-gray-50 rounded-md">
<h4 className="text-sm font-medium text-gray-900 mb-2"></h4>
<ul className="text-xs text-gray-600 space-y-1">
<li>
<strong></strong>
</li>
<li>
<strong></strong>
</li>
<li>
<strong></strong>PDF线
</li>
<li>
<strong></strong>访
</li>
</ul>
</div>
</div>
);
}
// 格式化文件大小
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}