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 { .. });
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::<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! {
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 {} },
}
}
}
}
}

View File

@ -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";
// ===========================================================================
// 组件

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: "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", "所有文章评论" }
}
}

View File

@ -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}" }
}
}
}

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: "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见文件末尾

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 {
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", "数据库与服务器诊断" }
}
}

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 {
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()})" }
}
}

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 {
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", "内容编辑器" }
}
}