Skip to content

Commit c0e5443

Browse files
committed
feat(博客页面): 添加BlogPost组件以显示文章详情
引入BlogPost组件,用于在博客页面中动态加载并显示文章内容。该组件通过API获取文章数据,并处理加载状态、错误状态以及文章内容的渲染。同时,更新页面布局以包含新的组件。
1 parent 56febcd commit c0e5443

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { createResource, Show } from "solid-js";
2+
import http from "@/lib/axios";
3+
4+
// 定义文章接口
5+
interface Post {
6+
id: string;
7+
title: string;
8+
slug: string;
9+
content: string;
10+
excerpt: string | null;
11+
featured_image: string | null;
12+
published: boolean;
13+
author_id: string;
14+
created_at: string;
15+
updated_at: string;
16+
published_at: string;
17+
labels: string[];
18+
}
19+
20+
// 格式化日期函数
21+
function formatDate(dateString: string) {
22+
const date = new Date(dateString);
23+
return date.toLocaleDateString("zh-CN", {
24+
year: "numeric",
25+
month: "long",
26+
day: "numeric",
27+
});
28+
}
29+
30+
// 获取单篇文章数据的函数
31+
async function fetchPost(slug: string): Promise<Post | null> {
32+
try {
33+
const response = await http.get<Post>(`/posts/${slug}`, {
34+
withToken: false,
35+
});
36+
37+
// 检查响应格式
38+
if (response && response.id) {
39+
// 处理特殊字符
40+
return {
41+
...response,
42+
featured_image: response.featured_image
43+
? response.data.featured_image.replace(/`/g, "").trim()
44+
: null,
45+
};
46+
} else {
47+
console.error("API响应格式不符合预期:", response);
48+
return null;
49+
}
50+
} catch (error) {
51+
console.error(`获取文章 ${slug} 失败:`, error);
52+
return null;
53+
}
54+
}
55+
56+
interface BlogPostProps {
57+
slug: string;
58+
}
59+
60+
export default function BlogPost(props: BlogPostProps) {
61+
// 使用SolidJS的资源加载功能获取文章
62+
const [post] = createResource<Post | null>(() => fetchPost(props.slug));
63+
64+
return (
65+
<div class="container mx-auto px-4 py-8">
66+
<Show
67+
when={!post.loading}
68+
fallback={
69+
<div class="text-center py-12">
70+
<span class="loading loading-spinner loading-lg"></span>
71+
</div>
72+
}
73+
>
74+
<Show
75+
when={post()}
76+
fallback={
77+
<div class="text-center py-12">
78+
<div class="alert alert-error shadow-lg max-w-md mx-auto">
79+
<svg
80+
xmlns="http://www.w3.org/2000/svg"
81+
class="stroke-current shrink-0 h-6 w-6"
82+
fill="none"
83+
viewBox="0 0 24 24"
84+
>
85+
<path
86+
stroke-linecap="round"
87+
stroke-linejoin="round"
88+
stroke-width="2"
89+
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
90+
/>
91+
</svg>
92+
<span>文章不存在或已被删除</span>
93+
</div>
94+
</div>
95+
}
96+
>
97+
{post() && (
98+
<article class="prose prose-lg max-w-none dark:prose-invert">
99+
{post()!.featured_image && (
100+
<img
101+
src={post()!.featured_image}
102+
alt={post()!.title}
103+
class="w-full h-64 md:h-96 object-cover rounded-lg shadow-md mb-8"
104+
/>
105+
)}
106+
107+
<h1 class="text-4xl font-bold mb-4">{post()!.title}</h1>
108+
109+
<div class="flex items-center text-sm opacity-70 mb-8">
110+
<span>{formatDate(post()!.published_at)}</span>
111+
{post()!.labels && post()!.labels.length > 0 && (
112+
<div class="ml-4 flex gap-2">
113+
{post()!.labels.map((tag) => (
114+
<div class="badge badge-outline">{tag}</div>
115+
))}
116+
</div>
117+
)}
118+
</div>
119+
120+
<div class="mt-8" innerHTML={post()!.content}></div>
121+
</article>
122+
)}
123+
</Show>
124+
</Show>
125+
</div>
126+
);
127+
}

blog-web/src/pages/blog/[slug].astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ export const prerender = false;
33
const { slug } = Astro.params;
44
55
import Layout from "@/layouts/Layout.astro";
6+
import BlogPost from "@/components/solid/BlogPost";
67
---
78

8-
<Layout>
9-
{slug}
9+
<Layout title="博客文章 | 精致的芯 - 精致的私人博客">
10+
<BlogPost client:load slug={slug ?? ""} />
1011
</Layout>

0 commit comments

Comments
 (0)