116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import React, { useMemo } from "react";
|
|
import { Image, Button, Row, Col } from "antd";
|
|
import { DownloadOutlined } from "@ant-design/icons";
|
|
import { PostDto } from "@nice/common";
|
|
import { env } from "@web/src/env";
|
|
import { getFileIcon } from "./utils";
|
|
|
|
export default function PostResources({ post }: { post: PostDto }) {
|
|
const { resources } = useMemo(() => {
|
|
if (!post?.resources) return { resources: [] };
|
|
|
|
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
|
|
|
|
const sortedResources = post.resources
|
|
.map((resource) => ({
|
|
...resource,
|
|
url: `http://${env.SERVER_IP}/uploads/${resource.url}`,
|
|
isImage: isImage(resource.url),
|
|
}))
|
|
.sort((a, b) => (a.isImage === b.isImage ? 0 : a.isImage ? -1 : 1));
|
|
|
|
return { resources: sortedResources };
|
|
}, [post]);
|
|
|
|
const imageResources = resources.filter((res) => res.isImage);
|
|
const fileResources = resources.filter((res) => !res.isImage);
|
|
|
|
return (
|
|
<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>
|
|
)}
|
|
</div>
|
|
);
|
|
} |