mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
⚡️ feat: Refactor API client to support server-side and client-side configurations
fix: Update post fetching logic to use dynamic ID instead of hardcoded value feat: Enhance layout with animated transitions using framer-motion refactor: Remove old post and user page implementations, introduce new structure feat: Implement sidebar components for blog home with dynamic content feat: Create blog post component with wave header and metadata display feat: Add responsive sidebar menu for navigation on mobile devices chore: Introduce reusable sheet component for modal-like functionality
This commit is contained in:
@ -1,18 +1,35 @@
|
||||
import { Navbar } from "@/components/navbar";
|
||||
"use client";
|
||||
|
||||
import { Navbar } from "@/components/navbar";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { usePathname } from "next/navigation";
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<>
|
||||
<header className="flex justify-center">
|
||||
<header className="fixed top-0 left-0 w-full z-50 bg-white/80 dark:bg-slate-900/80 backdrop-blur flex justify-center border-b border-slate-200 dark:border-slate-800">
|
||||
<Navbar />
|
||||
</header>
|
||||
<main>
|
||||
{children}
|
||||
</main>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.main
|
||||
key={pathname}
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 16 }}
|
||||
transition={{
|
||||
type: "tween",
|
||||
ease: "easeOut",
|
||||
duration: 0.18
|
||||
}}
|
||||
className="pt-16"
|
||||
>
|
||||
{children}
|
||||
</motion.main>
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
13
web/src/app/(main)/p/[id]/page.tsx
Normal file
13
web/src/app/(main)/p/[id]/page.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { getPostById } from "@/api/post";
|
||||
import BlogPost from "@/components/blog-post";
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: string }>
|
||||
}
|
||||
|
||||
export default async function PostPage({ params }: Props) {
|
||||
const { id } = await params;
|
||||
const post = await getPostById(id);
|
||||
if (!post) return <div>文章不存在</div>;
|
||||
return <BlogPost post={post} />;
|
||||
}
|
11
web/src/app/console/layout.tsx
Normal file
11
web/src/app/console/layout.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
import { getPostById } from "@/api/post";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
interface Props {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>
|
||||
}
|
||||
|
||||
export default async function PostPage({ params }: Props) {
|
||||
const { id } = await params;
|
||||
|
||||
try {
|
||||
const post = await getPostById(id);
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="max-w-4xl mx-auto px-4 py-8">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
|
||||
<div className="text-gray-600 mb-4">
|
||||
<time>{new Date(post.createdAt).toLocaleDateString()}</time>
|
||||
<span className="mx-2">·</span>
|
||||
<span>阅读量: {post.viewCount || 0}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="prose max-w-none">
|
||||
<div dangerouslySetInnerHTML={{ __html: post.content }} />
|
||||
</div>
|
||||
|
||||
{post.labels && post.labels.length > 0 && (
|
||||
<footer className="mt-8 pt-8 border-t">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.labels.map((label) => (
|
||||
<span key={label.id} className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">
|
||||
#{label.key}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</footer>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch post:", error);
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
|
||||
// 生成元数据
|
||||
export async function generateMetadata({ params }: Props) {
|
||||
const { id } = await params;
|
||||
|
||||
try {
|
||||
const post = await getPostById(id);
|
||||
|
||||
if (!post) {
|
||||
return {
|
||||
title: '文章未找到',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.content?.substring(0, 160),
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description: post.content?.substring(0, 160),
|
||||
type: 'article',
|
||||
publishedTime: post.createdAt,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
title: '文章未找到',
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user