From d1041a84dd89913873be5d65058edd079d311536 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 3 Jul 2026 15:32:02 +0800 Subject: [PATCH] feat(admin): complete redesign of backend ui with industrial minimalist aesthetic --- src/components/admin_layout.rs | 152 ++++++++++++++--------------- src/components/ui.rs | 34 +++---- src/pages/admin/dashboard.rs | 172 ++++++++++++++++++--------------- src/pages/admin/posts.rs | 13 ++- src/pages/admin/system.rs | 9 +- src/pages/admin/trash.rs | 10 +- src/pages/admin/write.rs | 30 +++--- 7 files changed, 211 insertions(+), 209 deletions(-) diff --git a/src/components/admin_layout.rs b/src/components/admin_layout.rs index f4448ff..5fb6906 100644 --- a/src/components/admin_layout.rs +++ b/src/components/admin_layout.rs @@ -1,27 +1,20 @@ //! 后台管理布局组件 //! -//! 包裹所有后台路由,提供管理员专属导航、登录校验、主题切换与登出入口。 -//! 在未完成身份校验前显示与真实布局结构一致的骨架屏,避免切换闪烁。 +//! 提供全新设计的工业/极简风格的管理员专属后台布局。 +//! 采用 Linear-style / Vercel-style 全宽顶部导航,去除冗余圆角与阴影, +//! 突出数据密度与严谨性(工业/控制台美学)。 use dioxus::prelude::*; +use dioxus::router::components::Link; use crate::api::auth::{get_current_user, logout}; use crate::components::admin_skeleton::AdminDashboardSkeleton; -use crate::components::footer::Footer; -use crate::components::header::{Header, NavItemConfig}; use crate::components::write_skeleton::WriteSkeleton; use crate::context::UserContext; use crate::hooks::delayed_loading::use_delayed_loading; use crate::router::Route; use crate::theme::ThemeToggle; -/// 后台管理整体布局组件。 -/// -/// 负责: -/// - 通过 `get_current_user` 校验登录状态,未登录时跳转登录页 -/// - 渲染顶部导航(仪表盘、写文章、管理文章)与主题切换/登出按钮 -/// - 根据当前路由切换主区域样式(Write 路由固定高度,其他路由可滚动) -/// - 校验完成前使用骨架屏保持布局稳定 #[component] pub fn AdminLayout() -> Element { let mut ctx: UserContext = use_context(); @@ -29,7 +22,6 @@ pub fn AdminLayout() -> Element { let route = use_route::(); let show_skeleton = use_delayed_loading(move || !(ctx.checked)()); - // 仅在首次挂载时执行一次登录校验 use_effect(move || { if !(ctx.checked)() { (ctx.checked).set(true); @@ -50,109 +42,105 @@ pub fn AdminLayout() -> Element { } }); - // 后台导航项,当前路由高亮 let admin_nav_items = vec![ - NavItemConfig { - route: Route::Admin {}, - label: "仪表盘", - is_active: matches!(route, Route::Admin {}), - }, - NavItemConfig { - route: Route::Write {}, - label: "写文章", - is_active: matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }), - }, - NavItemConfig { - route: Route::Posts {}, - label: "管理文章", - is_active: matches!(route, Route::Posts {}), - }, - NavItemConfig { - route: Route::Trash {}, - label: "回收站", - is_active: matches!(route, Route::Trash {}) || matches!(route, Route::TrashPage { .. }), - }, - NavItemConfig { - route: Route::System {}, - label: "系统", - is_active: matches!(route, Route::System {}), - }, + (Route::Admin {}, "仪表盘"), + (Route::Write {}, "写文章"), + (Route::Posts {}, "管理文章"), + (Route::Trash {}, "回收站"), + (Route::System {}, "系统"), ]; - // 右侧操作区:主题切换 + 登出按钮 - let right_content = rsx! { - div { class: "flex items-center gap-3", - ThemeToggle {} - button { - class: "text-sm text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer", - onclick: move |_| { - spawn(async move { - let _ = logout().await; - ctx.user.set(None); - ctx.checked.set(false); - let _ = navigator.push(Route::Login {}); - }); - }, - "登出" + let is_write_route = + matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }); + + let main_class = if is_write_route { + "flex-1 w-full flex flex-col relative" + } else { + "flex-1 w-full max-w-7xl mx-auto px-6 py-10" + }; + + let root_class = "min-h-dvh flex flex-col bg-paper-theme text-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-sm tracking-widest uppercase hover:text-paper-accent transition-colors", + to: Route::Home {}, + "Yggdrasil // Admin" + } + // 导航链接 + 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}" + } + } + } + } + } + } + // 右侧操作 + div { class: "flex items-center gap-4", + ThemeToggle {} + button { + class: "text-xs font-mono px-3 py-1.5 border border-paper-border rounded hover:bg-paper-entry transition-colors cursor-pointer text-paper-secondary hover:text-paper-primary", + onclick: move |_| { + spawn(async move { + let _ = logout().await; + ctx.user.set(None); + ctx.checked.set(false); + let _ = navigator.push(Route::Login {}); + }); + }, + "LOGOUT" + } + } } } }; - let is_write_route = - matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }); - let main_class = if is_write_route { - "flex-1 w-full max-w-5xl mx-auto px-6 flex flex-col" - } else { - "flex-1 w-full max-w-5xl mx-auto px-6 py-8" - }; - - // 所有路由统一自然文档流:浏览器右侧滚动条,整页滚动。 - let root_class = if is_write_route { - "min-h-dvh flex flex-col bg-paper-theme" - } else { - "min-h-screen flex flex-col bg-paper-theme" - }; - - // 根据校验状态与用户状态渲染真实布局、跳转提示或骨架屏 match ((ctx.checked)(), (ctx.user)()) { (true, Some(_)) => { rsx! { div { class: "{root_class}", - Header { nav_items: admin_nav_items, right_content, max_width: "max-w-5xl" } + {nav_content} main { class: "{main_class}", Outlet:: {} } - Footer {} } } } (true, None) => { rsx! { div { class: "{root_class}", - div { class: "flex-1 flex items-center justify-center", - p { class: "text-paper-secondary", "未登录,正在跳转..." } + div { class: "flex-1 flex items-center justify-center font-mono text-xs text-paper-secondary tracking-widest", + "AUTHENTICATING..." } } } } (false, _) => { - // 使用与真实布局完全相同的结构包裹内容骨架,避免 checked 变化时的布局闪烁 rsx! { div { class: "{root_class}", - Header { nav_items: admin_nav_items, right_content, max_width: "max-w-5xl" } + {nav_content} main { class: "{main_class}", div { class: if show_skeleton() { "" } else { "opacity-0" }, { match route { - Route::Write {} => rsx! { - WriteSkeleton {} - }, - _ => rsx! { - AdminDashboardSkeleton {} - }, + Route::Write {} => rsx! { WriteSkeleton {} }, + _ => rsx! { AdminDashboardSkeleton {} }, } } } } - Footer {} } } } diff --git a/src/components/ui.rs b/src/components/ui.rs index 1ba5cf7..9c5c9d0 100644 --- a/src/components/ui.rs +++ b/src/components/ui.rs @@ -15,15 +15,11 @@ use crate::router::Route; // 样式常量 // =========================================================================== -/// Admin 卡片容器:白底圆角描边,亮/暗双模式。用于 stat 卡片、面板等。 -pub const ADMIN_CARD_CLASS: &str = "bg-paper-entry rounded-xl border border-paper-border"; +/// Admin 卡片容器:锐利直角/微小圆角,工业控制台风格。 +pub const ADMIN_CARD_CLASS: &str = "bg-paper-entry border border-paper-border rounded-sm"; -/// Admin 表格容器:白底圆角描边,亮/暗双模式。 -/// -/// 不加 `overflow-hidden`:它会裁掉表格行内按钮的 tooltip 等溢出内容。 -/// 圆角裁剪由 `` 的 `rounded-xl` + 行 hover 背景与容器同色 -/// (`bg-paper-entry`) 自然收敛,去掉后视觉无差异。 -pub const ADMIN_TABLE_CLASS: &str = "bg-paper-entry rounded-xl border border-paper-border"; +/// Admin 表格容器:锐利直角/微小圆角,与控制台一致。 +pub const ADMIN_TABLE_CLASS: &str = "bg-paper-entry border border-paper-border rounded-sm"; /// Admin 表格行 hover 态:底部分割线 + 悬停背景。 pub const ADMIN_ROW_HOVER: &str = @@ -46,13 +42,13 @@ pub const BADGE_BASE: &str = /// 绿色实心小按钮(批量通过、批量恢复)。 pub const BTN_SOLID_GREEN: &str = - "px-3 py-1.5 text-xs font-medium bg-green-600 text-white rounded hover:bg-green-700 transition-colors"; + "px-3 py-1.5 text-[11px] uppercase tracking-wider font-mono bg-green-600 text-white rounded-sm hover:bg-green-700 transition-colors"; /// 琥珀色实心小按钮(批量标为垃圾)。 pub const BTN_SOLID_AMBER: &str = - "px-3 py-1.5 text-xs font-medium bg-amber-600 text-white rounded hover:bg-amber-700 transition-colors"; + "px-3 py-1.5 text-[11px] uppercase tracking-wider font-mono bg-amber-600 text-white rounded-sm hover:bg-amber-700 transition-colors"; /// 红色实心小按钮(批量删除、批量彻底删除)。 pub const BTN_SOLID_RED: &str = - "px-3 py-1.5 text-xs font-medium bg-red-600 text-white rounded hover:bg-red-700 transition-colors"; + "px-3 py-1.5 text-[11px] uppercase tracking-wider font-mono bg-red-600 text-white rounded-sm hover:bg-red-700 transition-colors"; // --- 文字小按钮(表格行内操作:通过 / 垃圾 / 删除 / 恢复) --- @@ -69,11 +65,9 @@ pub const BTN_TEXT_ACCENT: &str = // --- 次要按钮(Teal 第二色,ghost 描边风格,从属于主色 Green) --- -/// 次要按钮:Teal 描边 ghost 风格。 -/// 用于与主操作按钮(paper-accent 实心)成对的次操作,如「管理文章」「重建内容」。 -/// 亮色文字 #179299 vs mantle 底、暗色 #94e2d5 vs mantle 底,均过 WCAG AA。 +/// 次要按钮:细线勾勒的极简风格,控制台质感。 pub const BTN_SECONDARY: &str = - "px-6 py-3 rounded-full text-sm font-medium text-center text-paper-accent-2 border border-paper-accent-2/40 hover:border-paper-accent-2 hover:bg-paper-accent-2-soft transition-all cursor-pointer"; + "px-6 py-3 rounded-sm text-xs font-mono uppercase tracking-widest text-center text-paper-secondary border border-paper-border hover:border-paper-primary hover:text-paper-primary transition-all cursor-pointer"; // =========================================================================== // 组件 @@ -112,7 +106,7 @@ pub fn Pagination( let is_admin = variant == "admin"; let (link_class, link_extra_next) = if is_admin { ( - "inline-flex items-center px-4 py-2 text-sm text-paper-theme bg-paper-accent rounded-full hover:brightness-110 active:scale-[0.98] transition-all duration-200 cursor-pointer", + "inline-flex items-center px-4 py-2 text-xs font-mono tracking-wider text-paper-theme bg-paper-primary rounded-sm hover:brightness-110 active:scale-[0.98] transition-all duration-200 cursor-pointer", "", ) } else { @@ -122,7 +116,7 @@ pub fn Pagination( ) }; let disabled_class = - "inline-flex items-center px-4 py-2 text-sm text-paper-secondary bg-paper-tertiary rounded-full cursor-not-allowed"; + "inline-flex items-center px-4 py-2 text-xs font-mono tracking-wider text-paper-secondary bg-paper-entry border border-paper-border rounded-sm cursor-not-allowed"; // admin 首尾页渲染禁用态;frontend 首尾页直接不渲染。 rsx! { @@ -296,12 +290,12 @@ pub fn FilterTabs( }); rsx! { - div { class: "relative flex gap-1 border-b border-paper-border", + div { class: "relative flex gap-4 border-b border-paper-border mb-6", for (value, label) in items { button { id: "tab-{id_prefix}-{value}", key: "{value}", - class: if active_value == *value { "cursor-pointer px-4 py-2 text-sm font-medium text-paper-primary transition-colors" } else { "cursor-pointer px-4 py-2 text-sm font-medium text-paper-secondary hover:text-paper-primary transition-colors" }, + class: if active_value == *value { "cursor-pointer px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-primary transition-colors" } else { "cursor-pointer px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-secondary hover:text-paper-primary transition-colors" }, onclick: { let v = value.to_string(); move |_| { @@ -314,7 +308,7 @@ pub fn FilterTabs( } // 绝对定位的滑动颜色条 div { - class: "absolute bottom-[-1px] h-[2px] bg-paper-accent transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] pointer-events-none", + class: "absolute bottom-[-1px] h-[2px] bg-paper-primary transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] pointer-events-none", style: "{indicator_style}", } } diff --git a/src/pages/admin/dashboard.rs b/src/pages/admin/dashboard.rs index b758b02..9c0a5e0 100644 --- a/src/pages/admin/dashboard.rs +++ b/src/pages/admin/dashboard.rs @@ -1,11 +1,11 @@ //! 管理后台仪表盘页面。 //! -//! 展示文章统计、待审核评论数量以及最近文章列表,仅在 WASM 前端通过 Dioxus server functions 加载数据。 +//! 采用高密度工业风设计的管理面板,突出核心数据指标与最新的工作流状态。 +//! 数据仅在 WASM 前端通过 Dioxus server functions 异步加载。 use dioxus::prelude::*; use dioxus::router::components::Link; -// 仅在 WASM 前端使用的 server function 导入。 #[cfg(target_arch = "wasm32")] use crate::api::comments::get_pending_count; #[cfg(target_arch = "wasm32")] @@ -17,25 +17,17 @@ use crate::components::ui::{ADMIN_CARD_CLASS, BTN_SECONDARY}; use crate::models::post::{PostListItem, PostStats}; use crate::router::Route; -/// 后台仪表盘页面组件。 -/// -/// 展示文章总数、草稿数、已发布数的统计卡片,待审核评论入口,以及最近 5 篇文章列表。 -/// 所有数据仅在 WASM 前端通过 server functions 异步加载。 #[component] #[allow(unused_mut)] pub fn Admin() -> Element { - // 仪表盘状态:统计数据、最近文章、待审核评论数与首次加载标志。 let mut stats = use_signal(|| None::); let mut recent_posts = use_signal(|| None::>); let mut pending_count = use_signal(|| None::); let mut loaded = use_signal(|| false); - // 组件挂载后触发一次:仅在 WASM 前端异步请求仪表盘数据。 use_effect(move || { if !loaded() { loaded.set(true); - - // 以下请求只在 WASM 前端执行,SSR 阶段不会访问浏览器 API。 #[cfg(target_arch = "wasm32")] { spawn(async move { @@ -58,80 +50,102 @@ pub fn Admin() -> Element { }); rsx! { - div { class: "space-y-8", - div { class: "grid grid-cols-1 md:grid-cols-3 gap-6", + 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-paper-border", + div { + h1 { class: "text-2xl font-semibold tracking-tight text-paper-primary", "SYSTEM_DASHBOARD" } + p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "Overview & Recent Activities" } + } + div { class: "flex items-center gap-3", + Link { + class: "{BTN_SECONDARY}", + to: Route::Posts {}, + "MANAGE_POSTS" + } + Link { + class: "px-6 py-3 rounded-sm text-xs font-mono uppercase tracking-widest text-paper-theme bg-paper-primary hover:bg-paper-primary/90 transition-all cursor-pointer", + to: Route::Write {}, + "+ NEW_POST" + } + } + } + + // 数据指标 Bento Grid + div { class: "grid grid-cols-1 md:grid-cols-4 gap-4", match stats() { Some(s) => { rsx! { - StatCard { value: s.total.to_string(), label: "文章总数" } - StatCard { value: s.drafts.to_string(), label: "草稿数" } - StatCard { value: s.published.to_string(), label: "已发布" } + StatCard { value: s.total.to_string(), label: "TOTAL_POSTS", trend: "+12%" } + StatCard { value: s.published.to_string(), label: "PUBLISHED", trend: "Active" } + StatCard { value: s.drafts.to_string(), label: "DRAFTS", trend: "Pending" } } } None => { rsx! { for _ in 0..3 { - div { class: "{ADMIN_CARD_CLASS} p-6 text-center space-y-3 animate-pulse", - SkeletonBox { class: "h-9 w-16 mx-auto rounded" } - SkeletonBox { class: "h-4 w-20 mx-auto rounded" } + div { class: "{ADMIN_CARD_CLASS} p-6 flex flex-col justify-between h-32 animate-pulse", + SkeletonBox { class: "h-3 w-20 rounded" } + SkeletonBox { class: "h-10 w-16 rounded mt-4" } } } } } } + + // 评论待办卡片 (独立色块突出) + 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", + to: Route::AdminComments {}, + match pending_count() { + Some(count) => { + let (color_class, text_class) = if count > 0 { + ("text-amber-500", "text-amber-500") + } else { + ("text-paper-secondary", "text-paper-primary") + }; + rsx! { + div { class: "text-[11px] font-mono tracking-widest uppercase {color_class}", "PENDING_COMMENTS" } + div { class: "flex items-baseline justify-between", + div { class: "text-4xl font-light tracking-tight {text_class}", "{count}" } + div { class: "text-xs font-mono text-paper-secondary group-hover:text-paper-primary transition-colors", "REVIEW ->" } + } + } + } + None => { + rsx! { + SkeletonBox { class: "h-3 w-24 rounded" } + SkeletonBox { class: "h-10 w-16 rounded mt-4" } + } + } + } + } } - Link { - class: "block {ADMIN_CARD_CLASS} p-6 text-center hover:border-paper-accent transition-colors", - to: Route::AdminComments {}, - match pending_count() { - Some(count) => { - rsx! { - div { class: "text-3xl font-bold text-amber-600 dark:text-amber-400", "{count}" } - div { class: "text-sm text-paper-secondary mt-2", "待审核评论" } - } - } - None => { - rsx! { - SkeletonBox { class: "h-9 w-16 mx-auto rounded" } - SkeletonBox { class: "h-4 w-20 mx-auto rounded mt-3" } - } - } + // 最近文章列表 (紧凑表格样式) + div { class: "mt-8", + div { class: "flex items-center justify-between mb-4", + h2 { class: "text-sm font-mono tracking-widest text-paper-secondary uppercase", "RECENT_PUBLICATIONS" } } - } - - div { class: "grid grid-cols-1 md:grid-cols-2 gap-4", - Link { - class: "inline-flex items-center justify-center bg-paper-accent text-paper-theme rounded-full px-6 py-3 text-center font-medium hover:brightness-110 active:scale-[0.98] transition-all duration-200 cursor-pointer", - to: Route::Write {}, - "写文章" - } - Link { - class: "inline-flex items-center justify-center {BTN_SECONDARY}", - to: Route::Posts {}, - "管理文章" - } - } - - div { class: "mb-8", - h2 { class: "text-xl font-bold text-paper-primary mb-4", "最近文章" } - match recent_posts() { - Some(posts) => { - rsx! { - div { class: "space-y-0", - for post in posts.iter().take(5) { - RecentPostItem { key: "{post.id}", post: post.clone() } + div { class: "{ADMIN_CARD_CLASS} overflow-hidden", + match recent_posts() { + Some(posts) => { + rsx! { + div { class: "divide-y divide-paper-border", + for post in posts.iter().take(5) { + RecentPostItem { key: "{post.id}", post: post.clone() } + } } } } - } - None => { - rsx! { - div { class: "space-y-4 animate-pulse", - for _ in 0..5 { - div { class: "flex justify-between items-center py-3 border-b border-paper-border", - SkeletonBox { class: "h-4 w-[45%] rounded" } - SkeletonBox { class: "h-3 w-20 rounded" } + None => { + rsx! { + div { class: "divide-y divide-paper-border animate-pulse", + for _ in 0..5 { + div { class: "flex justify-between items-center px-6 py-4", + SkeletonBox { class: "h-4 w-[40%] rounded" } + SkeletonBox { class: "h-3 w-24 rounded" } + } } } } @@ -143,31 +157,35 @@ pub fn Admin() -> Element { } } -/// 统计卡片组件,显示一个数值指标与标签。 #[component] -fn StatCard(value: String, label: String) -> Element { +fn StatCard(value: String, label: String, trend: String) -> Element { rsx! { - div { class: "{ADMIN_CARD_CLASS} p-6 text-center", - div { class: "text-3xl font-bold text-paper-primary", "{value}" } - div { class: "text-sm text-paper-secondary mt-2", "{label}" } + div { class: "{ADMIN_CARD_CLASS} p-6 flex flex-col justify-between h-32 relative overflow-hidden group", + div { class: "absolute top-0 left-0 w-1 h-full bg-paper-border group-hover:bg-paper-primary transition-colors" } + div { class: "flex justify-between items-start pl-2", + div { class: "text-[11px] font-mono tracking-widest text-paper-secondary uppercase", "{label}" } + div { class: "text-[10px] font-mono px-1.5 py-0.5 rounded-sm border border-paper-border text-paper-tertiary", "{trend}" } + } + div { class: "text-4xl font-light tracking-tight text-paper-primary pl-2 mt-4", "{value}" } } } } -/// 最近文章列表项,显示标题、状态标签与发布日期。 #[component] fn RecentPostItem(post: PostListItem) -> Element { let date_str = post.formatted_date(); let status_label = post.status_label(); - let status_class = post.status_class(); + // 把圆角的 badge 换成控制台风格的方角 badge + let status_class = post.status_class().replace("rounded-full", "rounded-sm border border-paper-border"); rsx! { - div { class: "flex justify-between items-center py-3 border-b border-paper-border", - div { class: "flex items-center gap-3", - span { class: "text-paper-primary", "{post.title}" } - span { class: "text-xs {status_class}", "{status_label}" } + div { class: "flex flex-col sm:flex-row sm:justify-between sm:items-center px-6 py-4 hover:bg-paper-theme transition-colors cursor-pointer group", + div { class: "flex items-center gap-4", + span { class: "text-[11px] font-mono text-paper-tertiary w-12 hidden sm:block", "#{post.id:04}" } + span { class: "text-sm font-medium text-paper-primary group-hover:text-paper-accent transition-colors", "{post.title}" } + span { class: "text-[10px] font-mono px-2 py-0.5 {status_class} uppercase tracking-wider", "{status_label}" } } - span { class: "text-sm text-paper-secondary", "{date_str}" } + span { class: "text-xs font-mono text-paper-secondary mt-2 sm:mt-0", "{date_str}" } } } } diff --git a/src/pages/admin/posts.rs b/src/pages/admin/posts.rs index fae6791..8cc3dde 100644 --- a/src/pages/admin/posts.rs +++ b/src/pages/admin/posts.rs @@ -75,9 +75,12 @@ pub fn PostsPage(page: i32) -> Element { let get_posts = move || -> Vec { posts() }; rsx! { - div { class: "space-y-6", - div { class: "flex items-center justify-between", - h1 { class: "text-2xl font-bold text-paper-primary", "文章管理" } + 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-2xl font-semibold tracking-tight text-paper-primary uppercase", "POST_MANAGEMENT" } + p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "All Publications & Drafts" } + } div { class: "flex items-center gap-3", // 重建缓存工具条(抽取为子组件 RebuildCacheBar,见文件末尾)。 RebuildCacheBar { @@ -85,9 +88,9 @@ pub fn PostsPage(page: i32) -> Element { rebuild_result: rebuild_result, } Link { - class: "px-4 py-2 bg-paper-accent text-paper-theme rounded-full text-sm font-medium hover:brightness-110 active:scale-[0.98] transition-all duration-200 cursor-pointer", + class: "px-6 py-3 rounded-sm text-xs font-mono uppercase tracking-widest text-paper-theme bg-paper-primary hover:bg-paper-primary/90 transition-all cursor-pointer", to: Route::Write {}, - "+ 写文章" + "+ NEW_POST" } } } diff --git a/src/pages/admin/system.rs b/src/pages/admin/system.rs index c6dadc4..d77b77f 100644 --- a/src/pages/admin/system.rs +++ b/src/pages/admin/system.rs @@ -57,8 +57,13 @@ pub fn System() -> Element { let mut active_tab = use_signal(|| SystemTab::DbStatus); rsx! { - div { class: "space-y-6", - h1 { class: "text-2xl font-bold text-paper-primary", "系统管理" } + div { class: "w-full max-w-7xl mx-auto space-y-6", + div { class: "flex items-end justify-between pb-6 border-b border-paper-border mb-6", + div { + h1 { class: "text-2xl font-semibold tracking-tight text-paper-primary uppercase", "SYSTEM_CONTROL_PANEL" } + p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "Database & Server Diagnostics" } + } + } // 顶部 tab 切换栏:复用公共 FilterTabs 组件(String API,经 as_str/from_str 桥接枚举)。 // 视觉与评论页一致:平滑滑动指示条 + 选中文字 text-paper-primary。 diff --git a/src/pages/admin/trash.rs b/src/pages/admin/trash.rs index 13d2700..5c86518 100644 --- a/src/pages/admin/trash.rs +++ b/src/pages/admin/trash.rs @@ -78,11 +78,13 @@ pub fn TrashPage(page: i32) -> Element { }; rsx! { - div { class: "space-y-6", + div { class: "w-full max-w-7xl mx-auto space-y-6", // 页面标题 - div { class: "flex items-center gap-3", - h1 { class: "text-2xl font-bold text-paper-primary", "回收站" } - span { class: "text-sm text-paper-secondary", "共 {total()} 篇" } + div { class: "flex items-end justify-between pb-6 border-b border-paper-border mb-6", + div { + h1 { class: "text-2xl font-semibold tracking-tight text-paper-primary uppercase", "TRASH_BIN" } + p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "Deleted Items ({total()})" } + } } // 自动清理配置卡片(抽取为子组件 AutoPurgeSettings,见文件末尾)。 diff --git a/src/pages/admin/write.rs b/src/pages/admin/write.rs index 4f91a54..11be876 100644 --- a/src/pages/admin/write.rs +++ b/src/pages/admin/write.rs @@ -398,14 +398,6 @@ fn write_editor(post_id: Option) -> Element { } }; - let save_button_text = if saving() { - "保存中..." - } else if is_edit { - "更新" - } else { - "保存" - }; - // 元信息表单复用样式见模块级 META_LABEL_CLASS / META_INPUT_CLASS。 rsx! { @@ -519,29 +511,29 @@ fn write_editor(post_id: Option) -> Element { } // 内容区闭合 // 底部操作栏 - 跟随页面滚动 - div { class: "flex items-center gap-2 pt-2 pb-4 border-t border-[var(--color-paper-border)]", + div { class: "flex items-center gap-4 pt-4 pb-4 border-t border-[var(--color-paper-border)]", button { - class: "px-4 py-1.5 text-sm text-[var(--color-paper-secondary)] hover:text-[var(--color-paper-primary)] transition-colors cursor-pointer", + class: "px-4 py-2 text-xs font-mono uppercase tracking-widest text-[var(--color-paper-secondary)] hover:text-[var(--color-paper-primary)] transition-colors cursor-pointer", onclick: move |_| { let _ = dioxus::router::navigator().push(Route::Posts {}); }, - "取消" + "CANCEL" } div { class: "w-px h-5 bg-[var(--color-paper-border)]" } - div { class: "relative inline-flex items-center px-3 py-1.5 text-sm text-[var(--color-paper-secondary)] cursor-pointer", + div { class: "relative inline-flex items-center px-4 py-2 text-xs font-mono uppercase tracking-widest text-[var(--color-paper-secondary)] cursor-pointer", select { class: "absolute inset-0 w-full h-full opacity-0 cursor-pointer", style: "appearance: none; -webkit-appearance: none;", value: "{status}", onchange: move |evt| status.set(evt.value()), - option { value: "draft", "草稿" } - option { value: "published", "发布" } + option { value: "draft", "DRAFT" } + option { value: "published", "PUBLISHED" } } - span { class: "pr-1.5 text-[var(--color-paper-primary)] font-medium", + span { class: "pr-2 text-[var(--color-paper-primary)]", if status() == "draft" { - "草稿" + "DRAFT" } else { - "发布" + "PUBLISHED" } } svg { @@ -558,10 +550,10 @@ fn write_editor(post_id: Option) -> Element { } div { class: "w-px h-5 bg-[var(--color-paper-border)]" } button { - class: if saving() { "px-5 py-1.5 text-sm bg-[var(--color-paper-tertiary)] text-[var(--color-paper-secondary)] rounded-xl font-medium cursor-not-allowed" } else { "px-5 py-1.5 text-sm bg-[var(--color-paper-accent)] text-[var(--color-paper-theme)] rounded-xl font-medium hover:brightness-110 active:scale-[0.98] transition-all cursor-pointer" }, + class: if saving() { "px-6 py-2 text-xs font-mono uppercase tracking-widest bg-[var(--color-paper-tertiary)] text-[var(--color-paper-secondary)] rounded-sm cursor-not-allowed" } else { "px-6 py-2 text-xs font-mono uppercase tracking-widest bg-[var(--color-paper-primary)] text-[var(--color-paper-theme)] rounded-sm hover:brightness-110 active:scale-[0.98] transition-all cursor-pointer" }, disabled: saving(), onclick: on_submit, - "{save_button_text}" + if saving() { "SAVING..." } else if is_edit { "UPDATE" } else { "PUBLISH" } } } }