yggdrasil/src/pages/post_detail.rs
xfy 45f4e9cde0 refactor(ui): 去除 Rust 中硬编码颜色,统一语义色阶
跟随 Catppuccin 配色迁移,清理 src/ 中绕过 paper-* 语义 token 的
硬编码 dark:[#hex] 任意值,改用 Tailwind 默认灰阶(gray-700/800),
它们随主题切换自然适配。

- post.rs 状态徽章:dark:bg-[#333]/dark:text-[#9b9c9d] → gray-800/gray-400/500
  (同步更新测试断言)
- comments/* 与 skeletons/* 的 dark:[#2a2a2a]/[#2e2e33]/[#333]/[#5a5a62]
  → 对应 gray-600/700/800
- trash.rs 状态灯发光圈 rgba 由旧鼠尾草绿(92,122,94) 改 Latte green(64,160,43)
- ui.rs BTN_SECONDARY 注释更新为新 Teal 强调色值

注:src/pages/admin/posts.rs 的 clippy redundant_closure 报错为既有问题,
与本次配色迁移无关,不在范围内。
2026-07-03 14:25:40 +08:00

102 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 文章详情页面模块。
//!
//! 对应路由 `/post/:slug`。
//!
//! 数据获取:通过 `use_server_future` 调用 `get_post_by_slug` server function
//! 根据 URL 中的 slug 获取单篇文章详情(含正文 HTML、目录、封面及上下篇导航
//! 路由参数 slug 变化时Dioxus 会自动重跑该 future无需镜像成 signal 再手动同步。
//! 在 `wasm32` 目标下server function 的函数体被替换为向服务端端点发起 HTTP POST 请求的客户端存根;
//! 实际的数据库访问逻辑仅在 `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;
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::components::skeletons::delayed_skeleton::DelayedSkeleton;
use crate::components::skeletons::post_detail_skeleton::PostDetailSkeleton;
use crate::router::Route;
/// 文章详情页面组件,对应路由 `/post/:slug`。
///
/// 根据 slug 异步获取文章,渲染文章头部、封面、目录、正文、页脚及评论区;
/// 若文章不存在或加载失败,则展示对应的提示页面。
#[component]
pub fn PostDetail(slug: String) -> Element {
// 直接读取 slug prop路由参数变化时 Dioxus 会自动重跑该 server future
// 无需镜像成 signal 再手动 setrender 期 set signal 是 Dioxus 反模式)。
let post = use_server_future(move || get_post_by_slug(slug.clone()))?;
// 将结果映射为更直观的 Ok(post) / Err("not_found") / Err("error") 三种状态。
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"),
});
match post_data {
Some(Ok(post)) => {
rsx! {
article { class: "post-single", 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() }
}
PostContent { 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! {
crate::components::skeletons::comment_skeleton::CommentListSkeleton {}
},
crate::components::comments::section::CommentSection { post_id: post.id }
}
}
}
}
}
}
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! {
DelayedSkeleton { PostDetailSkeleton {} }
}
}
}
}