feat(ui): refactor EmptyState to accept dynamic string props and add empty state to archives page

This commit is contained in:
xfy 2026-06-29 10:11:00 +08:00
parent 824ad5a3d3
commit 178870427c
2 changed files with 32 additions and 18 deletions

View File

@ -14,7 +14,8 @@ use dioxus::router::components::Link;
#[derive(Props, Clone, PartialEq)]
pub struct EmptyStateAction {
/// 按钮文案。
pub label: &'static str,
#[props(into)]
pub label: String,
/// 跳转目标路由。
pub to: crate::router::Route,
}
@ -28,11 +29,14 @@ pub struct EmptyStateAction {
#[component]
pub fn EmptyState(
/// 主标题(通常为「还没有文章」之类)。
#[props(default)] title: Option<&'static str>,
#[props(into, default = "还没有文章".to_string())]
title: String,
/// 副文案,说明当前状态或引导用户。
#[props(default)] description: Option<&'static str>,
#[props(into, default = String::new())]
description: String,
/// 可选的行动按钮。
#[props(default)] action: Option<EmptyStateAction>,
#[props(default)]
action: Option<EmptyStateAction>,
) -> Element {
rsx! {
div { class: "flex flex-col items-center justify-center text-center py-20 px-4 page-enter",
@ -45,12 +49,12 @@ pub fn EmptyState(
}
// 主标题:衬线字体,与首页 H1 风格呼应但更轻量。
h2 { class: "mt-8 text-2xl font-bold tracking-tight text-paper-primary",
{title.unwrap_or("还没有文章")}
"{title}"
}
// 副文案:次要色,限宽保证可读性。
if let Some(desc) = description {
if !description.is_empty() {
p { class: "mt-3 text-sm leading-relaxed text-paper-secondary max-w-md",
{desc}
"{description}"
}
}
// 行动按钮:药丸形,与搜索页主按钮一致。
@ -58,7 +62,7 @@ pub fn EmptyState(
Link {
class: "mt-8 inline-flex items-center px-6 py-2 bg-paper-accent text-white rounded-full font-medium text-sm hover:brightness-110 active:scale-[0.98] transition-all duration-200",
to: act.to,
{act.label}
"{act.label}"
}
}
}

View File

@ -11,6 +11,7 @@ use dioxus::prelude::*;
use dioxus::router::components::Link;
use crate::api::posts::{list_published_posts, PostListResponse};
use crate::components::empty_state::EmptyState;
use crate::components::skeletons::archive_skeleton::ArchiveSkeleton;
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
use crate::models::post::PostListItem;
@ -119,17 +120,26 @@ fn ArchivesContent() -> Element {
let posts_data = posts_res.read();
match &*posts_data {
Some(Ok(PostListResponse { posts, total })) => {
let grouped = group_posts(posts);
rsx! {
div { class: "mt-2 text-base text-paper-secondary",
""
span { class: "font-medium text-paper-primary", "{total}" }
" 篇文章"
if *total == 0 {
rsx! {
EmptyState {
title: "还没有文章归档",
description: "发布文章后,这里会自动按年月进行归档显示。",
}
}
for year_group in grouped.iter() {
YearSection {
key: "{year_group.year}",
year_group: year_group.clone(),
} else {
let grouped = group_posts(posts);
rsx! {
div { class: "mt-2 text-base text-paper-secondary",
""
span { class: "font-medium text-paper-primary", "{total}" }
" 篇文章"
}
for year_group in grouped.iter() {
YearSection {
key: "{year_group.year}",
year_group: year_group.clone(),
}
}
}
}