fenghuo/apps/web/components/AdvancedFileDownload.tsx

235 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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];
}