casualroom/apps/fenghuo/web/app/[locale]/dashboard/resource/page.tsx

281 lines
14 KiB
TypeScript
Executable File

'use client';
import { useTranslation } from '@nice/i18n';
import { useSetPageInfo } from '@/components/providers/dashboard-provider';
import FileUpload from '@/components/common/file-upload';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '@fenghuo/client';
import { useMemo, useState } from 'react';
import { useAuth } from '@/components/providers/auth-provider';
import { Card, CardContent } from '@nice/ui/components/card';
import { Button } from '@nice/ui/components/button';
import { Folder, FileImage, Copy, Download } from 'lucide-react';
import { toast } from '@nice/ui/components/sonner';
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
PaginationEllipsis,
} from '@nice/ui/components/pagination';
interface Resource {
id: string;
title: string | null;
url: string | null;
type: string | null;
[key: string]: any;
}
export default function ProfilePage() {
const { t } = useTranslation();
const { user, isSuperAdmin, isAdmin } = useAuth();
const trpc = useTRPC();
const queryClient = useQueryClient();
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(14);
// 设置页面信息
useSetPageInfo({
title: "文件媒体库",
subtitle: '上传文件资源、音视频资源、图片资源,并进行管理'
});
const searchConditions = {
ownerId: isAdmin() || isSuperAdmin() ? undefined : user?.id,
};
const { data: resources, isLoading: resourcesLoading } = useQuery({
...trpc.resource.findManyWithPagination.queryOptions({
page,
pageSize,
where: {
...searchConditions,
},
orderBy: { createdAt: 'desc' }
}),
});
const resourcesData = useMemo(() => {
return (resources?.items || []) as Resource[];
}, [resources]);
const isImageFile = (filename: string | null) => {
if (!filename) return false;
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
return imageExtensions.some(ext => filename.toLowerCase().endsWith(ext));
};
const handleCopyLink = async (url: string | null, e: React.MouseEvent) => {
e.stopPropagation();
if (!url) {
toast("文件链接不存在");
return;
}
try {
await navigator.clipboard.writeText(url);
toast("复制成功");
} catch (err) {
toast("复制链接失败");
}
};
const handleDownload = (url: string | null, filename: string | null, e: React.MouseEvent) => {
e.stopPropagation();
if (!url) {
toast("文件链接不存在");
return;
}
const link = document.createElement('a');
link.href = url;
link.download = filename || '下载文件';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
toast("文件正在下载...");
};
const handleUploadComplete = async () => await queryClient.invalidateQueries({ queryKey: trpc.resource.pathKey() });
return (
<div className="flex flex-col h-full">
{/* 主要内容区域 */}
<div className="flex-1 overflow-hidden">
<div className='m-4 p-4'>
<FileUpload
multiple
maxSize={10 * 1024 * 1024 * 1024}
onUploadComplete={handleUploadComplete}
/>
</div>
<div className="px-4 pb-4">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 gap-3">
{resourcesData.map((resource) => (
<Card key={resource.id} className="h-48 !p-0 aspect-square hover:shadow-lg transition-shadow cursor-pointer group">
<CardContent className="p-0 h-full flex flex-col items-center justify-center relative">
{isImageFile(resource.title) && resource.url && resource.type === 'image' ? (
<div className="w-full h-full overflow-hidden rounded-lg">
<img
src={resource.url}
alt={resource.title || '未命名文件'}
className="w-full h-full object-cover transition-transform duration-200 hover:scale-105"
loading="lazy"
/>
</div>
) : (
<div className="flex flex-col items-center justify-center text-center h-full">
{resource.type === 'image' ? (
<FileImage className="w-16 h-16 text-muted-foreground/60 mb-3" />
) : (
<Folder className="w-16 h-16 text-muted-foreground/60 mb-3" />
)}
<span className="text-xs font-medium text-foreground/80 truncate max-w-full leading-tight text-wrap ">
{resource.title || '未命名文件'}
</span>
</div>
)}
{/* 悬停时显示的按钮覆盖层 */}
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center gap-2 rounded-lg">
<Button
size="sm"
variant="secondary"
className="h-8 w-8 p-0"
onClick={(e) => handleCopyLink(resource.url, e)}
title="复制下载链接"
>
<Copy className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="secondary"
className="h-8 w-8 p-0"
onClick={(e) => handleDownload(resource.url, resource.title, e)}
title="下载文件"
>
<Download className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
))}
</div>
{/* 分页组件 */}
{resources && resources.totalPages > 1 && (
<div className="flex justify-center mt-6">
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (page > 1) setPage(page - 1);
}}
className={page <= 1 ? 'pointer-events-none opacity-50' : 'cursor-pointer'}
/>
</PaginationItem>
{/* 页码显示逻辑 */}
{(() => {
const totalPages = resources.totalPages;
const currentPage = page;
const pages: React.ReactNode[] = [];
// 显示第一页
if (currentPage > 3) {
pages.push(
<PaginationItem key={1}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setPage(1);
}}
className="cursor-pointer"
>
1
</PaginationLink>
</PaginationItem>
);
if (currentPage > 4) {
pages.push(
<PaginationItem key="ellipsis1">
<PaginationEllipsis />
</PaginationItem>
);
}
}
// 显示当前页附近的页码
for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) {
pages.push(
<PaginationItem key={i}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setPage(i);
}}
isActive={i === currentPage}
className="cursor-pointer"
>
{i}
</PaginationLink>
</PaginationItem>
);
}
// 显示最后一页
if (currentPage < totalPages - 2) {
if (currentPage < totalPages - 3) {
pages.push(
<PaginationItem key="ellipsis2">
<PaginationEllipsis />
</PaginationItem>
);
}
pages.push(
<PaginationItem key={totalPages}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setPage(totalPages);
}}
className="cursor-pointer"
>
{totalPages}
</PaginationLink>
</PaginationItem>
);
}
return pages;
})()}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (page < resources.totalPages) setPage(page + 1);
}}
className={page >= resources.totalPages ? 'pointer-events-none opacity-50' : 'cursor-pointer'}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)}
</div>
</div>
</div>
);
}