style(admin): redesign admin UI with soft rounded corners and Catppuccin theme

This commit is contained in:
xfy 2026-07-03 15:46:18 +08:00
parent 9f69a132fa
commit 38ba6692d3
8 changed files with 82 additions and 79 deletions

View File

@ -1,8 +1,7 @@
//! 后台管理布局组件 //! 后台管理布局组件
//! //!
//! 提供全新设计的工业/极简风格的管理员专属后台布局。 //! 提供全新设计的柔和/软扁平化风格的管理员专属后台布局。
//! 采用 Linear-style / Vercel-style 全宽顶部导航,去除冗余圆角与阴影, //! 采用圆角矩形、大空间距与友好的交互设计。
//! 突出数据密度与严谨性(工业/控制台美学)。
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus::router::components::Link; use dioxus::router::components::Link;
@ -67,9 +66,9 @@ pub fn AdminLayout() -> Element {
div { class: "flex items-center gap-8", div { class: "flex items-center gap-8",
// 品牌标识 / 回前台 // 品牌标识 / 回前台
Link { Link {
class: "font-bold text-sm tracking-widest uppercase hover:text-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 // Admin" "Yggdrasil"
} }
// 导航链接 // 导航链接
nav { class: "hidden md:flex items-center gap-6", nav { class: "hidden md:flex items-center gap-6",
@ -93,7 +92,7 @@ pub fn AdminLayout() -> Element {
div { class: "flex items-center gap-4", div { class: "flex items-center gap-4",
ThemeToggle {} ThemeToggle {}
button { 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", 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 |_| { onclick: move |_| {
spawn(async move { spawn(async move {
let _ = logout().await; let _ = logout().await;
@ -102,7 +101,7 @@ pub fn AdminLayout() -> Element {
let _ = navigator.push(Route::Login {}); let _ = navigator.push(Route::Login {});
}); });
}, },
"LOGOUT" "登出"
} }
} }
} }
@ -121,8 +120,8 @@ pub fn AdminLayout() -> Element {
(true, None) => { (true, None) => {
rsx! { rsx! {
div { class: "{root_class}", div { class: "{root_class}",
div { class: "flex-1 flex items-center justify-center font-mono text-xs text-paper-secondary tracking-widest", div { class: "flex-1 flex items-center justify-center font-medium text-sm text-[var(--color-paper-secondary)]",
"AUTHENTICATING..." "正在验证身份..."
} }
} }
} }

View File

@ -15,15 +15,15 @@ use crate::router::Route;
// 样式常量 // 样式常量
// =========================================================================== // ===========================================================================
/// Admin 卡片容器:锐利直角/微小圆角,工业控制台风格。 /// Admin 卡片容器:大圆角柔和阴影Catppuccin 风格。
pub const ADMIN_CARD_CLASS: &str = "bg-paper-entry border border-paper-border rounded-sm"; pub const ADMIN_CARD_CLASS: &str = "bg-paper-entry border border-paper-border rounded-2xl shadow-sm";
/// Admin 表格容器:锐利直角/微小圆角,与控制台一致。 /// Admin 表格容器:大圆角,柔和阴影,与卡片一致。
pub const ADMIN_TABLE_CLASS: &str = "bg-paper-entry border border-paper-border rounded-sm"; pub const ADMIN_TABLE_CLASS: &str = "bg-paper-entry border border-paper-border rounded-2xl shadow-sm";
/// Admin 表格行 hover 态:底部分割线 + 悬停背景。 /// Admin 表格行 hover 态:底部分割线 + 悬停背景。
pub const ADMIN_ROW_HOVER: &str = pub const ADMIN_ROW_HOVER: &str =
"border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors"; "border-b border-paper-border last:border-b-0 hover:bg-[var(--color-paper-accent-soft)] transition-colors";
/// 列表复选框统一样式(全选表头 + 行内)。 /// 列表复选框统一样式(全选表头 + 行内)。
pub const CHECKBOX_CLASS: &str = "rounded border-paper-border"; pub const CHECKBOX_CLASS: &str = "rounded border-paper-border";
@ -42,13 +42,13 @@ pub const BADGE_BASE: &str =
/// 绿色实心小按钮(批量通过、批量恢复)。 /// 绿色实心小按钮(批量通过、批量恢复)。
pub const BTN_SOLID_GREEN: &str = pub const BTN_SOLID_GREEN: &str =
"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"; "px-4 py-1.5 text-sm font-medium bg-green-500/10 text-green-600 dark:text-green-400 rounded-full hover:bg-green-500/20 transition-colors cursor-pointer";
/// 琥珀色实心小按钮(批量标为垃圾)。 /// 琥珀色实心小按钮(批量标为垃圾)。
pub const BTN_SOLID_AMBER: &str = pub const BTN_SOLID_AMBER: &str =
"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"; "px-4 py-1.5 text-sm font-medium bg-amber-500/10 text-amber-600 dark:text-amber-400 rounded-full hover:bg-amber-500/20 transition-colors cursor-pointer";
/// 红色实心小按钮(批量删除、批量彻底删除)。 /// 红色实心小按钮(批量删除、批量彻底删除)。
pub const BTN_SOLID_RED: &str = pub const BTN_SOLID_RED: &str =
"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"; "px-4 py-1.5 text-sm font-medium bg-red-500/10 text-red-600 dark:text-red-400 rounded-full hover:bg-red-500/20 transition-colors cursor-pointer";
// --- 文字小按钮(表格行内操作:通过 / 垃圾 / 删除 / 恢复) --- // --- 文字小按钮(表格行内操作:通过 / 垃圾 / 删除 / 恢复) ---
@ -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-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"; "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";
// =========================================================================== // ===========================================================================
// 组件 // 组件

View File

@ -126,8 +126,13 @@ pub fn AdminCommentsPage(page: i32) -> Element {
}; };
rsx! { rsx! {
div { class: "space-y-6", div { class: "w-full max-w-7xl mx-auto space-y-6",
h1 { class: "text-2xl font-bold text-paper-primary", "评论管理" } 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", "所有文章评论" }
}
}
FilterTabs { FilterTabs {
items: vec![ items: vec![

View File

@ -52,21 +52,21 @@ 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-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-2xl font-semibold tracking-tight text-paper-primary", "SYSTEM_DASHBOARD" } h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" }
p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "Overview & Recent Activities" } p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "数据概览与近期活动" }
} }
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
Link { Link {
class: "{BTN_SECONDARY}", class: "{BTN_SECONDARY}",
to: Route::Posts {}, to: Route::Posts {},
"MANAGE_POSTS" "管理文章"
} }
Link { 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", class: "px-6 py-2 rounded-full text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] hover:opacity-90 shadow-sm transition-all cursor-pointer",
to: Route::Write {}, to: Route::Write {},
"+ NEW_POST" "发布文章"
} }
} }
} }
@ -76,9 +76,9 @@ pub fn Admin() -> Element {
match stats() { match stats() {
Some(s) => { Some(s) => {
rsx! { rsx! {
StatCard { value: s.total.to_string(), label: "TOTAL_POSTS", trend: "+12%" } StatCard { value: s.total.to_string(), label: "总文章数".to_string(), trend: "+12%".to_string() }
StatCard { value: s.published.to_string(), label: "PUBLISHED", trend: "Active" } StatCard { value: s.published.to_string(), label: "已发布".to_string(), trend: "活跃".to_string() }
StatCard { value: s.drafts.to_string(), label: "DRAFTS", trend: "Pending" } StatCard { value: s.drafts.to_string(), label: "草稿".to_string(), trend: "待处理".to_string() }
} }
} }
None => { None => {
@ -102,13 +102,13 @@ pub fn Admin() -> Element {
let (color_class, text_class) = if count > 0 { let (color_class, text_class) = if count > 0 {
("text-amber-500", "text-amber-500") ("text-amber-500", "text-amber-500")
} else { } else {
("text-paper-secondary", "text-paper-primary") ("text-[var(--color-paper-secondary)]", "text-[var(--color-paper-primary)]")
}; };
rsx! { rsx! {
div { class: "text-[11px] font-mono tracking-widest uppercase {color_class}", "PENDING_COMMENTS" } div { class: "text-sm font-medium {color_class}", "待审评论" }
div { class: "flex items-baseline justify-between", div { class: "flex items-baseline justify-between mt-4",
div { class: "text-4xl font-light tracking-tight {text_class}", "{count}" } 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 ->" } div { class: "text-xs font-medium text-[var(--color-paper-secondary)] group-hover:text-[var(--color-paper-primary)] transition-colors", "去审核 →" }
} }
} }
} }
@ -122,10 +122,10 @@ pub fn Admin() -> Element {
} }
} }
// 最近文章列表 (紧凑表格样式) // 最近文章列表
div { class: "mt-8", div { class: "mt-8",
div { class: "flex items-center justify-between mb-4", div { class: "flex items-center justify-between mb-4",
h2 { class: "text-sm font-mono tracking-widest text-paper-secondary uppercase", "RECENT_PUBLICATIONS" } h2 { class: "text-lg font-bold text-[var(--color-paper-primary)]", "近期文章" }
} }
div { class: "{ADMIN_CARD_CLASS} overflow-hidden", div { class: "{ADMIN_CARD_CLASS} overflow-hidden",
match recent_posts() { match recent_posts() {
@ -160,13 +160,12 @@ 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", 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: "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",
div { class: "flex justify-between items-start pl-2", div { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "{label}" }
div { class: "text-[11px] font-mono tracking-widest text-paper-secondary uppercase", "{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-[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}" } div { class: "text-4xl font-light tracking-tight text-[var(--color-paper-primary)] mt-4", "{value}" }
} }
} }
} }
@ -175,17 +174,16 @@ fn StatCard(value: String, label: String, trend: String) -> Element {
fn RecentPostItem(post: PostListItem) -> Element { fn RecentPostItem(post: PostListItem) -> Element {
let date_str = post.formatted_date(); let date_str = post.formatted_date();
let status_label = post.status_label(); let status_label = post.status_label();
// 把圆角的 badge 换成控制台风格的方角 badge let status_class = post.status_class();
let status_class = post.status_class().replace("rounded-full", "rounded-sm border border-paper-border");
rsx! { rsx! {
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 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", 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-xs text-[var(--color-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-sm font-medium text-[var(--color-paper-primary)] group-hover:text-[var(--color-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-xs px-2 py-0.5 {status_class}", "{status_label}" }
} }
span { class: "text-xs font-mono text-paper-secondary mt-2 sm:mt-0", "{date_str}" } span { class: "text-xs 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-2xl font-semibold tracking-tight text-paper-primary uppercase", "POST_MANAGEMENT" } h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "管理文章" }
p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "All Publications & Drafts" } p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "所有文章及草稿" }
} }
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
// 重建缓存工具条(抽取为子组件 RebuildCacheBar见文件末尾 // 重建缓存工具条(抽取为子组件 RebuildCacheBar见文件末尾
@ -88,9 +88,9 @@ pub fn PostsPage(page: i32) -> Element {
rebuild_result: rebuild_result, rebuild_result: rebuild_result,
} }
Link { 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", class: "px-6 py-2 rounded-full text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] hover:opacity-90 shadow-sm transition-all cursor-pointer",
to: Route::Write {}, to: Route::Write {},
"+ NEW_POST" "发布文章"
} }
} }
} }

View File

@ -58,10 +58,11 @@ pub fn System() -> Element {
rsx! { rsx! {
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 items-end justify-between 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-[var(--color-paper-border)] mb-6",
div { div {
h1 { class: "text-2xl font-semibold tracking-tight text-paper-primary uppercase", "SYSTEM_CONTROL_PANEL" } h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "系统面板" }
p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "Database & Server Diagnostics" } p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "数据库与服务器诊断" }
} }
} }

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-2xl font-semibold tracking-tight text-paper-primary uppercase", "TRASH_BIN" } h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]", "回收站" }
p { class: "text-sm text-paper-secondary mt-1 font-mono uppercase tracking-widest", "Deleted Items ({total()})" } p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "已删除文章 ({total()})" }
} }
} }

View File

@ -36,9 +36,9 @@ use dioxus::web::{WebEventExt, WebFileExt};
/// 元信息表单复用的样式常量label 与 input 各一份,避免多处重复粘贴)。 /// 元信息表单复用的样式常量label 与 input 各一份,避免多处重复粘贴)。
/// 提升到模块级以便 write_editor 与 CoverUploader 共用。 /// 提升到模块级以便 write_editor 与 CoverUploader 共用。
const META_LABEL_CLASS: &str = const META_LABEL_CLASS: &str =
"block text-[10px] font-mono text-[var(--color-paper-secondary)] uppercase tracking-widest mb-2"; "block text-sm font-medium text-[var(--color-paper-secondary)] mb-2";
const META_INPUT_CLASS: &str = const META_INPUT_CLASS: &str =
"w-full text-sm font-mono bg-transparent text-[var(--color-paper-primary)] placeholder-[var(--color-paper-tertiary)] focus:outline-none border-b border-[var(--color-paper-border)] focus:border-[var(--color-paper-primary)] transition-colors pb-1.5"; "w-full text-sm bg-[var(--color-paper-entry)] text-[var(--color-paper-primary)] placeholder-[var(--color-paper-tertiary)] focus:outline-none border border-[var(--color-paper-border)] focus:border-[var(--color-paper-primary)] rounded-xl px-4 py-2.5 shadow-sm transition-all";
/// 新建文章页面组件。 /// 新建文章页面组件。
/// ///
@ -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-2xl font-semibold tracking-tight text-[var(--color-paper-primary)] uppercase", h1 { class: "text-3xl font-bold tracking-tight text-[var(--color-paper-primary)]",
if is_edit { "EDIT_DOCUMENT" } else { "NEW_DOCUMENT" } if is_edit { "编辑文章" } else { "撰写新文章" }
} }
p { class: "text-sm text-[var(--color-paper-secondary)] mt-1 font-mono uppercase tracking-widest", "Content Editor System" } p { class: "text-base text-[var(--color-paper-secondary)] mt-1", "内容编辑器" }
} }
} }
@ -528,14 +528,14 @@ fn write_editor(post_id: Option<i32>) -> Element {
// 底部操作栏 - 跟随页面滚动 // 底部操作栏 - 跟随页面滚动
div { class: "flex items-center gap-4 pt-4 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 { button {
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", class: "px-6 py-2 rounded-full text-sm font-medium text-[var(--color-paper-secondary)] hover:text-[var(--color-paper-primary)] transition-colors cursor-pointer",
onclick: move |_| { onclick: move |_| {
let _ = dioxus::router::navigator().push(Route::Posts {}); let _ = dioxus::router::navigator().push(Route::Posts {});
}, },
"CANCEL" "取消"
} }
div { class: "w-px h-5 bg-[var(--color-paper-border)]" } div { class: "w-px h-5 bg-[var(--color-paper-border)]" }
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", div { class: "relative inline-flex items-center px-4 py-2 rounded-full text-sm font-medium text-[var(--color-paper-secondary)] border border-[var(--color-paper-border)] bg-[var(--color-paper-entry)] cursor-pointer hover:bg-[var(--color-paper-theme)] transition-colors",
select { select {
class: "absolute inset-0 w-full h-full opacity-0 cursor-pointer", class: "absolute inset-0 w-full h-full opacity-0 cursor-pointer",
style: "appearance: none; -webkit-appearance: none;", style: "appearance: none; -webkit-appearance: none;",
@ -565,10 +565,10 @@ fn write_editor(post_id: Option<i32>) -> Element {
} }
div { class: "w-px h-5 bg-[var(--color-paper-border)]" } div { class: "w-px h-5 bg-[var(--color-paper-border)]" }
button { button {
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" }, class: if saving() { "px-6 py-2 rounded-full text-sm font-medium bg-[var(--color-paper-tertiary)] text-[var(--color-paper-secondary)] cursor-not-allowed shadow-sm" } else { "px-6 py-2 rounded-full text-sm font-medium bg-[var(--color-paper-primary)] text-[var(--color-paper-theme)] hover:opacity-90 shadow-sm transition-all cursor-pointer" },
disabled: saving(), disabled: saving(),
onclick: on_submit, onclick: on_submit,
if saving() { "SAVING..." } else if is_edit { "UPDATE" } else { "PUBLISH" } if saving() { "保存中..." } else if is_edit { "更新文章" } else { "发布文章" }
} }
} }
} }
@ -677,7 +677,7 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
// —— 上传中:骨架占位 + 文案 —— // —— 上传中:骨架占位 + 文案 ——
if cover_uploading() { if cover_uploading() {
div { class: "absolute inset-0 flex flex-col items-center justify-center gap-3 bg-[var(--color-paper-tertiary)]/30 animate-pulse", div { class: "absolute inset-0 flex flex-col items-center justify-center gap-3 bg-[var(--color-paper-tertiary)]/30 animate-pulse",
span { class: "text-[10px] font-mono tracking-widest uppercase text-[var(--color-paper-secondary)]", "UPLOADING..." } span { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "正在上传..." }
} }
} }
@ -731,7 +731,7 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
} }
// 底部渐变遮罩 + "更换封面"提示hover 出现)。 // 底部渐变遮罩 + "更换封面"提示hover 出现)。
div { class: "absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover/cover:opacity-100 transition-opacity flex items-end justify-center pb-2 pointer-events-none", div { class: "absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover/cover:opacity-100 transition-opacity flex items-end justify-center pb-2 pointer-events-none",
span { class: "text-[10px] font-mono uppercase tracking-widest text-white/90", "CLICK_TO_REPLACE" } span { class: "text-sm font-medium text-white/90 drop-shadow-md", "点击更换封面" }
} }
} }
@ -758,19 +758,19 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
y2: "15", y2: "15",
} }
} }
span { class: "text-[10px] font-mono uppercase tracking-widest text-[var(--color-paper-secondary)] shrink-0", span { class: "text-sm font-medium text-[var(--color-paper-secondary)] shrink-0",
"DRAG / DROP / PASTE / BROWSE" "拖拽 / 粘贴 / 点击上传"
} }
// URL 文字链:阻止 label 的默认 file 触发,切换到 URL 输入模式。 // URL 文字链:阻止 label 的默认 file 触发,切换到 URL 输入模式。
span { span {
class: "text-[10px] font-mono uppercase tracking-widest text-[var(--color-paper-tertiary)] hover:text-[var(--color-paper-primary)] transition-colors ml-auto shrink-0", class: "text-sm font-medium text-[var(--color-paper-tertiary)] hover:text-[var(--color-paper-primary)] transition-colors ml-auto shrink-0",
onclick: move |evt| { onclick: move |evt| {
evt.prevent_default(); evt.prevent_default();
evt.stop_propagation(); evt.stop_propagation();
cover_url_mode.set(true); cover_url_mode.set(true);
cover_url_input.set(cover_image()); cover_url_input.set(cover_image());
}, },
"USE_URL" "输入链接"
} }
// 隐藏的 file input由 label 点击触发。 // 隐藏的 file input由 label 点击触发。
input { input {
@ -799,7 +799,7 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
div { class: "flex items-center gap-2 mt-2", div { class: "flex items-center gap-2 mt-2",
input { input {
class: "flex-1 {META_INPUT_CLASS}", class: "flex-1 {META_INPUT_CLASS}",
placeholder: "PASTE_IMAGE_URL...", placeholder: "粘贴图片链接...",
value: "{cover_url_input}", value: "{cover_url_input}",
oninput: move |evt| cover_url_input.set(evt.value()), oninput: move |evt| cover_url_input.set(evt.value()),
onkeydown: move |evt| { onkeydown: move |evt| {
@ -814,7 +814,7 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
}, },
} }
button { button {
class: "shrink-0 px-3 py-1 text-[10px] font-mono uppercase tracking-widest text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] rounded-sm hover:brightness-110 active:scale-[0.98] transition-all cursor-pointer", class: "shrink-0 px-4 py-1.5 rounded-full text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] hover:opacity-90 shadow-sm transition-all cursor-pointer",
onclick: move |_| { onclick: move |_| {
let v = cover_url_input().trim().to_string(); let v = cover_url_input().trim().to_string();
if !v.is_empty() { if !v.is_empty() {
@ -823,15 +823,15 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
cover_url_mode.set(false); cover_url_mode.set(false);
} }
}, },
"CONFIRM" "确认"
} }
button { button {
class: "shrink-0 px-3 py-1 text-[10px] font-mono uppercase tracking-widest text-[var(--color-paper-secondary)] hover:text-[var(--color-paper-primary)] transition-colors cursor-pointer", class: "shrink-0 px-4 py-1.5 rounded-full text-sm font-medium text-[var(--color-paper-secondary)] hover:bg-[var(--color-paper-entry)] transition-colors cursor-pointer",
onclick: move |_| { onclick: move |_| {
cover_url_mode.set(false); cover_url_mode.set(false);
cover_url_input.set(String::new()); cover_url_input.set(String::new());
}, },
"CANCEL" "取消"
} }
} }
} }