style(admin): redesign admin UI with soft rounded corners and Catppuccin theme
This commit is contained in:
parent
9f69a132fa
commit
38ba6692d3
@ -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..."
|
"正在验证身份..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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";
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// 组件
|
// 组件
|
||||||
|
|||||||
@ -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![
|
||||||
|
|||||||
@ -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}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
"发布文章"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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", "数据库与服务器诊断" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()})" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
"取消"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user