219 lines
6.5 KiB
TypeScript
219 lines
6.5 KiB
TypeScript
'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>
|
||
);
|
||
}
|