01252122
This commit is contained in:
parent
a2da55bd9e
commit
30968f4fab
|
@ -1,11 +1,12 @@
|
|||
import {
|
||||
EyeOutlined,
|
||||
LikeOutlined,
|
||||
LikeFilled,
|
||||
UserOutlined,
|
||||
BankOutlined,
|
||||
CalendarOutlined,
|
||||
FileTextOutlined,
|
||||
EyeOutlined,
|
||||
LikeOutlined,
|
||||
LikeFilled,
|
||||
UserOutlined,
|
||||
BankOutlined,
|
||||
CalendarOutlined,
|
||||
FileTextOutlined,
|
||||
SendOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Button, Typography, Space, Tooltip } from "antd";
|
||||
import toast from "react-hot-toast";
|
||||
|
@ -13,146 +14,115 @@ import { useState } from "react";
|
|||
import { getBadgeStyle } from "@web/src/app/main/letter/list/utils";
|
||||
import { PostDto } from "@nice/common";
|
||||
import dayjs from "dayjs";
|
||||
import PostLikeButton from "./detail/PostHeader/PostLikeButton";
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
interface LetterCardProps {
|
||||
letter: PostDto;
|
||||
letter: PostDto;
|
||||
}
|
||||
|
||||
export function LetterCard({ letter }: LetterCardProps) {
|
||||
const [likes, setLikes] = useState(0);
|
||||
const [liked, setLiked] = useState(false);
|
||||
const [views] = useState(Math.floor(Math.random() * 100)); // 模拟浏览量数据
|
||||
|
||||
const handleLike = () => {
|
||||
if (!liked) {
|
||||
setLikes((prev) => prev + 1);
|
||||
setLiked(true);
|
||||
toast.success("已点赞!", {
|
||||
icon: <LikeFilled className="text-blue-500" />,
|
||||
className: "custom-message",
|
||||
});
|
||||
} else {
|
||||
setLikes((prev) => prev - 1);
|
||||
setLiked(false);
|
||||
toast("已取消点赞", {
|
||||
className: "custom-message",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full p-4 bg-white transition-all duration-300 ease-in-out group">
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Title & Priority */}
|
||||
<div className="flex justify-between items-start">
|
||||
<Title level={4} className="!mb-0 flex-1">
|
||||
<a
|
||||
href={`/letters/${letter.id}`}
|
||||
target="_blank"
|
||||
className="text-primary transition-all duration-300 relative
|
||||
|
||||
return (
|
||||
<div className="w-full border-2 p-4 bg-white rounded-xl transition-all duration-300 ease-in-out group">
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Title & Priority */}
|
||||
<div className="flex justify-between items-start">
|
||||
<Title level={4} className="!mb-0 flex-1">
|
||||
<a
|
||||
href={`/letters/${letter.id}`}
|
||||
target="_blank"
|
||||
className="text-primary transition-all duration-300 relative
|
||||
before:absolute before:bottom-0 before:left-0 before:w-0 before:h-[2px] before:bg-primary-600
|
||||
group-hover:before:w-full before:transition-all before:duration-300
|
||||
group-hover:text-primary-600 group-hover:scale-105 group-hover:drop-shadow-md">
|
||||
{letter.title}
|
||||
</a>
|
||||
</Title>
|
||||
</div>
|
||||
{letter.title}
|
||||
</a>
|
||||
</Title>
|
||||
</div>
|
||||
|
||||
{/* Meta Info */}
|
||||
<div className="flex justify-between items-center text-sm text-secondary">
|
||||
<Space size="middle">
|
||||
<Space>
|
||||
<UserOutlined className="text-secondary-400" />
|
||||
<Text strong>
|
||||
{letter.author?.showname ||
|
||||
letter?.author?.username}
|
||||
</Text>
|
||||
</Space>
|
||||
<Text type="secondary">|</Text>
|
||||
<Space>
|
||||
<BankOutlined className="text-secondary-400" />
|
||||
<Text>{letter.author?.department?.name}</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
<Space>
|
||||
<CalendarOutlined className="text-secondary-400" />
|
||||
<Text type="secondary">
|
||||
{dayjs(letter.createdAt).format("YYYY-MM-DD")}
|
||||
</Text>
|
||||
</Space>
|
||||
</div>
|
||||
{/* Meta Info */}
|
||||
<div className="flex justify-between items-center text-sm text-secondary">
|
||||
<Space size="middle">
|
||||
<Space>
|
||||
<UserOutlined className=" text-secondary-400"></UserOutlined>
|
||||
<Text>
|
||||
{letter.author?.showname || '匿名用户'}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<BankOutlined className="text-secondary-400" />
|
||||
<Text>{letter.receivers.map(item => item.department?.name).toString()}</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<SendOutlined className=" text-secondary-400"></SendOutlined>
|
||||
<Text >
|
||||
{letter.receivers.map(item => item.showname).toString()}
|
||||
</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
<Space>
|
||||
<CalendarOutlined className="text-secondary-400" />
|
||||
<Text type="secondary">
|
||||
{dayjs(letter.createdAt).format("YYYY-MM-DD")}
|
||||
</Text>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* Content Preview */}
|
||||
{letter.content && (
|
||||
<div className="flex items-start gap-2">
|
||||
<FileTextOutlined className="text-gray-400 mt-1" />
|
||||
<Paragraph
|
||||
ellipsis={{ rows: 2 }}
|
||||
className="!mb-3 text-gray-600 flex-1">
|
||||
{letter.content}
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
{/* Content Preview */}
|
||||
{letter.content && (
|
||||
<div className="flex items-start gap-2">
|
||||
<FileTextOutlined className="text-gray-400 mt-1" />
|
||||
<Paragraph
|
||||
ellipsis={{ rows: 2 }}
|
||||
className="!mb-3 text-gray-600 flex-1">
|
||||
{letter.content}
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Badges & Interactions */}
|
||||
<div className="flex justify-between items-center">
|
||||
<Space size="small" wrap className="flex-1">
|
||||
<Badge type="category" value={"11"} />
|
||||
<Badge type="status" value={"22"} />
|
||||
</Space>
|
||||
{/* Badges & Interactions */}
|
||||
<div className="flex justify-between items-center">
|
||||
<Space size="small" wrap className="flex-1">
|
||||
<Badge type="category" value={"11"} />
|
||||
<Badge type="status" value={"22"} />
|
||||
</Space>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-1 text-gray-500">
|
||||
<EyeOutlined className="text-lg" />
|
||||
<span className="text-sm">{views}</span>
|
||||
</div>
|
||||
<Tooltip
|
||||
title={liked ? "取消点赞" : "点赞"}
|
||||
placement="top">
|
||||
<Button
|
||||
type={liked ? "primary" : "default"}
|
||||
shape="round"
|
||||
size="small"
|
||||
icon={liked ? <LikeFilled /> : <LikeOutlined />}
|
||||
onClick={handleLike}
|
||||
className={`
|
||||
flex items-center gap-1 px-3 transform transition-all duration-300
|
||||
hover:scale-105 hover:shadow-md
|
||||
${liked ? "bg-blue-500 hover:bg-blue-600" : "hover:border-blue-500 hover:text-blue-500"}
|
||||
`}>
|
||||
<span className={liked ? "text-white" : ""}>
|
||||
{likes}
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-1 text-gray-500">
|
||||
<EyeOutlined className="text-lg" />
|
||||
<span className="text-sm">{letter.views}</span>
|
||||
</div>
|
||||
<PostLikeButton post={letter as any}></PostLikeButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Badge({
|
||||
type,
|
||||
value,
|
||||
className = "",
|
||||
type,
|
||||
value,
|
||||
className = "",
|
||||
}: {
|
||||
type: "priority" | "category" | "status";
|
||||
value: string;
|
||||
className?: string;
|
||||
type: "priority" | "category" | "status";
|
||||
value: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
value && (
|
||||
<span
|
||||
className={`
|
||||
return (
|
||||
value && (
|
||||
<span
|
||||
className={`
|
||||
inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
${getBadgeStyle(type, value)}
|
||||
transition-all duration-200 ease-in-out transform hover:scale-105
|
||||
${className}
|
||||
`}>
|
||||
{value?.toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
);
|
||||
{value?.toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import { useContext } from "react";
|
|||
import { PostDetailContext } from "../context/PostDetailContext";
|
||||
import { Button, Tooltip } from "antd";
|
||||
import { LikeFilled, LikeOutlined } from "@ant-design/icons";
|
||||
import { useAuth } from "@web/src/providers/auth-provider";
|
||||
|
||||
export default function PostLikeButton({ post }: { post: PostDto }) {
|
||||
const { user } = useContext(PostDetailContext);
|
||||
const { user } = useAuth();
|
||||
const { like, unLike } = useVisitor();
|
||||
|
||||
function likeThisPost() {
|
||||
if (!post?.liked) {
|
||||
post.likes += 1;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { api, usePost } from "@nice/client";
|
|||
import toast from "react-hot-toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { PostState, PostType } from "@nice/common";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export interface LetterFormData {
|
||||
title: string;
|
||||
|
@ -70,18 +71,34 @@ export function LetterFormProvider({
|
|||
isPublic: data?.isPublic,
|
||||
resources: data.resources?.length
|
||||
? {
|
||||
connect: (
|
||||
data.resources?.filter(Boolean) || []
|
||||
).map((fileId) => ({
|
||||
fileId,
|
||||
})),
|
||||
}
|
||||
connect: (
|
||||
data.resources?.filter(Boolean) || []
|
||||
).map((fileId) => ({
|
||||
fileId,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
// console.log(123);
|
||||
const formattedDateTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
// 创建包含信件编号和提交时间的文本
|
||||
const fileContent = `信件编号: ${result.id}\n投递时间: ${formattedDateTime}`;
|
||||
// 创建包含信件编号和提交时间的Blob对象
|
||||
const blob = new Blob([fileContent], { type: 'text/plain' });
|
||||
// 创建下载链接
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.download = `信件编号-${result.id}.txt`; // 设置下载文件名
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
|
||||
toast.success(`信件投递成功!信件编号已保存到本地,请妥善保管用于进度查询`, {
|
||||
duration: 5000 // 10秒
|
||||
});
|
||||
navigate(`/${result.id}/detail`, { replace: true });
|
||||
toast.success("发送成功!");
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
console.error("Error submitting form:", error);
|
||||
|
|
|
@ -5,6 +5,7 @@ import { LetterCard } from "../LetterCard";
|
|||
import { NonVoid } from "@nice/utils";
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { postDetailSelect } from '@nice/common';
|
||||
export default function LetterList({ params }: { params: NonVoid<RouterInputs["post"]["findManyWithPagination"]> }) {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
@ -20,9 +21,14 @@ export default function LetterList({ params }: { params: NonVoid<RouterInputs["p
|
|||
}],
|
||||
...params?.where
|
||||
},
|
||||
select: params.select
|
||||
select: {
|
||||
...postDetailSelect,
|
||||
...params.select
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(data)
|
||||
}, [data])
|
||||
// Debounced search function
|
||||
const debouncedSearch = useMemo(
|
||||
() =>
|
||||
|
@ -73,7 +79,7 @@ export default function LetterList({ params }: { params: NonVoid<RouterInputs["p
|
|||
</div>
|
||||
) : data?.items.length ? (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
{data.items.map((letter: any) => (
|
||||
<LetterCard key={letter.id} letter={letter} />
|
||||
))}
|
||||
|
|
|
@ -192,11 +192,7 @@ model Post {
|
|||
state String? // 状态 : 未读、处理中、已回答
|
||||
title String? // 帖子标题,可为空
|
||||
content String? // 帖子内容,可为空
|
||||
|
||||
domainId String? @map("domain_id")
|
||||
// term Term? @relation(fields: [termId], references: [id])
|
||||
// termId String? @map("term_id")
|
||||
// 添加多对多关系
|
||||
terms Term[] @relation("post_term")
|
||||
// 日期时间类型字段
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
@ -208,7 +204,6 @@ model Post {
|
|||
visits Visit[] // 访问记录,关联 Visit 模型
|
||||
views Int @default(0)
|
||||
likes Int @default(0)
|
||||
|
||||
receivers Staff[] @relation("post_receiver")
|
||||
parentId String? @map("parent_id")
|
||||
parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型
|
||||
|
|
|
@ -12,11 +12,9 @@ export const postDetailSelect: Prisma.PostSelect = {
|
|||
resources: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
|
||||
|
||||
terms: {
|
||||
include: {
|
||||
|
||||
},
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
authorId: true,
|
||||
author: {
|
||||
|
|
Loading…
Reference in New Issue