feat: SSR for post detail page

This commit is contained in:
xfy 2026-06-03 14:17:52 +08:00
parent 60a2cd49ab
commit fe30d0495f

View File

@ -8,77 +8,85 @@ use crate::components::post::post_cover::PostCover;
use crate::components::post::post_footer::PostFooter; use crate::components::post::post_footer::PostFooter;
use crate::components::post::post_header::PostHeader; use crate::components::post::post_header::PostHeader;
use crate::components::post::post_toc::PostToc; use crate::components::post::post_toc::PostToc;
use crate::hooks::delayed_loading::use_delayed_loading; use crate::components::suspense_wrapper::SuspenseWrapper;
use crate::router::Route; use crate::router::Route;
#[component] #[component]
pub fn PostDetail(slug: String) -> Element { pub fn PostDetail(slug: String) -> Element {
let route = use_route::<Route>(); let route = use_route::<Route>();
let slug_clone = slug.clone();
let post_res = use_resource(move || get_post_by_slug(slug_clone.clone()));
let nav_items = use_nav_items(route); let nav_items = use_nav_items(route);
let show_skeleton = use_delayed_loading(move || post_res.read().is_none());
rsx! { rsx! {
PageLayout { nav_items, PageLayout { nav_items,
match &*post_res.read() { SuspenseWrapper {
Some(Ok(SinglePostResponse { post: Some(post) })) => { PostDetailContent { slug: slug.clone() }
rsx! { }
article { class: "post-single", }
PostHeader { post: post.clone() } }
}
if let Some(cover) = &post.cover_image { #[component]
PostCover { src: cover.clone() } fn PostDetailContent(slug: String) -> Element {
} let post_res = use_server_future(move || get_post_by_slug(slug.clone()))?;
if let Some(toc) = &post.toc_html { let post_data = post_res.read().as_ref().map(|r| match r {
PostToc { toc_html: toc.clone() } Ok(SinglePostResponse { post: Some(post) }) => Ok(post.clone()),
} Ok(SinglePostResponse { post: None }) => Err("not_found"),
Err(_) => Err("error"),
});
PostContent { match post_data {
content_html: post.content_html.clone().unwrap_or_default() Some(Ok(post)) => {
} rsx! {
article { class: "post-single",
PostHeader { post: post.clone() }
PostFooter { post: post.clone() } if let Some(cover) = &post.cover_image {
} PostCover { src: cover.clone() }
}
if let Some(toc) = &post.toc_html {
PostToc { toc_html: toc.clone() }
}
PostContent {
content_html: post.content_html.clone().unwrap_or_default()
}
PostFooter { post: post.clone() }
}
}
}
Some(Err("not_found")) => {
rsx! {
div { class: "text-center py-20",
h2 { class: "text-2xl font-bold text-paper-primary mb-4",
"文章不存在"
}
p { class: "text-paper-secondary mb-6",
"这篇文章可能已被删除或移动。"
}
button {
class: "px-6 py-2 bg-paper-primary text-paper-theme rounded-full font-medium hover:opacity-80 transition-opacity",
onclick: move |_| {
let _ = dioxus::router::navigator().push("/");
},
"返回首页"
} }
} }
Some(Ok(SinglePostResponse { post: None })) => { }
rsx! { }
div { class: "text-center py-20", Some(Err("error")) => {
h2 { class: "text-2xl font-bold text-paper-primary mb-4", rsx! {
"文章不存在" div { class: "text-center text-red-500 dark:text-red-400 py-20",
} "加载失败"
p { class: "text-paper-secondary mb-6",
"这篇文章可能已被删除或移动。"
}
button {
class: "px-6 py-2 bg-paper-primary text-paper-theme rounded-full font-medium hover:opacity-80 transition-opacity",
onclick: move |_| {
let _ = dioxus::router::navigator().push("/");
},
"返回首页"
}
}
}
} }
Some(Err(e)) => { }
rsx! { }
div { class: "text-center text-red-500 dark:text-red-400 py-20", _ => {
"加载失败: {e}" rsx! {
} div { class: "text-center py-20",
} "加载中..."
}
None => {
rsx! {
div { class: if show_skeleton() { "animate-pulse py-6 space-y-4" } else { "py-6 space-y-4 opacity-0" },
div { class: "h-10 w-3/4 bg-paper-tertiary rounded" }
div { class: "h-4 w-32 bg-paper-tertiary rounded" }
div { class: "h-4 w-full bg-paper-tertiary rounded mt-8" }
div { class: "h-4 w-full bg-paper-tertiary rounded" }
div { class: "h-4 w-2/3 bg-paper-tertiary rounded" }
}
}
} }
} }
} }