doctor-mail/apps/web/src/components/models/post/detail/PostResources.tsx

116 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-01-26 12:48:10 +08:00
import React, { useMemo } from "react";
import { Image, Button, Row, Col } from "antd";
import { DownloadOutlined } from "@ant-design/icons";
2025-01-25 21:20:54 +08:00
import { PostDto } from "@nice/common";
2025-01-26 12:48:10 +08:00
import { env } from "@web/src/env";
import { getFileIcon } from "./utils";
2025-01-25 19:51:08 +08:00
export default function PostResources({ post }: { post: PostDto }) {
2025-01-26 12:48:10 +08:00
const { resources } = useMemo(() => {
if (!post?.resources) return { resources: [] };
2025-01-25 21:20:54 +08:00
2025-01-26 12:48:10 +08:00
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
2025-01-25 19:51:08 +08:00
2025-01-26 12:48:10 +08:00
const sortedResources = post.resources
2025-01-25 21:20:54 +08:00
.map((resource) => ({
2025-01-26 12:48:10 +08:00
...resource,
url: `http://${env.SERVER_IP}/uploads/${resource.url}`,
2025-01-25 21:20:54 +08:00
isImage: isImage(resource.url),
}))
2025-01-26 12:48:10 +08:00
.sort((a, b) => (a.isImage === b.isImage ? 0 : a.isImage ? -1 : 1));
return { resources: sortedResources };
2025-01-25 21:20:54 +08:00
}, [post]);
2025-01-25 19:51:08 +08:00
2025-01-26 12:48:10 +08:00
const imageResources = resources.filter((res) => res.isImage);
const fileResources = resources.filter((res) => !res.isImage);
2025-01-25 19:51:08 +08:00
return (
2025-01-26 12:48:10 +08:00
<div className="space-y-6">
{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 shadow-md hover:shadow-lg transition-shadow duration-300 bg-gray-100">
<div className="w-full h-full">
<Image
src={resource.url}
alt={resource.title}
preview={{
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>
)}
{fileResources.length > 0 && (
<div className="rounded-xl p-4 bg-gray-200">
<div className="space-y-2">
{fileResources.map((resource) => (
<div
key={resource.url}
className="flex items-center justify-between p-3 hover:bg-gray-50 rounded-md transition-colors duration-200"
>
<div className="flex items-center space-x-3 min-w-0">
<span className="text-xl">{getFileIcon(resource.url)}</span>
<div className="min-w-0">
<p className="text-gray-800 truncate">
{resource.title || "未命名文件"}
</p>
<div className="flex items-center space-x-2">
<span className="text-xs text-gray-500">
{resource.url.split(".").pop()?.toUpperCase()}
</span>
<span className="text-xs text-gray-400">
{resource.metadata.size &&
`${(resource.metadata.size / 1024 / 1024).toFixed(1)}MB`}
</span>
</div>
</div>
</div>
<Button
icon={<DownloadOutlined />}
href={resource.url}
download
>
</Button>
</div>
))}
</div>
</div>
)}
2025-01-25 19:51:08 +08:00
</div>
);
2025-01-26 12:48:10 +08:00
}