- 实现 FilePreviewItem 展示单个文件信息 - 根据文件类型显示对应图标和颜色 - 图片文件显示缩略图预览 - 显示文件名、大小和上传状态 - 支持上传进度条展示 - 提供悬浮删除按钮
148 lines
4.2 KiB
TypeScript
148 lines
4.2 KiB
TypeScript
'use client';
|
|
|
|
import { X, File, FileImage, FileText, FileSpreadsheet, FileCode, FileArchive, Loader2 } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import { UploadFile, FileType, formatFileSize } from '@/types/file-upload';
|
|
import Image from 'next/image';
|
|
|
|
interface FilePreviewListProps {
|
|
files: UploadFile[];
|
|
onRemove: (fileId: string) => void;
|
|
className?: string;
|
|
}
|
|
|
|
// 根据文件类型获取图标
|
|
function getFileIcon(fileType: FileType) {
|
|
switch (fileType) {
|
|
case 'image':
|
|
return FileImage;
|
|
case 'pdf':
|
|
case 'document':
|
|
return FileText;
|
|
case 'spreadsheet':
|
|
return FileSpreadsheet;
|
|
case 'code':
|
|
case 'markdown':
|
|
return FileCode;
|
|
case 'archive':
|
|
return FileArchive;
|
|
default:
|
|
return File;
|
|
}
|
|
}
|
|
|
|
// 根据文件类型获取颜色
|
|
function getFileColor(fileType: FileType): string {
|
|
switch (fileType) {
|
|
case 'image':
|
|
return 'text-blue-500';
|
|
case 'pdf':
|
|
return 'text-red-500';
|
|
case 'document':
|
|
return 'text-blue-600';
|
|
case 'spreadsheet':
|
|
return 'text-green-500';
|
|
case 'code':
|
|
case 'markdown':
|
|
return 'text-purple-500';
|
|
case 'archive':
|
|
return 'text-orange-500';
|
|
default:
|
|
return 'text-gray-500';
|
|
}
|
|
}
|
|
|
|
interface FilePreviewItemProps {
|
|
file: UploadFile;
|
|
onRemove: (fileId: string) => void;
|
|
}
|
|
|
|
function FilePreviewItem({ file, onRemove }: FilePreviewItemProps) {
|
|
const Icon = getFileIcon(file.type);
|
|
const colorClass = getFileColor(file.type);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'relative group flex items-center gap-2 p-2 rounded-lg border transition-all',
|
|
'bg-[var(--color-bg-secondary)] border-[var(--color-border)]',
|
|
'hover:border-[var(--color-border-hover)]',
|
|
file.status === 'error' && 'border-red-500 bg-red-50 dark:bg-red-950/20'
|
|
)}
|
|
>
|
|
{/* 文件预览/图标 */}
|
|
<div className="flex-shrink-0 w-10 h-10 rounded-lg overflow-hidden bg-[var(--color-bg-tertiary)] flex items-center justify-center">
|
|
{file.type === 'image' && file.previewUrl ? (
|
|
<Image
|
|
src={file.previewUrl}
|
|
alt={file.name}
|
|
width={40}
|
|
height={40}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
) : (
|
|
<Icon className={cn('w-5 h-5', colorClass)} />
|
|
)}
|
|
</div>
|
|
|
|
{/* 文件信息 */}
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-[var(--color-text-primary)] truncate" title={file.name}>
|
|
{file.name}
|
|
</p>
|
|
<p className="text-xs text-[var(--color-text-tertiary)]">
|
|
{formatFileSize(file.size)}
|
|
{file.status === 'error' && file.error && (
|
|
<span className="text-red-500 ml-2">{file.error}</span>
|
|
)}
|
|
</p>
|
|
</div>
|
|
|
|
{/* 状态指示器 */}
|
|
{file.status === 'uploading' && (
|
|
<div className="flex-shrink-0">
|
|
<Loader2 className="w-4 h-4 animate-spin text-[var(--color-primary)]" />
|
|
</div>
|
|
)}
|
|
|
|
{/* 进度条 */}
|
|
{file.status === 'uploading' && (
|
|
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gray-200 dark:bg-gray-700 rounded-b-lg overflow-hidden">
|
|
<div
|
|
className="h-full bg-[var(--color-primary)] transition-all duration-300"
|
|
style={{ width: `${file.uploadProgress}%` }}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* 删除按钮 */}
|
|
<button
|
|
onClick={() => onRemove(file.id)}
|
|
className={cn(
|
|
'flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center',
|
|
'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]',
|
|
'hover:bg-[var(--color-bg-hover)] transition-colors',
|
|
'opacity-0 group-hover:opacity-100'
|
|
)}
|
|
title="移除文件"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function FilePreviewList({ files, onRemove, className }: FilePreviewListProps) {
|
|
if (files.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className={cn('flex flex-wrap gap-2', className)}>
|
|
{files.map((file) => (
|
|
<FilePreviewItem key={file.id} file={file} onRemove={onRemove} />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|