fix(admin): 仪表盘空数据时显示空状态占位

空库下 get_post_stats / list_posts 正常返回,但 list_posts 返回
Some([]) 时走有数据分支、循环 0 次,渲染出一个空表格容器,视觉
上仪表盘近期文章区域一片空白,没有空状态引导。

复用项目既有的 empty_state::EmptyState 约定(与 posts.rs / trash.rs /
comments.rs 列表页一致):Some(posts) 且 posts.is_empty() 时渲染
暂无文章插画 + 写文章CTA。空状态放在 ADMIN_TABLE_CLASS 容器
之外,避免 overflow-hidden 裁掉插画的 py-20 内边距。
This commit is contained in:
xfy 2026-07-04 22:49:25 +08:00
parent 383e6f6b43
commit 16bd644783

View File

@ -12,6 +12,7 @@ use crate::api::comments::get_pending_count;
use crate::api::posts::{get_post_stats, list_posts}; use crate::api::posts::{get_post_stats, list_posts};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use crate::api::posts::{PostListResponse, PostStatsResponse}; use crate::api::posts::{PostListResponse, PostStatsResponse};
use crate::components::empty_state::{EmptyState, EmptyStateAction};
use crate::components::skeletons::atoms::SkeletonBox; use crate::components::skeletons::atoms::SkeletonBox;
use crate::components::ui::{ADMIN_CARD_CLASS, ADMIN_TABLE_CLASS, BTN_SECONDARY}; use crate::components::ui::{ADMIN_CARD_CLASS, ADMIN_TABLE_CLASS, BTN_SECONDARY};
use crate::models::post::{PostListItem, PostStats}; use crate::models::post::{PostListItem, PostStats};
@ -127,10 +128,24 @@ pub fn Admin() -> Element {
div { class: "flex items-center justify-between mb-6", div { class: "flex items-center justify-between mb-6",
h2 { class: "text-xl font-bold text-[var(--color-paper-primary)] tracking-tight", "近期文章" } h2 { class: "text-xl font-bold text-[var(--color-paper-primary)] tracking-tight", "近期文章" }
} }
div { class: "{ADMIN_TABLE_CLASS}",
match recent_posts() { match recent_posts() {
// 空库 / 无文章:展示空状态占位(与 posts.rs 列表页一致)。
// 放在 ADMIN_TABLE_CLASS 容器之外,避免 overflow-hidden 裁掉插画的 py-20 内边距。
Some(posts) if posts.is_empty() => {
rsx! {
EmptyState {
title: "暂无文章",
description: "还没有创建任何文章,开始写下你的第一篇文字吧。",
action: Some(EmptyStateAction {
label: "写文章".to_string(),
to: Route::Write {},
}),
}
}
}
Some(posts) => { Some(posts) => {
rsx! { rsx! {
div { class: "{ADMIN_TABLE_CLASS}",
div { class: "divide-y divide-paper-border", div { class: "divide-y divide-paper-border",
for post in posts.iter().take(5) { for post in posts.iter().take(5) {
RecentPostItem { key: "{post.id}", post: post.clone() } RecentPostItem { key: "{post.id}", post: post.clone() }
@ -138,8 +153,11 @@ pub fn Admin() -> Element {
} }
} }
} }
}
// 加载中:骨架屏。
None => { None => {
rsx! { rsx! {
div { class: "{ADMIN_TABLE_CLASS}",
div { class: "divide-y divide-paper-border animate-pulse", div { class: "divide-y divide-paper-border animate-pulse",
for _ in 0..5 { for _ in 0..5 {
div { class: "flex justify-between items-center px-6 py-4", div { class: "flex justify-between items-center px-6 py-4",