2025-01-24 00:19:02 +08:00
|
|
|
import { useContext } from "react";
|
|
|
|
import { PostDetailContext } from "./context/PostDetailContext";
|
|
|
|
import { motion } from "framer-motion";
|
|
|
|
import {
|
|
|
|
CalendarIcon,
|
|
|
|
UserCircleIcon,
|
|
|
|
LockClosedIcon,
|
|
|
|
LockOpenIcon,
|
|
|
|
StarIcon,
|
|
|
|
ClockIcon,
|
|
|
|
EyeIcon,
|
|
|
|
ChatBubbleLeftIcon,
|
|
|
|
} from "@heroicons/react/24/outline";
|
|
|
|
import { format } from "date-fns";
|
|
|
|
import { useVisitor } from "@nice/client";
|
|
|
|
import { VisitType } from "@nice/common";
|
|
|
|
|
|
|
|
export default function PostHeader() {
|
|
|
|
const { post, user } = useContext(PostDetailContext);
|
|
|
|
const { like } = useVisitor();
|
|
|
|
|
|
|
|
function likeThisPost() {
|
|
|
|
if (!post?.liked) {
|
|
|
|
like.mutateAsync({
|
|
|
|
data: {
|
|
|
|
visitorId: user?.id || null,
|
|
|
|
postId: post.id,
|
|
|
|
type: VisitType.LIKE,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, y: -20 }}
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
transition={{ duration: 0.5 }}
|
|
|
|
className="relative bg-gradient-to-br from-[#E6E9F0] via-[#EDF0F8] to-[#D8E2EF] rounded-lg p-6 shadow-lg border border-[#97A9C4]/30">
|
|
|
|
{/* Corner Decorations */}
|
|
|
|
<div className="absolute top-0 left-0 w-5 h-5 border-t-2 border-l-2 border-[#97A9C4] rounded-tl-lg" />
|
|
|
|
<div className="absolute bottom-0 right-0 w-5 h-5 border-b-2 border-r-2 border-[#97A9C4] rounded-br-lg" />
|
|
|
|
|
|
|
|
{/* Title Section */}
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
transition={{ delay: 0.2 }}
|
|
|
|
className="relative mb-6">
|
|
|
|
<div className="absolute -left-2 top-1/2 -translate-y-1/2 w-1 h-8 bg-[#97A9C4]" />
|
|
|
|
<h1 className="text-2xl font-bold text-[#2B4C7E] pl-4 tracking-wider uppercase">
|
|
|
|
{post?.title}
|
|
|
|
</h1>
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
{/* First Row - Basic Info */}
|
|
|
|
<div className="flex flex-wrap gap-4">
|
|
|
|
{/* Author Info Badge */}
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
// transition={{ delay: 0.3 }}
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 bg-white px-3 py-1.5 rounded-md border border-[#97A9C4]/50 shadow-md hover:bg-[#F8FAFC] transition-colors duration-300">
|
|
|
|
<UserCircleIcon className="h-5 w-5 text-[#2B4C7E]" />
|
|
|
|
<span className="font-medium text-[#2B4C7E]">
|
|
|
|
{post?.author?.showname || "匿名用户"}
|
|
|
|
</span>
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
{/* Date Info Badge */}
|
|
|
|
{post?.createdAt && (
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
// transition={{ delay: 0.4 }}
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 bg-white px-3 py-1.5 rounded-md border border-[#97A9C4]/50 shadow-md hover:bg-[#F8FAFC] transition-colors duration-300">
|
|
|
|
<CalendarIcon className="h-5 w-5 text-[#2B4C7E]" />
|
|
|
|
<span className="text-[#2B4C7E]">
|
|
|
|
{format(
|
|
|
|
new Date(post?.createdAt),
|
|
|
|
"yyyy.MM.dd"
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
</motion.div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Last Updated Badge */}
|
|
|
|
{post?.updatedAt && post.updatedAt !== post.createdAt && (
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
// transition={{ delay: 0.45 }}
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 bg-white px-3 py-1.5 rounded-md border border-[#97A9C4]/50 shadow-md hover:bg-[#F8FAFC] transition-colors duration-300">
|
|
|
|
<ClockIcon className="h-5 w-5 text-[#2B4C7E]" />
|
|
|
|
<span className="text-[#2B4C7E]">
|
|
|
|
更新于:{" "}
|
|
|
|
{format(
|
|
|
|
new Date(post?.updatedAt),
|
|
|
|
"yyyy.MM.dd"
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
</motion.div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Visibility Status Badge */}
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
// transition={{ delay: 0.5 }}
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 bg-white px-3 py-1.5 rounded-md border border-[#97A9C4]/50 shadow-md hover:bg-[#F8FAFC] transition-colors duration-300">
|
|
|
|
{post?.isPublic ? (
|
|
|
|
<LockOpenIcon className="h-5 w-5 text-[#2B4C7E]" />
|
|
|
|
) : (
|
|
|
|
<LockClosedIcon className="h-5 w-5 text-[#2B4C7E]" />
|
|
|
|
)}
|
|
|
|
<span className="text-[#2B4C7E]">
|
|
|
|
{post?.isPublic ? "公开" : "私信"}
|
|
|
|
</span>
|
|
|
|
</motion.div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Second Row - Term and Tags */}
|
|
|
|
<div className="flex flex-wrap gap-4">
|
|
|
|
{/* Term Badge */}
|
|
|
|
{post?.term?.name && (
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
transition={{ delay: 0.55 }}
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 bg-[#507AAF]/10 px-3 py-1.5 rounded border border-[#97A9C4]/50 shadow-md hover:bg-[#507AAF]/20">
|
|
|
|
<StarIcon className="h-5 w-5 text-[#2B4C7E]" />
|
|
|
|
<span className="font-medium text-[#2B4C7E]">
|
|
|
|
{post.term.name}
|
|
|
|
</span>
|
|
|
|
</motion.div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Tags Badges */}
|
|
|
|
{post?.meta?.tags && post.meta.tags.length > 0 && (
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
transition={{ delay: 0.6 }}
|
|
|
|
className="flex flex-wrap gap-2">
|
|
|
|
{post.meta.tags.map((tag, index) => (
|
|
|
|
<motion.span
|
|
|
|
key={index}
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="inline-flex items-center bg-[#507AAF]/10 px-3 py-1.5 rounded border border-[#97A9C4]/50 shadow-md hover:bg-[#507AAF]/20">
|
|
|
|
<span className="text-sm text-[#2B4C7E]">
|
|
|
|
#{tag}
|
|
|
|
</span>
|
|
|
|
</motion.span>
|
|
|
|
))}
|
|
|
|
</motion.div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Content Section */}
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
transition={{ delay: 0.6 }}
|
|
|
|
className="mt-6 text-[#2B4C7E]">
|
|
|
|
<div
|
|
|
|
className="ql-editor space-y-4 leading-relaxed bg-white/60 p-4 rounded-md border border-[#97A9C4]/30 shadow-inner hover:bg-white/80 transition-colors duration-300"
|
|
|
|
dangerouslySetInnerHTML={{ __html: post?.content || "" }}
|
|
|
|
/>
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
{/* Stats Section */}
|
|
|
|
<motion.div
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
transition={{ delay: 0.7 }}
|
|
|
|
className="mt-6 flex flex-wrap gap-4 justify-start items-center">
|
|
|
|
<motion.button
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
onClick={likeThisPost}
|
|
|
|
className={`flex items-center gap-2 px-4 py-2 rounded-md ${
|
|
|
|
post?.liked
|
|
|
|
? "bg-[#507AAF] text-white"
|
|
|
|
: "bg-white text-[#2B4C7E] hover:bg-[#507AAF] hover:text-white"
|
|
|
|
} transition-all duration-300 shadow-md border border-[#97A9C4]/30`}>
|
|
|
|
<StarIcon
|
|
|
|
className={`h-5 w-5 ${post?.liked ? "fill-white" : ""}`}
|
|
|
|
/>
|
|
|
|
<span className="font-medium">
|
|
|
|
{post?.likes || 0} 有帮助
|
|
|
|
</span>
|
|
|
|
</motion.button>
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 px-4 py-2 bg-white text-[#2B4C7E] rounded-md shadow-md border border-[#97A9C4]/30">
|
|
|
|
<EyeIcon className="h-5 w-5" />
|
|
|
|
<span className="font-medium">{post?.views || 0} 浏览</span>
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
className="flex items-center gap-2 px-4 py-2 bg-white text-[#2B4C7E] rounded-md shadow-md border border-[#97A9C4]/30">
|
|
|
|
<ChatBubbleLeftIcon className="h-5 w-5" />
|
|
|
|
<span className="font-medium">
|
2025-01-24 15:06:57 +08:00
|
|
|
{post?.commentsCount || 0} 回复
|
2025-01-24 00:19:02 +08:00
|
|
|
</span>
|
|
|
|
</motion.div>
|
|
|
|
</motion.div>
|
|
|
|
</motion.div>
|
|
|
|
);
|
|
|
|
}
|