From d3a50728edc2638025a3c423d1a23248d40d5a84 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 3 Jul 2026 15:50:59 +0800 Subject: [PATCH] style(admin): overhaul layout to modern minimalist sidebar design --- src/components/admin_layout.rs | 111 +++++++++++++++++++-------------- src/components/ui.rs | 12 ++-- src/pages/admin/comments.rs | 4 +- src/pages/admin/dashboard.rs | 32 +++++----- src/pages/admin/posts.rs | 4 +- src/pages/admin/system.rs | 4 +- src/pages/admin/trash.rs | 4 +- src/pages/admin/write.rs | 4 +- 8 files changed, 95 insertions(+), 80 deletions(-) diff --git a/src/components/admin_layout.rs b/src/components/admin_layout.rs index c7c9df6..83f67a0 100644 --- a/src/components/admin_layout.rs +++ b/src/components/admin_layout.rs @@ -53,56 +53,59 @@ pub fn AdminLayout() -> Element { matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }); 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 { - "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! { - header { class: "w-full border-b border-paper-border bg-paper-theme sticky top-0 z-40", - div { class: "w-full max-w-7xl mx-auto px-6 h-14 flex items-center justify-between", - div { class: "flex items-center gap-8", - // 品牌标识 / 回前台 - Link { - class: "font-bold text-lg hover:text-[var(--color-paper-accent)] transition-colors tracking-tight", - to: Route::Home {}, - "Yggdrasil" - } - // 导航链接 - nav { class: "hidden md:flex items-center gap-6", - for (dest, label) in admin_nav_items { - { - 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" }; - rsx! { - Link { - key: "{label}", - class: "text-sm font-medium transition-colors {text_class}", - to: dest, - "{label}" - } - } + aside { class: "w-64 flex-shrink-0 hidden md:flex flex-col h-screen sticky top-0 p-6 bg-[var(--color-paper-entry)]", + // Logo + div { class: "mb-10 px-4", + Link { + class: "font-extrabold text-2xl tracking-tight text-[var(--color-paper-primary)] hover:text-[var(--color-paper-accent)] transition-colors", + to: Route::Home {}, + "Yggdrasil." + } + } + // Nav Items + nav { class: "flex-1 flex flex-col gap-2", + for (dest, label) in admin_nav_items { + { + let is_active = route == dest || (label == "写文章" && is_write_route) || (label == "回收站" && matches!(route, Route::TrashPage { .. })); + let base_class = "flex items-center px-4 py-3 rounded-2xl text-sm font-medium transition-all"; + let text_class = if is_active { + "bg-[var(--color-paper-theme)] text-[var(--color-paper-primary)] shadow-sm border border-[var(--color-paper-border)]" + } else { + "text-[var(--color-paper-secondary)] hover:bg-[var(--color-paper-theme)]/50 hover:text-[var(--color-paper-primary)] border border-transparent" + }; + rsx! { + Link { + key: "{label}", + class: "{base_class} {text_class}", + to: dest, + "{label}" } } } } - // 右侧操作 - div { class: "flex items-center gap-4", - ThemeToggle {} - button { - 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)]", - onclick: move |_| { - spawn(async move { - let _ = logout().await; - ctx.user.set(None); - ctx.checked.set(false); - let _ = navigator.push(Route::Login {}); - }); - }, - "登出" - } + } + // Bottom Tools + div { class: "mt-auto pt-6 border-t border-[var(--color-paper-border)] flex items-center justify-between px-4", + ThemeToggle {} + button { + 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", + onclick: move |_| { + spawn(async move { + let _ = logout().await; + ctx.user.set(None); + ctx.checked.set(false); + let _ = navigator.push(Route::Login {}); + }); + }, + "退出" } } } @@ -113,7 +116,15 @@ pub fn AdminLayout() -> Element { rsx! { div { class: "{root_class}", {nav_content} - main { class: "{main_class}", Outlet:: {} } + 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:: {} } + } + } else { + main { class: "{main_class}", Outlet:: {} } + } + } } } } @@ -130,12 +141,16 @@ pub fn AdminLayout() -> Element { rsx! { div { class: "{root_class}", {nav_content} - main { class: "{main_class}", - div { class: if show_skeleton() { "" } else { "opacity-0" }, - { - match route { - Route::Write {} => rsx! { WriteSkeleton {} }, - _ => rsx! { AdminDashboardSkeleton {} }, + div { class: "flex-1 flex flex-col min-w-0 h-screen p-2 md:p-4", + 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}", + div { class: if show_skeleton() { "p-10" } else { "opacity-0" }, + { + match route { + Route::Write {} => rsx! { WriteSkeleton {} }, + _ => rsx! { AdminDashboardSkeleton {} }, + } + } } } } diff --git a/src/components/ui.rs b/src/components/ui.rs index 1bce888..6693291 100644 --- a/src/components/ui.rs +++ b/src/components/ui.rs @@ -15,11 +15,11 @@ use crate::router::Route; // 样式常量 // =========================================================================== -/// Admin 卡片容器:大圆角,柔和阴影,Catppuccin 风格。 -pub const ADMIN_CARD_CLASS: &str = "bg-paper-entry border border-paper-border rounded-2xl shadow-sm"; +/// Admin 卡片容器:大圆角,极简风,现代简约。 +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 表格容器:大圆角,柔和阴影,与卡片一致。 -pub const ADMIN_TABLE_CLASS: &str = "bg-paper-entry border border-paper-border rounded-2xl shadow-sm"; +/// Admin 表格容器:大圆角,现代简约,与卡片一致。 +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 态:底部分割线 + 悬停背景。 pub const ADMIN_ROW_HOVER: &str = @@ -65,9 +65,9 @@ pub const BTN_TEXT_ACCENT: &str = // --- 次要按钮(Teal 第二色,ghost 描边风格,从属于主色 Green) --- -/// 次要按钮:柔和胶囊形状。 +/// 次要按钮:极简风次要操作。 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"; // =========================================================================== // 组件 diff --git a/src/pages/admin/comments.rs b/src/pages/admin/comments.rs index ec4b47d..5e2e39b 100644 --- a/src/pages/admin/comments.rs +++ b/src/pages/admin/comments.rs @@ -129,8 +129,8 @@ pub fn AdminCommentsPage(page: i32) -> Element { 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 { - h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "评论管理" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "所有文章评论" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "评论管理" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "所有文章评论" } } } diff --git a/src/pages/admin/dashboard.rs b/src/pages/admin/dashboard.rs index a08d1a4..bf5c9ec 100644 --- a/src/pages/admin/dashboard.rs +++ b/src/pages/admin/dashboard.rs @@ -13,7 +13,7 @@ use crate::api::posts::{get_post_stats, list_posts}; #[cfg(target_arch = "wasm32")] use crate::api::posts::{PostListResponse, PostStatsResponse}; 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::router::Route; @@ -52,10 +52,10 @@ pub fn Admin() -> Element { rsx! { 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 { - h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "数据概览与近期活动" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据概览与近期活动" } } div { class: "flex items-center gap-3", Link { @@ -95,7 +95,7 @@ pub fn Admin() -> Element { // 评论待办卡片 (独立色块突出) 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 {}, match pending_count() { Some(count) => { @@ -123,11 +123,11 @@ pub fn Admin() -> Element { } // 最近文章列表 - div { class: "mt-8", - div { class: "flex items-center justify-between mb-4", - h2 { class: "text-lg font-bold text-[var(--color-paper-primary)]", "近期文章" } + div { class: "mt-12", + div { class: "flex items-center justify-between mb-6", + 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() { Some(posts) => { rsx! { @@ -160,7 +160,7 @@ pub fn Admin() -> Element { #[component] fn StatCard(value: String, label: String, trend: String) -> Element { 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: "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}" } @@ -177,13 +177,13 @@ fn RecentPostItem(post: PostListItem) -> Element { let status_class = post.status_class(); 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 items-center gap-4", - span { class: "text-xs 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-xs px-2 py-0.5 {status_class}", "{status_label}" } + 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-6", + span { class: "text-xs font-mono text-[var(--color-paper-tertiary)] w-12 hidden sm:block", "#{post.id:04}" } + 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-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}" } } } } diff --git a/src/pages/admin/posts.rs b/src/pages/admin/posts.rs index 0ae51ec..5ee7325 100644 --- a/src/pages/admin/posts.rs +++ b/src/pages/admin/posts.rs @@ -78,8 +78,8 @@ pub fn PostsPage(page: i32) -> Element { 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 { - h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "管理文章" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "所有文章及草稿" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "管理文章" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "所有文章及草稿" } } div { class: "flex items-center gap-3", // 重建缓存工具条(抽取为子组件 RebuildCacheBar,见文件末尾)。 diff --git a/src/pages/admin/system.rs b/src/pages/admin/system.rs index e145879..8ed967e 100644 --- a/src/pages/admin/system.rs +++ b/src/pages/admin/system.rs @@ -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 { - h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "系统面板" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "数据库与服务器诊断" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "系统面板" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据库与服务器诊断" } } } diff --git a/src/pages/admin/trash.rs b/src/pages/admin/trash.rs index 03888be..614d4a4 100644 --- a/src/pages/admin/trash.rs +++ b/src/pages/admin/trash.rs @@ -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 { - h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "回收站" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "已删除文章 ({total()})" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "回收站" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "已删除文章 ({total()})" } } } diff --git a/src/pages/admin/write.rs b/src/pages/admin/write.rs index e51aa21..501b76b 100644 --- a/src/pages/admin/write.rs +++ b/src/pages/admin/write.rs @@ -413,10 +413,10 @@ fn write_editor(post_id: Option) -> 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 { - 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 { "撰写新文章" } } - p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "内容编辑器" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "内容编辑器" } } }