fenghuo/apps/web/components/FileUpload.tsx

219 lines
6.5 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.

'use client';
import React, { useCallback, useState } from 'react';
import { useTusUpload } from '../hooks/useTusUpload';
interface UploadedFile {
fileId: string;
fileName: string;
url: string;
}
export function FileUpload() {
const { uploadProgress, isUploading, uploadError, handleFileUpload, getFileUrlByFileId, serverUrl } = useTusUpload();
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
const [dragOver, setDragOver] = useState(false);
// 处理文件选择
const handleFileSelect = useCallback(
async (files: FileList | null) => {
if (!files || files.length === 0) return;
for (let i = 0; i < files.length; i++) {
const file = files[i];
try {
const result = await handleFileUpload(
file,
(result) => {
console.log('Upload success:', result);
setUploadedFiles((prev) => [
...prev,
{
fileId: result.fileId,
fileName: result.fileName,
url: result.url,
},
]);
},
(error) => {
console.error('Upload error:', error);
},
);
} catch (error) {
console.error('Upload failed:', error);
}
}
},
[handleFileUpload],
);
// 处理拖拽上传
const handleDrop = useCallback(
(e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
handleFileSelect(e.dataTransfer.files);
},
[handleFileSelect],
);
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
setDragOver(true);
}, []);
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
}, []);
// 处理文件输入
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
handleFileSelect(e.target.files);
},
[handleFileSelect],
);
// 复制链接到剪贴板
const copyToClipboard = useCallback(async (url: string) => {
try {
await navigator.clipboard.writeText(url);
alert('链接已复制到剪贴板!');
} catch (error) {
console.error('Failed to copy:', error);
}
}, []);
return (
<div className="max-w-2xl mx-auto p-6">
<h2 className="text-2xl font-bold mb-6"></h2>
{/* 服务器信息 */}
<div className="mb-4 p-3 bg-gray-100 rounded-lg">
<p className="text-sm text-gray-600">
: <span className="font-mono">{serverUrl}</span>
</p>
</div>
{/* 拖拽上传区域 */}
<div
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
dragOver ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
}`}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
>
<div className="space-y-4">
<div className="text-gray-500">
<svg className="mx-auto h-12 w-12" stroke="currentColor" fill="none" viewBox="0 0 48 48">
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
<div>
<p className="text-lg font-medium text-gray-900"></p>
<label className="cursor-pointer">
<span className="mt-2 block text-sm font-medium text-blue-600 hover:text-blue-500"></span>
<input type="file" multiple className="hidden" onChange={handleInputChange} disabled={isUploading} />
</label>
</div>
<p className="text-xs text-gray-500">TUS </p>
</div>
</div>
{/* 上传进度 */}
{isUploading && (
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
<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">{uploadProgress}%</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: `${uploadProgress}%` }}
/>
</div>
</div>
)}
{/* 错误信息 */}
{uploadError && (
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-sm text-red-600">
<span className="font-medium"></span>
{uploadError}
</p>
</div>
)}
{/* 已上传文件列表 */}
{uploadedFiles.length > 0 && (
<div className="mt-6">
<h3 className="text-lg font-medium mb-4"></h3>
<div className="space-y-3">
{uploadedFiles.map((file, index) => (
<div
key={index}
className="flex items-center justify-between p-4 bg-green-50 border border-green-200 rounded-lg"
>
<div className="flex items-center space-x-3">
<div className="flex-shrink-0">
<svg className="h-8 w-8 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div>
<p className="text-sm font-medium text-gray-900">{file.fileName}</p>
<p className="text-xs text-gray-500">ID: {file.fileId}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
>
</a>
<button
onClick={() => copyToClipboard(file.url)}
className="text-gray-600 hover:text-gray-800 text-sm font-medium"
>
</button>
</div>
</div>
))}
</div>
</div>
)}
{/* 使用说明 */}
<div className="mt-8 p-4 bg-gray-50 rounded-lg">
<h4 className="text-sm font-medium text-gray-900 mb-2">使</h4>
<ul className="text-xs text-gray-600 space-y-1">
<li> </li>
<li> 使 TUS </li>
<li> 访</li>
<li> PDF </li>
<li> </li>
</ul>
</div>
</div>
);
}