168 lines
5.4 KiB
TypeScript
168 lines
5.4 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import Image from 'next/image';
|
|
import { MoreHorizontal, Eye, EyeOff, Trash2, Download, Play } from 'lucide-react';
|
|
import { File } from '@/types/file';
|
|
import { formatFileSize, formatDate, isImageFile, isVideoFile } from '@/lib/utils';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@/components/ui/dialog';
|
|
import { useFiles } from '@/hooks/useFiles';
|
|
|
|
interface FileCardProps {
|
|
file: File;
|
|
isAdmin?: boolean;
|
|
}
|
|
|
|
export const FileCard: React.FC<FileCardProps> = ({ file, isAdmin = false }) => {
|
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
const { deleteFile, toggleFileVisibility } = useFiles();
|
|
|
|
const handleDelete = async () => {
|
|
if (confirm('确定要删除这个文件吗?')) {
|
|
await deleteFile(file.id);
|
|
}
|
|
};
|
|
|
|
const handleToggleVisibility = async () => {
|
|
await toggleFileVisibility(file.id, !file.isVisible);
|
|
};
|
|
|
|
const handleDownload = () => {
|
|
const link = document.createElement('a');
|
|
link.href = file.filePath;
|
|
link.download = file.originalName;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
};
|
|
|
|
const isImage = isImageFile(file.mimeType);
|
|
const isVideo = isVideoFile(file.mimeType);
|
|
|
|
return (
|
|
<div className={`file-card ${!file.isVisible ? 'opacity-50' : ''}`}>
|
|
<div className="aspect-square relative overflow-hidden bg-muted">
|
|
{isImage ? (
|
|
<Image
|
|
src={file.filePath}
|
|
alt={file.originalName}
|
|
fill
|
|
className="object-cover"
|
|
onClick={() => setPreviewOpen(true)}
|
|
/>
|
|
) : isVideo ? (
|
|
<div className="relative w-full h-full flex items-center justify-center">
|
|
<video
|
|
src={file.filePath}
|
|
className="w-full h-full object-cover"
|
|
muted
|
|
onClick={() => setPreviewOpen(true)}
|
|
/>
|
|
<div className="absolute inset-0 bg-black/20 flex items-center justify-center">
|
|
<Play className="w-12 h-12 text-white/80" />
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center">
|
|
<div className="text-center">
|
|
<div className="w-12 h-12 mx-auto mb-2 bg-muted rounded flex items-center justify-center">
|
|
<span className="text-2xl">📄</span>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">文件</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!file.isVisible && (
|
|
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
|
|
<EyeOff className="w-8 h-8 text-white" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<div className="flex items-start justify-between mb-2">
|
|
<h3 className="font-medium text-sm line-clamp-2 flex-1 mr-2">
|
|
{file.originalName}
|
|
</h3>
|
|
{isAdmin && (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem onClick={handleToggleVisibility}>
|
|
{file.isVisible ? (
|
|
<>
|
|
<EyeOff className="mr-2 h-4 w-4" />
|
|
隐藏文件
|
|
</>
|
|
) : (
|
|
<>
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
显示文件
|
|
</>
|
|
)}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={handleDownload}>
|
|
<Download className="mr-2 h-4 w-4" />
|
|
下载文件
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={handleDelete} className="text-destructive">
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
删除文件
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-1 text-xs text-muted-foreground">
|
|
<p>{formatFileSize(file.fileSize)}</p>
|
|
<p>{formatDate(file.uploadedAt)}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preview Dialog */}
|
|
<Dialog open={previewOpen} onOpenChange={setPreviewOpen}>
|
|
<DialogContent className="max-w-4xl">
|
|
<DialogHeader>
|
|
<DialogTitle>{file.originalName}</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="flex justify-center">
|
|
{isImage ? (
|
|
<Image
|
|
src={file.filePath}
|
|
alt={file.originalName}
|
|
width={800}
|
|
height={600}
|
|
className="max-w-full max-h-[600px] object-contain"
|
|
/>
|
|
) : isVideo ? (
|
|
<video
|
|
src={file.filePath}
|
|
controls
|
|
className="max-w-full max-h-[600px]"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}; |