From fe30d0495f0e2db2eaefc369c495a0ee4ef15807 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 3 Jun 2026 14:17:52 +0800 Subject: [PATCH] feat: SSR for post detail page --- src/pages/post_detail.rs | 118 +++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/src/pages/post_detail.rs b/src/pages/post_detail.rs index 4f8215d..3d19a0e 100644 --- a/src/pages/post_detail.rs +++ b/src/pages/post_detail.rs @@ -8,77 +8,85 @@ use crate::components::post::post_cover::PostCover; use crate::components::post::post_footer::PostFooter; use crate::components::post::post_header::PostHeader; 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; #[component] pub fn PostDetail(slug: String) -> Element { let route = use_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 show_skeleton = use_delayed_loading(move || post_res.read().is_none()); rsx! { PageLayout { nav_items, - match &*post_res.read() { - Some(Ok(SinglePostResponse { post: Some(post) })) => { - rsx! { - article { class: "post-single", - PostHeader { post: post.clone() } + SuspenseWrapper { + PostDetailContent { slug: slug.clone() } + } + } + } +} - if let Some(cover) = &post.cover_image { - PostCover { src: cover.clone() } - } +#[component] +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 { - PostToc { toc_html: toc.clone() } - } + let post_data = post_res.read().as_ref().map(|r| match r { + Ok(SinglePostResponse { post: Some(post) }) => Ok(post.clone()), + Ok(SinglePostResponse { post: None }) => Err("not_found"), + Err(_) => Err("error"), + }); - PostContent { - content_html: post.content_html.clone().unwrap_or_default() - } + match post_data { + 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", - 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(Err("error")) => { + rsx! { + div { class: "text-center text-red-500 dark:text-red-400 py-20", + "加载失败" } - Some(Err(e)) => { - rsx! { - div { class: "text-center text-red-500 dark:text-red-400 py-20", - "加载失败: {e}" - } - } - } - 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" } - } - } + } + } + _ => { + rsx! { + div { class: "text-center py-20", + "加载中..." } } }