From 5d018864c262a50b2cf637dd993c42f4efb40efa Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 3 Jun 2026 18:38:11 +0800 Subject: [PATCH] refactor: remove PageLayout from all frontend pages, delegate to FrontendLayout - Remove PageLayout wrapper from Home, HomePage, Archives, Tags, TagDetail, PostDetail, Search, and About components - Remove unused imports: use_nav_items, use_route, PageLayout, Route - Pages now render only their content; Header/Footer are provided by FrontendLayout via the router's #[layout] attribute - Skeleton screens (DelayedSkeleton) remain in data-loading branches - This eliminates redundant Header/Footer re-mounting on every route change, which was the primary source of page transition flicker Files changed: - src/pages/home.rs: remove PageLayout, keep HomeInfo + HomePosts - src/pages/about.rs: remove PageLayout, render content directly - src/pages/archives.rs: remove PageLayout, keep header + ArchivesContent - src/pages/post_detail.rs: remove PageLayout, keep PostDetailContent - src/pages/search.rs: remove PageLayout, keep search form + results - src/pages/tags.rs: remove PageLayout from Tags and TagDetail --- src/pages/about.rs | 53 +++++++++++++----------------- src/pages/archives.rs | 16 +++------ src/pages/home.rs | 11 ++----- src/pages/post_detail.rs | 10 +----- src/pages/search.rs | 71 ++++++++++++++++++---------------------- src/pages/tags.rs | 29 +++++----------- 6 files changed, 69 insertions(+), 121 deletions(-) diff --git a/src/pages/about.rs b/src/pages/about.rs index 70779c2..ea48f81 100644 --- a/src/pages/about.rs +++ b/src/pages/about.rs @@ -1,40 +1,31 @@ use dioxus::prelude::*; -use crate::components::nav::use_nav_items; -use crate::components::page_layout::PageLayout; -use crate::router::Route; - #[component] pub fn About() -> Element { - let route = use_route::(); - let nav_items = use_nav_items(route); - rsx! { - PageLayout { nav_items, - header { class: "page-header mb-6", - h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", - "关于" - } + header { class: "page-header mb-6", + h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", + "关于" } - article { class: "prose dark:prose-invert max-w-none text-gray-800 dark:text-[#c9cacc] leading-relaxed", - p { "Yggdrasil 是一个以文字为主的简约博客系统。" } - p { "它使用 Rust + Dioxus 构建,采用 PostgreSQL 作为数据库,支持 Markdown 写作、标签管理和暗色模式。" } - h2 { class: "text-xl font-bold text-gray-900 dark:text-[#dadadb] mt-8 mb-4", "技术栈" } - ul { class: "list-disc pl-5 space-y-1", - li { "Rust + Dioxus 0.7 (全栈 Web 框架)" } - li { "PostgreSQL + tokio-postgres (数据库)" } - li { "Tailwind CSS (样式)" } - li { "Tiptap Editor (富文本编辑器)" } - li { "pulldown-cmark (Markdown 渲染)" } - } - h2 { class: "text-xl font-bold text-gray-900 dark:text-[#dadadb] mt-8 mb-4", "特性" } - ul { class: "list-disc pl-5 space-y-1", - li { "Markdown 写作与实时预览" } - li { "文章标签与归档" } - li { "暗色/亮色主题切换" } - li { "响应式设计" } - li { "文章搜索" } - } + } + article { class: "prose dark:prose-invert max-w-none text-gray-800 dark:text-[#c9cacc] leading-relaxed", + p { "Yggdrasil 是一个以文字为主的简约博客系统。" } + p { "它使用 Rust + Dioxus 构建,采用 PostgreSQL 作为数据库,支持 Markdown 写作、标签管理和暗色模式。" } + h2 { class: "text-xl font-bold text-gray-900 dark:text-[#dadadb] mt-8 mb-4", "技术栈" } + ul { class: "list-disc pl-5 space-y-1", + li { "Rust + Dioxus 0.7 (全栈 Web 框架)" } + li { "PostgreSQL + tokio-postgres (数据库)" } + li { "Tailwind CSS (样式)" } + li { "Tiptap Editor (富文本编辑器)" } + li { "pulldown-cmark (Markdown 渲染)" } + } + h2 { class: "text-xl font-bold text-gray-900 dark:text-[#dadadb] mt-8 mb-4", "特性" } + ul { class: "list-disc pl-5 space-y-1", + li { "Markdown 写作与实时预览" } + li { "文章标签与归档" } + li { "暗色/亮色主题切换" } + li { "响应式设计" } + li { "文章搜索" } } } } diff --git a/src/pages/archives.rs b/src/pages/archives.rs index e21a64e..79ad30d 100644 --- a/src/pages/archives.rs +++ b/src/pages/archives.rs @@ -1,12 +1,9 @@ use dioxus::prelude::*; use crate::api::posts::{list_published_posts, PostListResponse}; -use crate::components::nav::use_nav_items; -use crate::components::page_layout::PageLayout; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::archive_skeleton::ArchiveSkeleton; use crate::models::post::Post; -use crate::router::Route; #[derive(Clone, PartialEq)] struct YearGroup { @@ -80,18 +77,13 @@ fn group_posts(posts: &[Post]) -> Vec { #[component] pub fn Archives() -> Element { - let route = use_route::(); - let nav_items = use_nav_items(route); - rsx! { - PageLayout { nav_items, - header { class: "page-header mb-6", - h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", - "归档" - } + header { class: "page-header mb-6", + h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", + "归档" } - ArchivesContent {} } + ArchivesContent {} } } diff --git a/src/pages/home.rs b/src/pages/home.rs index 8155383..cdca908 100644 --- a/src/pages/home.rs +++ b/src/pages/home.rs @@ -1,12 +1,9 @@ use dioxus::prelude::*; use crate::api::posts::{list_published_posts, PostListResponse}; -use crate::components::nav::use_nav_items; -use crate::components::page_layout::PageLayout; use crate::components::post_card::PostCard; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::home_skeleton::HomeSkeleton; -use crate::router::Route; const POSTS_PER_PAGE: i32 = 10; @@ -17,15 +14,11 @@ pub fn Home() -> Element { #[component] pub fn HomePage(page: i32) -> Element { - let route = use_route::(); - let nav_items = use_nav_items(route); let current_page = page.max(1); rsx! { - PageLayout { nav_items, - HomeInfo {} - HomePosts { current_page } - } + HomeInfo {} + HomePosts { current_page } } } diff --git a/src/pages/post_detail.rs b/src/pages/post_detail.rs index dc0df62..b55f94f 100644 --- a/src/pages/post_detail.rs +++ b/src/pages/post_detail.rs @@ -1,8 +1,6 @@ use dioxus::prelude::*; use crate::api::posts::{get_post_by_slug, SinglePostResponse}; -use crate::components::nav::use_nav_items; -use crate::components::page_layout::PageLayout; use crate::components::post::post_content::PostContent; use crate::components::post::post_cover::PostCover; use crate::components::post::post_footer::PostFooter; @@ -10,17 +8,11 @@ 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; #[component] pub fn PostDetail(slug: String) -> Element { - let route = use_route::(); - let nav_items = use_nav_items(route); - rsx! { - PageLayout { nav_items, - PostDetailContent { slug: slug.clone() } - } + PostDetailContent { slug: slug.clone() } } } diff --git a/src/pages/search.rs b/src/pages/search.rs index c282c8b..756e307 100644 --- a/src/pages/search.rs +++ b/src/pages/search.rs @@ -1,20 +1,15 @@ use dioxus::prelude::*; use crate::api::posts::{search_posts, PostListResponse}; -use crate::components::nav::use_nav_items; -use crate::components::page_layout::PageLayout; use crate::components::post_card::PostCard; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::search_skeleton::SearchSkeleton; -use crate::router::Route; #[component] pub fn Search() -> Element { - let route = use_route::(); let mut query = use_signal(|| "".to_string()); let mut search_res = use_signal(|| None::>); let mut is_searching = use_signal(|| false); - let nav_items = use_nav_items(route); let mut on_search = move || { let q = query().trim().to_string(); if q.is_empty() { @@ -30,45 +25,43 @@ pub fn Search() -> Element { }; rsx! { - PageLayout { nav_items, - header { class: "page-header mb-6", - h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", + header { class: "page-header mb-6", + h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", + "搜索" + } + } + div { class: "mb-8", + div { class: "flex gap-2", + input { + class: "flex-1 px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + r#type: "text", + placeholder: "输入关键词搜索文章...", + value: query(), + oninput: move |e| query.set(e.value()), + onkeydown: move |e| if e.key() == Key::Enter { on_search() }, + } + button { + class: "px-6 py-2 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full font-medium hover:opacity-80 transition-opacity", + onclick: move |_| on_search(), "搜索" } } - div { class: "mb-8", - div { class: "flex gap-2", - input { - class: "flex-1 px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", - r#type: "text", - placeholder: "输入关键词搜索文章...", - value: query(), - oninput: move |e| query.set(e.value()), - onkeydown: move |e| if e.key() == Key::Enter { on_search() }, - } - button { - class: "px-6 py-2 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full font-medium hover:opacity-80 transition-opacity", - onclick: move |_| on_search(), - "搜索" - } + } + if is_searching() { + DelayedSkeleton { SearchSkeleton {} } + } else if let Some(Ok(PostListResponse { posts })) = search_res() { + if posts.is_empty() { + div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20", + "未找到相关文章" + } + } else { + for post in posts.iter() { + PostCard { post: post.clone() } } } - if is_searching() { - DelayedSkeleton { SearchSkeleton {} } - } else if let Some(Ok(PostListResponse { posts })) = search_res() { - if posts.is_empty() { - div { class: "text-center text-gray-500 dark:text-[#9b9c9d] py-20", - "未找到相关文章" - } - } else { - for post in posts.iter() { - PostCard { post: post.clone() } - } - } - } else if let Some(Err(e)) = search_res() { - div { class: "text-center text-red-500 dark:text-red-400 py-20", - "搜索失败: {e}" - } + } else if let Some(Err(e)) = search_res() { + div { class: "text-center text-red-500 dark:text-red-400 py-20", + "搜索失败: {e}" } } } diff --git a/src/pages/tags.rs b/src/pages/tags.rs index c0656ea..c2e6752 100644 --- a/src/pages/tags.rs +++ b/src/pages/tags.rs @@ -1,27 +1,19 @@ use dioxus::prelude::*; use crate::api::posts::{get_posts_by_tag, list_tags, PostListResponse, TagListResponse}; -use crate::components::nav::use_nav_items; -use crate::components::page_layout::PageLayout; use crate::components::post_card::PostCard; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::tags_skeleton::{TagsSkeleton, TagDetailSkeleton}; -use crate::router::Route; #[component] pub fn Tags() -> Element { - let route = use_route::(); - let nav_items = use_nav_items(route); - rsx! { - PageLayout { nav_items, - header { class: "page-header mb-6", - h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", - "标签" - } + header { class: "page-header mb-6", + h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", + "标签" } - TagsContent {} } + TagsContent {} } } @@ -80,18 +72,13 @@ fn TagsContent() -> Element { #[component] pub fn TagDetail(tag: String) -> Element { - let route = use_route::(); - let nav_items = use_nav_items(route); - rsx! { - PageLayout { nav_items, - header { class: "page-header mb-6", - h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", - "{tag}" - } + header { class: "page-header mb-6", + h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]", + "{tag}" } - TagDetailContent { tag: tag.clone() } } + TagDetailContent { tag: tag.clone() } } }