style(admin): overhaul layout to modern minimalist sidebar design
This commit is contained in:
parent
38ba6692d3
commit
d3a50728ed
@ -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 {} },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
// ===========================================================================
|
||||
// 组件
|
||||
|
||||
@ -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", "所有文章评论" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,见文件末尾)。
|
||||
|
||||
@ -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", "数据库与服务器诊断" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()})" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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", "内容编辑器" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user