yggdrasil/src/pages/archives.rs
xfy 5d018864c2 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
2026-06-03 18:38:11 +08:00

200 lines
6.1 KiB
Rust

use dioxus::prelude::*;
use crate::api::posts::{list_published_posts, PostListResponse};
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
use crate::components::skeletons::archive_skeleton::ArchiveSkeleton;
use crate::models::post::Post;
#[derive(Clone, PartialEq)]
struct YearGroup {
year: String,
months: Vec<MonthGroup>,
}
#[derive(Clone, PartialEq)]
struct MonthGroup {
month: String,
month_en: String,
posts: Vec<Post>,
}
fn group_posts(posts: &[Post]) -> Vec<YearGroup> {
let mut years: Vec<YearGroup> = vec![];
for post in posts {
let date_str = post.formatted_date();
let parts: Vec<&str> = date_str.split('-').collect();
if parts.len() != 3 {
continue;
}
let year = parts[0].to_string();
let month_num = parts[1];
let month_en = match month_num {
"01" => "January",
"02" => "February",
"03" => "March",
"04" => "April",
"05" => "May",
"06" => "June",
"07" => "July",
"08" => "August",
"09" => "September",
"10" => "October",
"11" => "November",
"12" => "December",
_ => month_num,
};
if let Some(yg) = years.last_mut() {
if yg.year == year {
if let Some(mg) = yg.months.last_mut() {
if mg.month_en == month_en {
mg.posts.push(post.clone());
continue;
}
}
yg.months.push(MonthGroup {
month: month_en.to_string(),
month_en: month_en.to_string(),
posts: vec![post.clone()],
});
continue;
}
}
years.push(YearGroup {
year,
months: vec![MonthGroup {
month: month_en.to_string(),
month_en: month_en.to_string(),
posts: vec![post.clone()],
}],
});
}
years
}
#[component]
pub fn Archives() -> Element {
rsx! {
header { class: "page-header mb-6",
h1 { class: "text-[34px] font-bold text-gray-900 dark:text-[#dadadb]",
"归档"
}
}
ArchivesContent {}
}
}
#[component]
fn ArchivesContent() -> Element {
let posts_res = use_server_future(move || list_published_posts(1, 10000))?;
let posts_data = posts_res.read();
match &*posts_data {
Some(Ok(PostListResponse { posts })) => {
let grouped = group_posts(posts);
rsx! {
div { class: "mt-2 text-base text-gray-500 dark:text-[#9b9c9d]",
""
span { class: "font-medium text-gray-700 dark:text-[#dadadb]", "{posts.len()}" }
" 篇文章"
}
for year_group in grouped.iter() {
YearSection { year_group: year_group.clone() }
}
}
}
Some(Err(e)) => {
rsx! {
div { class: "text-center text-red-500 dark:text-red-400 py-20",
"加载失败: {e}"
}
}
}
None => {
rsx! {
DelayedSkeleton { ArchiveSkeleton {} }
}
}
}
}
#[component]
fn YearSection(year_group: YearGroup) -> Element {
let total = year_group
.months
.iter()
.map(|m| m.posts.len())
.sum::<usize>();
rsx! {
div { class: "archive-year mt-10",
h2 {
class: "archive-year-header text-2xl font-bold text-gray-900 dark:text-[#dadadb] mb-4",
id: "{year_group.year}",
a {
class: "archive-header-link hover:opacity-80 transition-opacity",
href: "#{year_group.year}",
"{year_group.year}"
}
sup { class: "archive-count text-sm text-gray-400 dark:text-[#9b9c9d] ml-1", "{total}" }
}
for month_group in year_group.months.iter() {
MonthSection { month_group: month_group.clone(), year: year_group.year.clone() }
}
}
}
}
#[component]
fn MonthSection(month_group: MonthGroup, year: String) -> Element {
let count = month_group.posts.len();
rsx! {
div { class: "archive-month flex flex-col md:flex-row md:items-start py-2.5 border-b border-gray-100 dark:border-[#333]/50",
h3 {
class: "archive-month-header text-lg font-medium text-gray-700 dark:text-[#9b9c9d] md:w-[200px] shrink-0 mt-0 mb-0 py-1.5",
id: "{year}-{month_group.month_en}",
a {
class: "archive-header-link hover:opacity-80 transition-opacity",
href: "#{year}-{month_group.month_en}",
"{month_group.month}"
}
sup { class: "archive-count text-sm text-gray-400 dark:text-[#9b9c9d] ml-1", "{count}" }
}
div { class: "archive-posts flex-1",
for post in month_group.posts.iter() {
ArchiveEntry { post: post.clone() }
}
}
}
}
}
#[component]
fn ArchiveEntry(post: Post) -> Element {
let date_str = post.formatted_date();
rsx! {
div { class: "archive-entry relative py-1.5 my-2.5 group",
h3 { class: "archive-entry-title text-base font-normal text-gray-900 dark:text-[#dadadb] m-0",
"{post.title}"
}
div { class: "archive-meta text-sm text-gray-400 dark:text-[#9b9c9d] mt-1",
"{date_str}"
}
a {
class: "entry-link absolute inset-0 z-10",
aria_label: "post link to {post.title}",
href: "/post/{post.slug}",
onclick: move |evt| {
evt.prevent_default();
dioxus::router::navigator().push(format!("/post/{}", post.slug).as_str());
},
}
}
}
}