style(admin): overhaul layout to modern minimalist sidebar design

This commit is contained in:
xfy 2026-07-03 15:50:59 +08:00
parent 38ba6692d3
commit d3a50728ed
8 changed files with 95 additions and 80 deletions

View File

@ -53,56 +53,59 @@ pub fn AdminLayout() -> Element {
matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }); matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. });
let main_class = if is_write_route { let main_class = if is_write_route {
"flex-1 w-full flex flex-col relative" "flex-1 flex flex-col relative w-full h-full overflow-hidden rounded-[2rem] bg-[var(--color-paper-theme)] shadow-sm border border-[var(--color-paper-border)]"
} else { } else {
"flex-1 w-full max-w-7xl mx-auto px-6 py-10" "flex-1 w-full max-w-7xl mx-auto px-10 py-12 overflow-y-auto"
}; };
let root_class = "min-h-dvh flex flex-col bg-paper-theme text-paper-primary font-sans"; let root_class = "min-h-dvh flex bg-[var(--color-paper-entry)] text-[var(--color-paper-primary)] font-sans";
let nav_content = rsx! { let nav_content = rsx! {
header { class: "w-full border-b border-paper-border bg-paper-theme sticky top-0 z-40", aside { class: "w-64 flex-shrink-0 hidden md:flex flex-col h-screen sticky top-0 p-6 bg-[var(--color-paper-entry)]",
div { class: "w-full max-w-7xl mx-auto px-6 h-14 flex items-center justify-between", // Logo
div { class: "flex items-center gap-8", div { class: "mb-10 px-4",
// 品牌标识 / 回前台 Link {
Link { class: "font-extrabold text-2xl tracking-tight text-[var(--color-paper-primary)] hover:text-[var(--color-paper-accent)] transition-colors",
class: "font-bold text-lg hover:text-[var(--color-paper-accent)] transition-colors tracking-tight", to: Route::Home {},
to: Route::Home {}, "Yggdrasil."
"Yggdrasil" }
} }
// 导航链接 // Nav Items
nav { class: "hidden md:flex items-center gap-6", nav { class: "flex-1 flex flex-col gap-2",
for (dest, label) in admin_nav_items { for (dest, label) in admin_nav_items {
{ {
let is_active = route == dest || (label == "写文章" && is_write_route) || (label == "回收站" && matches!(route, Route::TrashPage { .. })); let is_active = route == dest || (label == "写文章" && is_write_route) || (label == "回收站" && matches!(route, Route::TrashPage { .. }));
let text_class = if is_active { "text-paper-primary" } else { "text-paper-secondary hover:text-paper-primary" }; let base_class = "flex items-center px-4 py-3 rounded-2xl text-sm font-medium transition-all";
rsx! { let text_class = if is_active {
Link { "bg-[var(--color-paper-theme)] text-[var(--color-paper-primary)] shadow-sm border border-[var(--color-paper-border)]"
key: "{label}", } else {
class: "text-sm font-medium transition-colors {text_class}", "text-[var(--color-paper-secondary)] hover:bg-[var(--color-paper-theme)]/50 hover:text-[var(--color-paper-primary)] border border-transparent"
to: dest, };
"{label}" rsx! {
} Link {
} key: "{label}",
class: "{base_class} {text_class}",
to: dest,
"{label}"
} }
} }
} }
} }
// 右侧操作 }
div { class: "flex items-center gap-4", // Bottom Tools
ThemeToggle {} div { class: "mt-auto pt-6 border-t border-[var(--color-paper-border)] flex items-center justify-between px-4",
button { ThemeToggle {}
class: "text-sm font-medium px-4 py-1.5 bg-[var(--color-paper-entry)] border border-[var(--color-paper-border)] rounded-full shadow-sm hover:shadow-md transition-all cursor-pointer text-[var(--color-paper-secondary)] hover:text-[var(--color-paper-primary)]", button {
onclick: move |_| { class: "text-sm font-medium px-4 py-2 rounded-xl bg-[var(--color-paper-theme)] border border-[var(--color-paper-border)] shadow-sm hover:shadow-md transition-all text-[var(--color-paper-secondary)] hover:text-red-500 cursor-pointer",
spawn(async move { onclick: move |_| {
let _ = logout().await; spawn(async move {
ctx.user.set(None); let _ = logout().await;
ctx.checked.set(false); ctx.user.set(None);
let _ = navigator.push(Route::Login {}); ctx.checked.set(false);
}); let _ = navigator.push(Route::Login {});
}, });
"登出" },
} "退出"
} }
} }
} }
@ -113,7 +116,15 @@ pub fn AdminLayout() -> Element {
rsx! { rsx! {
div { class: "{root_class}", div { class: "{root_class}",
{nav_content} {nav_content}
main { class: "{main_class}", Outlet::<Route> {} } div { class: "flex-1 flex flex-col min-w-0 h-screen p-2 md:p-4",
if !is_write_route {
div { class: "flex-1 bg-[var(--color-paper-theme)] rounded-[2rem] shadow-sm border border-[var(--color-paper-border)] overflow-y-auto relative flex flex-col",
main { class: "{main_class}", Outlet::<Route> {} }
}
} else {
main { class: "{main_class}", Outlet::<Route> {} }
}
}
} }
} }
} }
@ -130,12 +141,16 @@ pub fn AdminLayout() -> Element {
rsx! { rsx! {
div { class: "{root_class}", div { class: "{root_class}",
{nav_content} {nav_content}
main { class: "{main_class}", div { class: "flex-1 flex flex-col min-w-0 h-screen p-2 md:p-4",
div { class: if show_skeleton() { "" } else { "opacity-0" }, div { class: "flex-1 bg-[var(--color-paper-theme)] rounded-[2rem] shadow-sm border border-[var(--color-paper-border)] overflow-hidden relative flex flex-col",
{ main { class: "{main_class}",
match route { div { class: if show_skeleton() { "p-10" } else { "opacity-0" },
Route::Write {} => rsx! { WriteSkeleton {} }, {
_ => rsx! { AdminDashboardSkeleton {} }, match route {
Route::Write {} => rsx! { WriteSkeleton {} },
_ => rsx! { AdminDashboardSkeleton {} },
}
}
} }
} }
} }

View File

@ -15,11 +15,11 @@ use crate::router::Route;
// 样式常量 // 样式常量
// =========================================================================== // ===========================================================================
/// Admin 卡片容器:大圆角,柔和阴影Catppuccin 风格 /// Admin 卡片容器:大圆角,极简风,现代简约
pub const ADMIN_CARD_CLASS: &str = "bg-paper-entry border border-paper-border rounded-2xl shadow-sm"; pub const ADMIN_CARD_CLASS: &str = "bg-[var(--color-paper-entry)] rounded-3xl shadow-sm border border-transparent hover:border-[var(--color-paper-border)] transition-colors";
/// Admin 表格容器:大圆角,柔和阴影,与卡片一致。 /// Admin 表格容器:大圆角,现代简约,与卡片一致。
pub const ADMIN_TABLE_CLASS: &str = "bg-paper-entry border border-paper-border rounded-2xl shadow-sm"; pub const ADMIN_TABLE_CLASS: &str = "bg-[var(--color-paper-entry)] rounded-3xl shadow-sm border border-transparent hover:border-[var(--color-paper-border)] transition-colors overflow-hidden";
/// Admin 表格行 hover 态:底部分割线 + 悬停背景。 /// Admin 表格行 hover 态:底部分割线 + 悬停背景。
pub const ADMIN_ROW_HOVER: &str = pub const ADMIN_ROW_HOVER: &str =
@ -65,9 +65,9 @@ pub const BTN_TEXT_ACCENT: &str =
// --- 次要按钮Teal 第二色ghost 描边风格,从属于主色 Green --- // --- 次要按钮Teal 第二色ghost 描边风格,从属于主色 Green ---
/// 次要按钮:柔和胶囊形状 /// 次要按钮:极简风次要操作
pub const BTN_SECONDARY: &str = pub const BTN_SECONDARY: &str =
"px-6 py-2 rounded-full text-sm font-medium text-center text-[var(--color-paper-secondary)] bg-[var(--color-paper-entry)] border border-[var(--color-paper-border)] hover:bg-[var(--color-paper-theme)] hover:text-[var(--color-paper-primary)] shadow-sm transition-all cursor-pointer"; "px-6 py-2.5 rounded-full text-sm font-medium text-center text-[var(--color-paper-secondary)] bg-[var(--color-paper-entry)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition-all cursor-pointer";
// =========================================================================== // ===========================================================================
// 组件 // 组件

View File

@ -129,8 +129,8 @@ pub fn AdminCommentsPage(page: i32) -> Element {
div { class: "w-full max-w-7xl mx-auto space-y-6", div { class: "w-full max-w-7xl mx-auto space-y-6",
div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)] mb-6", div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)] mb-6",
div { div {
h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "评论管理" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "评论管理" }
p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "所有文章评论" } p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "所有文章评论" }
} }
} }

View File

@ -13,7 +13,7 @@ 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::skeletons::atoms::SkeletonBox; use crate::components::skeletons::atoms::SkeletonBox;
use crate::components::ui::{ADMIN_CARD_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};
use crate::router::Route; use crate::router::Route;
@ -52,10 +52,10 @@ pub fn Admin() -> Element {
rsx! { rsx! {
div { class: "w-full max-w-7xl mx-auto space-y-8", div { class: "w-full max-w-7xl mx-auto space-y-8",
// 顶部标题和全局操作栏 // 顶部标题和全局操作栏
div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)]", div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-8 border-b border-[var(--color-paper-border)]/50",
div { div {
h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" }
p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "数据概览与近期活动" } p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据概览与近期活动" }
} }
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
Link { Link {
@ -95,7 +95,7 @@ pub fn Admin() -> Element {
// 评论待办卡片 (独立色块突出) // 评论待办卡片 (独立色块突出)
Link { Link {
class: "block {ADMIN_CARD_CLASS} p-6 bg-paper-entry hover:bg-paper-entry/80 transition-colors h-32 flex flex-col justify-between group", class: "block {ADMIN_CARD_CLASS} p-8 bg-[var(--color-paper-entry)] hover:bg-[var(--color-paper-border)]/20 transition-all h-36 flex flex-col justify-between group hover:-translate-y-1 hover:shadow-md duration-300",
to: Route::AdminComments {}, to: Route::AdminComments {},
match pending_count() { match pending_count() {
Some(count) => { Some(count) => {
@ -123,11 +123,11 @@ pub fn Admin() -> Element {
} }
// 最近文章列表 // 最近文章列表
div { class: "mt-8", div { class: "mt-12",
div { class: "flex items-center justify-between mb-4", div { class: "flex items-center justify-between mb-6",
h2 { class: "text-lg font-bold text-[var(--color-paper-primary)]", "近期文章" } h2 { class: "text-xl font-bold text-[var(--color-paper-primary)] tracking-tight", "近期文章" }
} }
div { class: "{ADMIN_CARD_CLASS} overflow-hidden", div { class: "{ADMIN_TABLE_CLASS}",
match recent_posts() { match recent_posts() {
Some(posts) => { Some(posts) => {
rsx! { rsx! {
@ -160,7 +160,7 @@ pub fn Admin() -> Element {
#[component] #[component]
fn StatCard(value: String, label: String, trend: String) -> Element { fn StatCard(value: String, label: String, trend: String) -> Element {
rsx! { rsx! {
div { class: "{ADMIN_CARD_CLASS} p-6 flex flex-col justify-between h-32 relative overflow-hidden group hover:shadow-md transition-shadow", div { class: "{ADMIN_CARD_CLASS} p-8 flex flex-col justify-between h-36 relative group hover:-translate-y-1 hover:shadow-md transition-all duration-300",
div { class: "flex justify-between items-start", div { class: "flex justify-between items-start",
div { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "{label}" } div { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "{label}" }
div { class: "text-xs px-2 py-0.5 rounded-full border border-[var(--color-paper-border)] text-[var(--color-paper-tertiary)]", "{trend}" } div { class: "text-xs px-2 py-0.5 rounded-full border border-[var(--color-paper-border)] text-[var(--color-paper-tertiary)]", "{trend}" }
@ -177,13 +177,13 @@ fn RecentPostItem(post: PostListItem) -> Element {
let status_class = post.status_class(); let status_class = post.status_class();
rsx! { rsx! {
div { class: "flex flex-col sm:flex-row sm:justify-between sm:items-center px-6 py-4 hover:bg-[var(--color-paper-accent-soft)] transition-colors cursor-pointer group", div { class: "flex flex-col sm:flex-row sm:justify-between sm:items-center px-8 py-5 hover:bg-[var(--color-paper-accent-soft)] transition-colors cursor-pointer group",
div { class: "flex items-center gap-4", div { class: "flex items-center gap-6",
span { class: "text-xs text-[var(--color-paper-tertiary)] w-12 hidden sm:block", "#{post.id:04}" } span { class: "text-xs font-mono text-[var(--color-paper-tertiary)] w-12 hidden sm:block", "#{post.id:04}" }
span { class: "text-sm font-medium text-[var(--color-paper-primary)] group-hover:text-[var(--color-paper-accent)] transition-colors", "{post.title}" } span { class: "text-base font-semibold text-[var(--color-paper-primary)] group-hover:text-[var(--color-paper-accent)] transition-colors", "{post.title}" }
span { class: "text-xs px-2 py-0.5 {status_class}", "{status_label}" } span { class: "text-xs px-3 py-1 font-medium rounded-full {status_class}", "{status_label}" }
} }
span { class: "text-xs text-[var(--color-paper-secondary)] mt-2 sm:mt-0", "{date_str}" } span { class: "text-sm text-[var(--color-paper-secondary)] mt-2 sm:mt-0", "{date_str}" }
} }
} }
} }

View File

@ -78,8 +78,8 @@ pub fn PostsPage(page: i32) -> Element {
div { class: "w-full max-w-7xl mx-auto space-y-6", div { class: "w-full max-w-7xl mx-auto space-y-6",
div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-paper-border mb-6", div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-paper-border mb-6",
div { div {
h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "管理文章" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "管理文章" }
p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "所有文章及草稿" } p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "所有文章及草稿" }
} }
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
// 重建缓存工具条(抽取为子组件 RebuildCacheBar见文件末尾 // 重建缓存工具条(抽取为子组件 RebuildCacheBar见文件末尾

View File

@ -61,8 +61,8 @@ pub fn System() -> Element {
// 页面标题 // 页面标题
div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)] mb-6", div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)] mb-6",
div { div {
h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "系统面板" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "系统面板" }
p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "数据库与服务器诊断" } p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据库与服务器诊断" }
} }
} }

View File

@ -82,8 +82,8 @@ pub fn TrashPage(page: i32) -> Element {
// 页面标题 // 页面标题
div { class: "flex items-end justify-between pb-6 border-b border-paper-border mb-6", div { class: "flex items-end justify-between pb-6 border-b border-paper-border mb-6",
div { div {
h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "回收站" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "回收站" }
p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "已删除文章 ({total()})" } p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "已删除文章 ({total()})" }
} }
} }

View File

@ -413,10 +413,10 @@ fn write_editor(post_id: Option<i32>) -> Element {
// 页面标题与状态 // 页面标题与状态
div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)]", div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)]",
div { div {
h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]",
if is_edit { "编辑文章" } else { "撰写新文章" } if is_edit { "编辑文章" } else { "撰写新文章" }
} }
p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "内容编辑器" } p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "内容编辑器" }
} }
} }