training_data/apps/web/src/components/common/uploader/ResourceShower.tsx

137 lines
4.2 KiB
TypeScript
Executable File

import React, { useMemo } from "react";
import { Image, Button, Row, Col, Tooltip } from "antd";
import { ResourceDto } from "@nice/common";
import { env } from "@web/src/env";
import { getFileIcon } from "./utils";
import { formatFileSize, getCompressedImageUrl } from "@nice/utils";
export default function ResourcesShower({
resources = [],
}: {
resources: ResourceDto[];
}) {
const { resources: dealedResources } = useMemo(() => {
if (!resources) return { resources: [] };
const isImage = (url: string) =>
/\.(png|jpg|jpeg|gif|webp)$/i.test(url);
const sortedResources = resources
.map((resource) => {
const original = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${resource.url}`;
const isImg = isImage(resource.url);
return {
...resource,
url: isImg ? getCompressedImageUrl(original) : original,
originalUrl: original,
isImage: isImg,
};
})
.sort((a, b) => (a.isImage === b.isImage ? 0 : a.isImage ? -1 : 1));
return { resources: sortedResources };
}, [resources]);
const imageResources = dealedResources.filter((res) => res.isImage);
const fileResources = dealedResources.filter((res) => !res.isImage);
return (
<div className="space-y-3">
{imageResources.length > 0 && (
<Row gutter={[16, 16]} className="mb-6">
<Image.PreviewGroup>
{imageResources.map((resource) => (
<Col
key={resource.url}
xs={12}
sm={8}
md={6}
lg={6}
xl={4}
className="relative">
<div className="relative aspect-square rounded-lg overflow-hidden bg-gray-100">
<div className="w-full h-full">
<Image
src={resource.url}
alt={resource.title}
preview={{
src: resource.originalUrl,
mask: (
<div className="flex items-center justify-center text-white">
</div>
),
}}
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
objectFit: "cover",
}}
rootClassName="w-full h-full"
/>
</div>
{resource.title && (
<div className="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/60 to-transparent text-white text-sm truncate">
{resource.title}
</div>
)}
</div>
</Col>
))}
</Image.PreviewGroup>
</Row>
)}
<div className=" text-sm px-2">:</div>
{fileResources.length > 0 && (
<div className="rounded-xl p-1 border border-gray-100 bg-white">
<div className="flex flex-nowrap overflow-x-auto scrollbar-hide gap-1.5">
{fileResources.map((resource) => {
return (
<a
key={resource.url}
className="flex-shrink-0 relative active:scale-95 transition-transform select-none "
href={resource.originalUrl}
target="_blank"
download={true}
title="点击下载文件">
{/* 超紧凑卡片容器 */}
<div className="w-[120px] h-[80px] p-2 flex flex-col items-center justify-between rounded-xl hover:bg-primary-50/40 cursor-pointer">
{/* 微型文件图标 */}
<div className="text-primary-600 text-base">
{getFileIcon(resource.url)}
</div>
{/* 压缩信息展示 */}
<div className="w-full text-center space-y-0.5">
<p className="text-xs font-medium text-gray-800 truncate">
{resource.title?.slice(0, 12) ||
"未命名"}
</p>
<div className="flex items-center justify-between text-xs text-gray-500 ">
<span className="bg-gray-100 px-0.5 rounded-sm mr-2 whitespace-pre-wrap">
{resource.url
.split(".")
.pop()
?.slice(0, 4)
.toUpperCase()}
</span>
<span className="flex bg-gray-100 px-0.5 rounded-sm justify-items-center whitespace-pre-wrap">
{resource.meta.size &&
formatFileSize(
resource.meta.size
)}
</span>
</div>
</div>
</div>
</a>
);
})}
</div>
</div>
)}
</div>
);
}