feat(post_detail): propagate NotFound and other load errors to ErrorBoundary
This commit is contained in:
parent
37e8f51fac
commit
1b38162ca4
@ -20,7 +20,6 @@
|
||||
//! 实际的数据库访问逻辑仅在 `feature = "server"` 启用时运行。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
use crate::api::posts::{get_post_by_slug, SinglePostResponse};
|
||||
use crate::components::post::post_content::PostContent;
|
||||
@ -55,92 +54,80 @@ pub fn PostDetail(slug: String) -> Element {
|
||||
get_post_by_slug(current_slug)
|
||||
})?;
|
||||
|
||||
// 将结果映射为更直观的 Ok(post) / Err("not_found") / Err("error") 三种状态。
|
||||
// 将结果映射为 Ok(post) 或 Err(ServerFnError) 用于错误边界捕获
|
||||
let post_data = post.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"),
|
||||
Ok(SinglePostResponse { post: None }) => Err(ServerFnError::ServerError {
|
||||
message: "not_found".to_string(),
|
||||
code: 404,
|
||||
details: None,
|
||||
}),
|
||||
Err(e) => Err(e.clone()),
|
||||
});
|
||||
|
||||
match post_data {
|
||||
Some(Ok(post)) => {
|
||||
rsx! {
|
||||
article { class: "post-single animate-page-enter", key: "{post.slug}",
|
||||
PostHeader { post: post.clone() }
|
||||
|
||||
// 如果文章设置了封面图,则渲染封面组件。
|
||||
if let Some(cover) = &post.cover_image {
|
||||
PostCover { src: cover.clone() }
|
||||
}
|
||||
|
||||
// 如果文章生成了目录 HTML,则渲染目录组件。
|
||||
if let Some(toc) = &post.toc_html {
|
||||
PostToc { toc_html: toc.clone() }
|
||||
}
|
||||
|
||||
// 用单元素 keyed 列表包裹 PostContent,key 绑定 slug。
|
||||
//
|
||||
// 为什么不能直接给 PostContent 加 key:Dioxus 的 key diff(diff_keyed_children)
|
||||
// 只在「兄弟节点列表」里生效,对单个非列表元素的 key 变化会走 diff_non_keyed
|
||||
// 路径、按位置复用,不触发 remount。把组件放进单元素 for 循环并带 key,
|
||||
// 才会进入 keyed diff——slug 变化时旧 PostContent 被移除、新的被创建。
|
||||
//
|
||||
// 为什么需要 remount:上下篇切换时 PostContent 若被复用(仅重渲染),
|
||||
// (1) 内部 CodeRunner 用片段索引作 key,两篇文章索引可能相同(如 1/3/5),
|
||||
// keyed diff 按相同 key 复用 CodeRunner 实例——其挂载 use_effect 的
|
||||
// 「防重复 init」守卫阻止 CodeMirror 挂载到新的(已替换的)DOM 容器,
|
||||
// 表现为翻页后代码块消失;
|
||||
// (2) PostContent 自身的 use_effect(__initPostContent 复制按钮 / 灯箱初始化)
|
||||
// 也不会重跑,新文章的交互脚本不初始化。
|
||||
// 强制 remount 让编辑器与脚本随文章切换全部重新初始化。
|
||||
for post_slug in std::iter::once(post.slug.clone()) {
|
||||
PostContent {
|
||||
key: "{post_slug}",
|
||||
content_html: post.content_html.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
PostFooter { post: post.clone() }
|
||||
|
||||
// 仅对已发布文章展示评论区域,使用 SuspenseBoundary 处理加载状态。
|
||||
if post.status == crate::models::post::PostStatus::Published {
|
||||
div { class: "mt-12 border-t border-gray-200 dark:border-gray-700 pt-8",
|
||||
SuspenseBoundary {
|
||||
fallback: move |_| rsx! {
|
||||
DelayedSkeleton {
|
||||
crate::components::skeletons::comment_skeleton::CommentListSkeleton {}
|
||||
}
|
||||
},
|
||||
crate::components::comments::section::CommentSection { post_id: post.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let post = match post_data {
|
||||
Some(Ok(post)) => post,
|
||||
Some(Err(err)) => {
|
||||
// Bubble the error up to the ErrorBoundary
|
||||
return Err(err.into());
|
||||
}
|
||||
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",
|
||||
"这篇文章可能已被删除或移动。"
|
||||
}
|
||||
Link {
|
||||
class: "px-6 py-2 bg-paper-primary text-paper-theme rounded-full font-medium hover:opacity-80 transition-opacity",
|
||||
to: Route::Home {},
|
||||
"返回首页"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err("error")) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20", "加载失败" }
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
rsx! {
|
||||
None => {
|
||||
return rsx! {
|
||||
DelayedSkeleton { PostDetailSkeleton {} }
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
rsx! {
|
||||
article { class: "post-single animate-page-enter", key: "{post.slug}",
|
||||
PostHeader { post: post.clone() }
|
||||
|
||||
// 如果文章设置了封面图,则渲染封面组件。
|
||||
if let Some(cover) = &post.cover_image {
|
||||
PostCover { src: cover.clone() }
|
||||
}
|
||||
|
||||
// 如果文章生成了目录 HTML,则渲染目录组件。
|
||||
if let Some(toc) = &post.toc_html {
|
||||
PostToc { toc_html: toc.clone() }
|
||||
}
|
||||
|
||||
// 用单元素 keyed 列表包裹 PostContent,key 绑定 slug。
|
||||
//
|
||||
// 为什么不能直接给 PostContent 加 key:Dioxus 的 key diff(diff_keyed_children)
|
||||
// 只在「兄弟节点列表」里生效,对单个非列表元素的 key 变化会走 diff_non_keyed
|
||||
// 路径、按位置复用,不触发 remount。把组件放进单元素 for 循环并带 key,
|
||||
// 才会进入 keyed diff——slug 变化时旧 PostContent 被移除、新的被创建。
|
||||
//
|
||||
// 为什么需要 remount:上下篇切换时 PostContent 若被复用(仅重渲染),
|
||||
// (1) 内部 CodeRunner 用片段索引作 key,两篇文章索引可能相同(如 1/3/5),
|
||||
// keyed diff 按相同 key 复用 CodeRunner 实例——其挂载 use_effect 的
|
||||
// 「防重复 init」守卫阻止 CodeMirror 挂载到新的(已替换的)DOM 容器,
|
||||
// 表现为翻页后代码块消失;
|
||||
// (2) PostContent 自身的 use_effect(__initPostContent 复制按钮 / 灯箱初始化)
|
||||
// 也不会重跑,新文章的交互脚本不初始化。
|
||||
// 强制 remount 让编辑器与脚本随文章切换全部重新初始化。
|
||||
for post_slug in std::iter::once(post.slug.clone()) {
|
||||
PostContent {
|
||||
key: "{post_slug}",
|
||||
content_html: post.content_html.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
PostFooter { post: post.clone() }
|
||||
|
||||
// 仅对已发布文章展示评论区域,使用 SuspenseBoundary 处理加载状态。
|
||||
if post.status == crate::models::post::PostStatus::Published {
|
||||
div { class: "mt-12 border-t border-gray-200 dark:border-gray-700 pt-8",
|
||||
SuspenseBoundary {
|
||||
fallback: move |_| rsx! {
|
||||
DelayedSkeleton {
|
||||
crate::components::skeletons::comment_skeleton::CommentListSkeleton {}
|
||||
}
|
||||
},
|
||||
crate::components::comments::section::CommentSection { post_id: post.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user