style: 全项目 rsx! 格式规范化(cargo fmt)
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
xfy 2026-07-15 17:49:03 +08:00
parent cbb8be7441
commit 886eeca915
19 changed files with 453 additions and 273 deletions

View File

@ -95,12 +95,7 @@ pub fn AdminLayout() -> Element {
"text-[var(--color-paper-secondary)] hover:bg-[var(--color-paper-theme)]/50 hover:text-[var(--color-paper-primary)] border border-transparent" "text-[var(--color-paper-secondary)] hover:bg-[var(--color-paper-theme)]/50 hover:text-[var(--color-paper-primary)] border border-transparent"
}; };
rsx! { rsx! {
Link { Link { key: "{label}", class: "{base_class} {text_class}", to: dest, "{label}" }
key: "{label}",
class: "{base_class} {text_class}",
to: dest,
"{label}"
}
} }
} }
} }
@ -149,8 +144,12 @@ pub fn AdminLayout() -> Element {
div { class: "flex-1 min-h-0 flex flex-col animate-pulse", div { class: "flex-1 min-h-0 flex flex-col animate-pulse",
{ {
match route { match route {
Route::Write {} => rsx! { WriteSkeleton {} }, Route::Write {} => rsx! {
_ => rsx! { AdminDashboardSkeleton {} }, WriteSkeleton {}
},
_ => rsx! {
AdminDashboardSkeleton {}
},
} }
} }
} }

View File

@ -487,7 +487,9 @@ pub fn CodeRunner(
div { class: "flex justify-between items-center px-4 py-2.5 border-b border-[var(--color-paper-border)] bg-[var(--color-paper-theme)]", div { class: "flex justify-between items-center px-4 py-2.5 border-b border-[var(--color-paper-border)] bg-[var(--color-paper-theme)]",
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
span { class: "w-2 h-2 rounded-full bg-[var(--color-paper-accent)]" } span { class: "w-2 h-2 rounded-full bg-[var(--color-paper-accent)]" }
span { class: "font-mono text-sm font-semibold text-[var(--color-paper-primary)]", "{language}" } span { class: "font-mono text-sm font-semibold text-[var(--color-paper-primary)]",
"{language}"
}
button { button {
class: format!( class: format!(
"text-[10px] px-1.5 py-0.5 rounded border transition cursor-pointer {}", "text-[10px] px-1.5 py-0.5 rounded border transition cursor-pointer {}",
@ -495,7 +497,7 @@ pub fn CodeRunner(
"bg-[var(--color-paper-accent)]/15 text-[var(--color-paper-accent)] border-[var(--color-paper-accent)]/30 font-semibold" "bg-[var(--color-paper-accent)]/15 text-[var(--color-paper-accent)] border-[var(--color-paper-accent)]/30 font-semibold"
} else { } else {
"bg-transparent text-[var(--color-paper-tertiary)] border-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)]" "bg-transparent text-[var(--color-paper-tertiary)] border-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)]"
} },
), ),
onclick: toggle_vim, onclick: toggle_vim,
"Vim" "Vim"
@ -506,7 +508,8 @@ pub fn CodeRunner(
disabled: running(), disabled: running(),
onclick: run_code, onclick: run_code,
if running() { if running() {
span { class: "inline-block w-3.5 h-3.5 text-[var(--color-paper-theme)]", span {
class: "inline-block w-3.5 h-3.5 text-[var(--color-paper-theme)]",
dangerous_inner_html: SPINNER_SVG, dangerous_inner_html: SPINNER_SVG,
} }
"{stage()}" "{stage()}"
@ -523,14 +526,28 @@ pub fn CodeRunner(
// editor_ready 由挂载 effect 置 true 后,此处 if 分支消失,骨架屏从 DOM 移除。 // editor_ready 由挂载 effect 置 true 后,此处 if 分支消失,骨架屏从 DOM 移除。
// 用绝对定位覆盖在(始终存在的)容器上方,不影响 CodeMirror 的 getElementById 挂载。 // 用绝对定位覆盖在(始终存在的)容器上方,不影响 CodeMirror 的 getElementById 挂载。
if !editor_ready() { if !editor_ready() {
div { div { class: "absolute inset-0 flex flex-col justify-center gap-2.5 px-4 py-4 bg-[var(--color-paper-code-block)]",
class: "absolute inset-0 flex flex-col justify-center gap-2.5 px-4 py-4 bg-[var(--color-paper-code-block)]",
// 代码行占位条:递减宽度模拟代码缩进,贴合等宽字体语境。 // 代码行占位条:递减宽度模拟代码缩进,贴合等宽字体语境。
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 90%" } div {
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 70%" } class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 55%" } style: "width: 90%",
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 85%" } }
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 40%" } div {
class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
style: "width: 70%",
}
div {
class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
style: "width: 55%",
}
div {
class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
style: "width: 85%",
}
div {
class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
style: "width: 40%",
}
} }
} }
} }
@ -548,9 +565,18 @@ pub fn CodeRunner(
// running 且尚未收到首个 chunk 时,显示骨架屏占位。 // running 且尚未收到首个 chunk 时,显示骨架屏占位。
if running() && !has_output() { if running() && !has_output() {
div { class: "absolute inset-0 flex flex-col justify-center gap-2.5 px-4 py-4 bg-[var(--color-paper-code-block)]", div { class: "absolute inset-0 flex flex-col justify-center gap-2.5 px-4 py-4 bg-[var(--color-paper-code-block)]",
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 70%" } div {
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 55%" } class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
div { class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", style: "width: 85%" } style: "width: 70%",
}
div {
class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
style: "width: 55%",
}
div {
class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse",
style: "width: 85%",
}
} }
} }
} }

View File

@ -119,8 +119,7 @@ pub fn CommentSection(post_id: i32) -> Element {
let has_any = approved_count > 0 || pending_count > 0; let has_any = approved_count > 0 || pending_count > 0;
if !has_any { if !has_any {
rsx! { rsx! {
p { class: "text-paper-tertiary text-center py-8", p { class: "text-paper-tertiary text-center py-8", "暂无评论,成为第一个评论的人吧!" }
"暂无评论,成为第一个评论的人吧!"
} }
} }
} else { } else {

View File

@ -170,11 +170,12 @@ pub fn PostContent(content_html: String) -> Element {
rsx! { rsx! {
div { class: "post-content md-content", div { class: "post-content md-content",
for (i, fragment) in fragments.iter().enumerate() { for (i, fragment) in fragments.iter().enumerate() {
{match fragment { {
match fragment {
ContentFragment::Html(html) => rsx! { ContentFragment::Html(html) => rsx! {
div { key: "html-{i}", dangerous_inner_html: "{html}" } div { key: "html-{i}", dangerous_inner_html: "{html}" }
}, },
ContentFragment::Runnable { lang, source, overrides } => rsx! { ContentFragment::Runnable { lang, source, overrides } => rsx! { // i 是片段序列中的确定性索引(来自纯函数 split_content_fragments
CodeRunner { CodeRunner {
key: "runner-{i}", key: "runner-{i}",
source: source.clone(), source: source.clone(),
@ -186,7 +187,8 @@ pub fn PostContent(content_html: String) -> Element {
instance_id: i, instance_id: i,
} }
}, },
}} }
}
} }
} }
} }

View File

@ -47,8 +47,7 @@ fn render_cell(value: &serde_json::Value) -> Element {
} }
}, },
Value::String(s) => rsx! { Value::String(s) => rsx! {
span { span { class: "block font-mono text-xs text-[var(--color-paper-secondary)] truncate {TEXT_CELL_MAX_WIDTH_CLASS}",
class: "block font-mono text-xs text-[var(--color-paper-secondary)] truncate {TEXT_CELL_MAX_WIDTH_CLASS}",
"{s}" "{s}"
} }
}, },
@ -79,9 +78,7 @@ fn render_expanded_value(col: &str, value: &serde_json::Value) -> Element {
span { class: "shrink-0 font-mono text-xs text-[var(--color-paper-tertiary)] min-w-[6rem]", span { class: "shrink-0 font-mono text-xs text-[var(--color-paper-tertiary)] min-w-[6rem]",
"{col}" "{col}"
} }
span { class: "font-mono text-xs text-[var(--color-paper-secondary)]", span { class: "font-mono text-xs text-[var(--color-paper-secondary)]", "{display}" }
"{display}"
}
} }
} }
} }
@ -114,8 +111,7 @@ pub fn SqlResultTable(props: SqlResultTableProps) -> Element {
tr { class: "border-b border-[var(--color-paper-border)] sticky top-0 bg-[var(--color-paper-entry)] z-10", tr { class: "border-b border-[var(--color-paper-border)] sticky top-0 bg-[var(--color-paper-entry)] z-10",
th { class: "w-8 px-2 py-2", "" } th { class: "w-8 px-2 py-2", "" }
for col in props.result.columns.iter() { for col in props.result.columns.iter() {
th { th { class: "px-4 py-2 text-left font-medium whitespace-nowrap text-[var(--color-paper-secondary)]",
class: "px-4 py-2 text-left font-medium whitespace-nowrap text-[var(--color-paper-secondary)]",
"{col}" "{col}"
} }
} }
@ -135,22 +131,23 @@ pub fn SqlResultTable(props: SqlResultTableProps) -> Element {
} }
}, },
td { class: "px-2 py-2 text-center text-[var(--color-paper-tertiary)] text-xs select-none", td { class: "px-2 py-2 text-center text-[var(--color-paper-tertiary)] text-xs select-none",
if expanded_row() == Some(row_idx) { "" } else { "" } if expanded_row() == Some(row_idx) {
""
} else {
""
}
} }
for cell in row.iter() { for cell in row.iter() {
td { td { class: "px-4 py-2 align-top", {render_cell(cell)} }
class: "px-4 py-2 align-top",
{render_cell(cell)}
}
} }
} }
// 展开详情行(跨列) // 展开详情行(跨列)
if expanded_row() == Some(row_idx) { if expanded_row() == Some(row_idx) {
tr { tr {
td { colspan: "{expand_colspan}", td {
colspan: "{expand_colspan}",
class: "px-4 py-3 bg-[var(--color-paper-theme)]", class: "px-4 py-3 bg-[var(--color-paper-theme)]",
div { div { class: "{EXPAND_MAX_HEIGHT_CLASS} overflow-y-auto space-y-0.5 whitespace-pre-wrap break-all",
class: "{EXPAND_MAX_HEIGHT_CLASS} overflow-y-auto space-y-0.5 whitespace-pre-wrap break-all",
for (ci, col) in props.result.columns.iter().enumerate() { for (ci, col) in props.result.columns.iter().enumerate() {
{render_expanded_value(col, row.get(ci).unwrap_or(&serde_json::Value::Null))} {render_expanded_value(col, row.get(ci).unwrap_or(&serde_json::Value::Null))}
} }

View File

@ -169,13 +169,13 @@ pub fn Pagination(
nav { class: if is_admin { "flex mt-6 justify-between" } else { "flex mt-10 mb-6 justify-between" }, nav { class: if is_admin { "flex mt-6 justify-between" } else { "flex mt-10 mb-6 justify-between" },
if has_prev { if has_prev {
if let Some(on_prev) = on_prev { if let Some(on_prev) = on_prev {
button { class: "{link_class}", onclick: move |_| on_prev.call(()), button {
class: "{link_class}",
onclick: move |_| on_prev.call(()),
{prev_inner} {prev_inner}
} }
} else { } else {
Link { class: "{link_class}", to: prev_route, Link { class: "{link_class}", to: prev_route, {prev_inner} }
{prev_inner}
}
} }
} else if is_admin { } else if is_admin {
span { class: "{disabled_class}", span { class: "{disabled_class}",
@ -193,11 +193,15 @@ pub fn Pagination(
if has_next { if has_next {
if let Some(on_next) = on_next { if let Some(on_next) = on_next {
button { class: "{link_class} {link_extra_next}", onclick: move |_| on_next.call(()), button {
class: "{link_class} {link_extra_next}",
onclick: move |_| on_next.call(()),
{next_inner} {next_inner}
} }
} else { } else {
Link { class: "{link_class} {link_extra_next}", to: next_route, Link {
class: "{link_class} {link_extra_next}",
to: next_route,
{next_inner} {next_inner}
} }
} }
@ -386,11 +390,7 @@ pub fn Popover(
onclick: move |_| on_close.call(()), onclick: move |_| on_close.call(()),
} }
// 面板:fixed 定位逃出 overflow-hidden 容器。 // 面板:fixed 定位逃出 overflow-hidden 容器。
div { div { class: "{POPOVER_PANEL_CLASS}", style: "{style}", {children} }
class: "{POPOVER_PANEL_CLASS}",
style: "{style}",
{children}
}
} }
} }

View File

@ -15,7 +15,9 @@ pub fn About() -> Element {
rsx! { rsx! {
div { class: "animate-page-enter", div { class: "animate-page-enter",
header { class: "page-header mb-6", header { class: "page-header mb-6",
h1 { class: "text-4xl font-bold text-paper-primary tracking-tight", "关于" } h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
"关于"
}
} }
article { class: "prose dark:prose-invert max-w-none text-paper-content leading-relaxed", article { class: "prose dark:prose-invert max-w-none text-paper-content leading-relaxed",
p { "世界……遗忘我……" } p { "世界……遗忘我……" }

View File

@ -129,8 +129,12 @@ pub fn AdminCommentsPage(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-[var(--color-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-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "评论管理" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]",
p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "所有文章评论" } "评论管理"
}
p { class: "text-base text-[var(--color-paper-secondary)] mt-2",
"所有文章评论"
}
} }
} }

View File

@ -55,20 +55,16 @@ pub fn Admin() -> Element {
// 顶部标题和全局操作栏 // 顶部标题和全局操作栏
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 { 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 { div {
h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]",
p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据概览与近期活动" } "仪表盘"
}
p { class: "text-base text-[var(--color-paper-secondary)] mt-2",
"数据概览与近期活动"
}
} }
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
Link { Link { class: "{BTN_SECONDARY}", to: Route::Posts {}, "管理文章" }
class: "{BTN_SECONDARY}", Link { class: "{BTN_PRIMARY}", to: Route::Write {}, "发布文章" }
to: Route::Posts {},
"管理文章"
}
Link {
class: "{BTN_PRIMARY}",
to: Route::Write {},
"发布文章"
}
} }
} }
@ -77,9 +73,21 @@ pub fn Admin() -> Element {
match stats() { match stats() {
Some(s) => { Some(s) => {
rsx! { rsx! {
StatCard { value: s.total.to_string(), label: "总文章数".to_string(), trend: "+12%".to_string() } StatCard {
StatCard { value: s.published.to_string(), label: "已发布".to_string(), trend: "活跃".to_string() } value: s.total.to_string(),
StatCard { value: s.drafts.to_string(), label: "草稿".to_string(), trend: "待处理".to_string() } label: "总文章数".to_string(),
trend: "+12%".to_string(),
}
StatCard {
value: s.published.to_string(),
label: "已发布".to_string(),
trend: "活跃".to_string(),
}
StatCard {
value: s.drafts.to_string(),
label: "草稿".to_string(),
trend: "待处理".to_string(),
}
} }
} }
None => { None => {
@ -103,13 +111,19 @@ 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-[var(--color-paper-secondary)]", "text-[var(--color-paper-primary)]") (
}; "text-[var(--color-paper-secondary)]",
"text-[var(--color-paper-primary)]",
)
}
}
rsx! { rsx! {
div { class: "text-sm font-medium {color_class}", "待审评论" } div { class: "text-sm font-medium {color_class}", "待审评论" }
div { class: "flex items-baseline justify-between mt-4", 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-medium text-[var(--color-paper-secondary)] group-hover:text-[var(--color-paper-primary)] transition-colors", "去审核 →" } div { class: "text-xs font-medium text-[var(--color-paper-secondary)] group-hover:text-[var(--color-paper-primary)] transition-colors",
"去审核 →"
}
} }
} }
} }
@ -126,7 +140,9 @@ pub fn Admin() -> Element {
// 最近文章列表 // 最近文章列表
div { class: "mt-12", div { class: "mt-12",
div { class: "flex items-center justify-between mb-6", div { class: "flex items-center justify-between mb-6",
h2 { class: "text-xl font-bold text-[var(--color-paper-primary)] tracking-tight", "近期文章" } h2 { class: "text-xl font-bold text-[var(--color-paper-primary)] tracking-tight",
"近期文章"
}
} }
match recent_posts() { match recent_posts() {
// 空库 / 无文章:展示空状态占位(与 posts.rs 列表页一致)。 // 空库 / 无文章:展示空状态占位(与 posts.rs 列表页一致)。
@ -180,10 +196,16 @@ fn StatCard(value: String, label: String, trend: String) -> Element {
rsx! { rsx! {
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: "{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: "flex justify-between items-start",
div { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "{label}" } div { class: "text-sm font-medium text-[var(--color-paper-secondary)]",
div { class: "text-xs px-2 py-0.5 rounded-full border border-[var(--color-paper-border)] text-[var(--color-paper-tertiary)]", "{trend}" } "{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-4xl font-light tracking-tight text-[var(--color-paper-primary)] mt-4",
"{value}"
} }
div { class: "text-4xl font-light tracking-tight text-[var(--color-paper-primary)] mt-4", "{value}" }
} }
} }
} }
@ -197,11 +219,19 @@ fn RecentPostItem(post: PostListItem) -> Element {
rsx! { rsx! {
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 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", 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-xs font-mono text-[var(--color-paper-tertiary)] w-12 hidden sm:block",
span { class: "text-base font-semibold text-[var(--color-paper-primary)] group-hover:text-[var(--color-paper-accent)] transition-colors", "{post.title}" } "#{post.id:04}"
span { class: "text-xs px-3 py-1 font-medium rounded-full {status_class}", "{status_label}" } }
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-sm text-[var(--color-paper-secondary)] mt-2 sm:mt-0",
"{date_str}"
} }
span { class: "text-sm text-[var(--color-paper-secondary)] mt-2 sm:mt-0", "{date_str}" }
} }
} }
} }

View File

@ -106,11 +106,7 @@ pub fn Posts() -> Element {
if active_tab() == PostsTab::All { if active_tab() == PostsTab::All {
div { class: "flex items-center gap-3", div { class: "flex items-center gap-3",
RebuildCacheBar {} RebuildCacheBar {}
Link { Link { class: "{BTN_PRIMARY}", to: Route::Write {}, "发布文章" }
class: "{BTN_PRIMARY}",
to: Route::Write {},
"发布文章"
}
} }
} }
} }
@ -125,8 +121,12 @@ pub fn Posts() -> Element {
// key 化条件渲染:切 tab 完全卸载/重挂,隔离各自 use_paginated 状态。 // key 化条件渲染:切 tab 完全卸载/重挂,隔离各自 use_paginated 状态。
div { key: "{active_tab().as_str()}", div { key: "{active_tab().as_str()}",
match active_tab() { match active_tab() {
PostsTab::All => rsx! { AllPostsList {} }, PostsTab::All => rsx! {
PostsTab::Trash => rsx! { PostsTrashPanel {} }, AllPostsList {}
},
PostsTab::Trash => rsx! {
PostsTrashPanel {}
},
} }
} }
} }
@ -184,13 +184,9 @@ fn AllPostsList() -> Element {
thead { thead {
tr { class: "border-b border-paper-border text-left text-paper-secondary", tr { class: "border-b border-paper-border text-left text-paper-secondary",
th { class: "px-4 py-3 font-medium", "标题" } th { class: "px-4 py-3 font-medium", "标题" }
th { class: "px-4 py-3 font-medium w-24 text-center", th { class: "px-4 py-3 font-medium w-24 text-center", "状态" }
"状态"
}
th { class: "px-4 py-3 font-medium w-32", "日期" } th { class: "px-4 py-3 font-medium w-32", "日期" }
th { class: "px-4 py-3 font-medium w-44 text-right", th { class: "px-4 py-3 font-medium w-44 text-right", "操作" }
"操作"
}
} }
} }
tbody { tbody {
@ -310,10 +306,7 @@ fn RebuildCacheBar() -> Element {
class: if rebuilding() { "relative px-4 py-2 rounded-full text-sm font-medium cursor-not-allowed text-paper-secondary border border-paper-border" } else { BTN_OUTLINE }, class: if rebuilding() { "relative px-4 py-2 rounded-full text-sm font-medium cursor-not-allowed text-paper-secondary border border-paper-border" } else { BTN_OUTLINE },
disabled: rebuilding(), disabled: rebuilding(),
onclick: move |_| do_rebuild(false), onclick: move |_| do_rebuild(false),
span { span { class: if rebuilding() { "opacity-40" } else { "" }, "重建内容" }
class: if rebuilding() { "opacity-40" } else { "" },
"重建内容"
}
if rebuilding() { if rebuilding() {
span { span {
class: "absolute inset-0 flex items-center justify-center", class: "absolute inset-0 flex items-center justify-center",
@ -329,10 +322,7 @@ fn RebuildCacheBar() -> Element {
class: if rebuilding() { "relative px-4 py-2 rounded-full text-sm font-medium cursor-not-allowed text-paper-secondary border border-paper-border" } else { BTN_OUTLINE }, class: if rebuilding() { "relative px-4 py-2 rounded-full text-sm font-medium cursor-not-allowed text-paper-secondary border border-paper-border" } else { BTN_OUTLINE },
disabled: rebuilding(), disabled: rebuilding(),
onclick: move |_| do_rebuild(true), onclick: move |_| do_rebuild(true),
span { span { class: if rebuilding() { "opacity-40" } else { "" }, "重建全部" }
class: if rebuilding() { "opacity-40" } else { "" },
"重建全部"
}
if rebuilding() { if rebuilding() {
span { span {
class: "absolute inset-0 flex items-center justify-center", class: "absolute inset-0 flex items-center justify-center",
@ -344,9 +334,7 @@ fn RebuildCacheBar() -> Element {
} }
// 重建结果消息:独立成行,右对齐,与按钮行同属本组件。 // 重建结果消息:独立成行,右对齐,与按钮行同属本组件。
if let Some(msg) = rebuild_result() { if let Some(msg) = rebuild_result() {
div { class: "text-xs text-paper-secondary whitespace-pre-line", div { class: "text-xs text-paper-secondary whitespace-pre-line", "{msg}" }
"{msg}"
}
} }
} }
} }
@ -388,20 +376,12 @@ fn PostRow(
to: Route::WriteEdit { id: post.id }, to: Route::WriteEdit { id: post.id },
"编辑" "编辑"
} }
Tooltip { Tooltip { tip: "重新渲染这篇文章的 HTML".to_string(),
tip: "重新渲染这篇文章的 HTML".to_string(),
button { button {
class: if rebuilding { class: if rebuilding { "relative inline-flex items-center text-xs text-paper-accent cursor-not-allowed" } else { BTN_TEXT_ACCENT },
"relative inline-flex items-center text-xs text-paper-accent cursor-not-allowed"
} else {
BTN_TEXT_ACCENT
},
disabled: rebuilding, disabled: rebuilding,
onclick: move |_| on_rebuild.call(post.id), onclick: move |_| on_rebuild.call(post.id),
span { span { class: if rebuilding { "opacity-40" } else { "" }, "重建" }
class: if rebuilding { "opacity-40" } else { "" },
"重建"
}
if rebuilding { if rebuilding {
span { span {
class: "absolute inset-0 flex items-center justify-center", class: "absolute inset-0 flex items-center justify-center",
@ -411,17 +391,10 @@ fn PostRow(
} }
} }
button { button {
class: if deleting { class: if deleting { "relative inline-flex items-center text-xs text-paper-secondary cursor-not-allowed" } else { BTN_TEXT_RED },
"relative inline-flex items-center text-xs text-paper-secondary cursor-not-allowed"
} else {
BTN_TEXT_RED
},
disabled: deleting, disabled: deleting,
onclick: move |_| on_delete.call(post.id), onclick: move |_| on_delete.call(post.id),
span { span { class: if deleting { "opacity-40" } else { "" }, "删除" }
class: if deleting { "opacity-40" } else { "" },
"删除"
}
if deleting { if deleting {
span { span {
class: "absolute inset-0 flex items-center justify-center", class: "absolute inset-0 flex items-center justify-center",
@ -498,31 +471,18 @@ pub(super) fn PostsTabs(
div { class: "relative flex items-center gap-4 border-b border-paper-border", div { class: "relative flex items-center gap-4 border-b border-paper-border",
button { button {
id: "posts-tab-{id_prefix}-all", id: "posts-tab-{id_prefix}-all",
class: if !is_trash { class: if !is_trash { "inline-flex items-center px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-primary transition-colors cursor-pointer" } else { "inline-flex items-center px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer" },
"inline-flex items-center px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-primary transition-colors cursor-pointer"
} else {
"inline-flex items-center px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer"
},
onclick: move |_| on_change.call(PostsTab::All), onclick: move |_| on_change.call(PostsTab::All),
"全部文章" "全部文章"
} }
button { button {
id: "posts-tab-{id_prefix}-trash", id: "posts-tab-{id_prefix}-trash",
class: if is_trash { class: if is_trash { "inline-flex items-center gap-1.5 px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-primary transition-colors cursor-pointer" } else { "inline-flex items-center gap-1.5 px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer" },
"inline-flex items-center gap-1.5 px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-primary transition-colors cursor-pointer"
} else {
"inline-flex items-center gap-1.5 px-2 py-3 text-xs font-mono tracking-widest uppercase text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer"
},
onclick: move |_| on_change.call(PostsTab::Trash), onclick: move |_| on_change.call(PostsTab::Trash),
"回收站" "回收站"
// 数量角标有数据才显示。0 显示中性灰,>0 用主题强调色提醒。 // 数量角标有数据才显示。0 显示中性灰,>0 用主题强调色提醒。
if let Some(count) = trash_count() { if let Some(count) = trash_count() {
span { span { class: if count > 0 { "inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-[0.625rem] font-semibold normal-case tracking-normal bg-paper-accent-soft text-paper-accent" } else { "inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-[0.625rem] font-semibold normal-case tracking-normal bg-paper-tertiary text-paper-secondary" },
class: if count > 0 {
"inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-[0.625rem] font-semibold normal-case tracking-normal bg-paper-accent-soft text-paper-accent"
} else {
"inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-[0.625rem] font-semibold normal-case tracking-normal bg-paper-tertiary text-paper-secondary"
},
"{count}" "{count}"
} }
} }

View File

@ -57,7 +57,9 @@ pub fn Runner() -> Element {
// 页头:与 dashboard / posts / system 对齐h1 text-4xl + 底部分割线) // 页头:与 dashboard / posts / system 对齐h1 text-4xl + 底部分割线)
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 { 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 { div {
h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "代码试运行沙箱" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]",
"代码试运行沙箱"
}
p { class: "text-base text-[var(--color-paper-secondary)] mt-2", p { class: "text-base text-[var(--color-paper-secondary)] mt-2",
"在此快速试运行代码,验证文章中可运行代码块的预期输出。资源钳制与读者侧一致,速率限制对 admin 放行。" "在此快速试运行代码,验证文章中可运行代码块的预期输出。资源钳制与读者侧一致,速率限制对 admin 放行。"
} }
@ -68,7 +70,9 @@ pub fn Runner() -> Element {
div { class: "{ADMIN_CARD_CLASS} p-8 flex flex-col gap-6", div { class: "{ADMIN_CARD_CLASS} p-8 flex flex-col gap-6",
// 语言切换 // 语言切换
div { class: "flex flex-col gap-2", div { class: "flex flex-col gap-2",
label { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "语言" } label { class: "text-sm font-medium text-[var(--color-paper-secondary)]",
"语言"
}
div { class: "flex gap-2", div { class: "flex gap-2",
for l in SUPPORTED_LANGS { for l in SUPPORTED_LANGS {
button { button {
@ -77,7 +81,8 @@ pub fn Runner() -> Element {
BTN_PRIMARY_SM BTN_PRIMARY_SM
} else { } else {
"px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-secondary)] bg-[var(--color-paper-theme)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition cursor-pointer" "px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-secondary)] bg-[var(--color-paper-theme)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition cursor-pointer"
}).to_string(), })
.to_string(),
onclick: { onclick: {
let ll = (*l).to_string(); let ll = (*l).to_string();
move |_| { move |_| {
@ -119,7 +124,7 @@ pub fn Runner() -> Element {
CodeRunner { CodeRunner {
source: source(), source: source(),
language: lang(), language: lang(),
overrides: overrides, overrides,
instance_id: 0, instance_id: 0,
} }
} }

View File

@ -65,8 +65,12 @@ 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 { 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-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "系统面板" } h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]",
p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据库与服务器诊断" } "系统面板"
}
p { class: "text-base text-[var(--color-paper-secondary)] mt-2",
"数据库与服务器诊断"
}
} }
} }
@ -92,11 +96,21 @@ pub fn System() -> Element {
// 避免 hook slot 复用导致 DelayedSkeleton 的 visible 信号残留为 true。 // 避免 hook slot 复用导致 DelayedSkeleton 的 visible 信号残留为 true。
div { key: "{active_tab().as_str()}", div { key: "{active_tab().as_str()}",
match active_tab() { match active_tab() {
SystemTab::DbStatus => rsx! { DbStatusTab {} }, SystemTab::DbStatus => rsx! {
SystemTab::ServerStatus => rsx! { ServerStatusTab {} }, DbStatusTab {}
SystemTab::SqlConsole => rsx! { SqlConsoleTab {} }, },
SystemTab::Export => rsx! { ExportTab {} }, SystemTab::ServerStatus => rsx! {
SystemTab::Backup => rsx! { BackupTab {} }, ServerStatusTab {}
},
SystemTab::SqlConsole => rsx! {
SqlConsoleTab {}
},
SystemTab::Export => rsx! {
ExportTab {}
},
SystemTab::Backup => rsx! {
BackupTab {}
},
} }
} }
} }
@ -252,13 +266,16 @@ fn DbStatusTab() -> Element {
value: "{refresh_interval().map(|s| s.to_string()).unwrap_or_default()}", value: "{refresh_interval().map(|s| s.to_string()).unwrap_or_default()}",
onchange: move |e| { onchange: move |e| {
let v = e.value(); let v = e.value();
refresh_interval.set(match v.as_str() { refresh_interval
.set(
match v.as_str() {
"1" => Some(1), "1" => Some(1),
"2" => Some(2), "2" => Some(2),
"5" => Some(5), "5" => Some(5),
"30" => Some(30), "30" => Some(30),
_ => None, _ => None,
}); },
);
}, },
option { value: "", "手动" } option { value: "", "手动" }
option { value: "1", "1s" } option { value: "1", "1s" }
@ -278,15 +295,21 @@ fn DbStatusTab() -> Element {
div { class: "grid grid-cols-2 md:grid-cols-4 gap-4", div { class: "grid grid-cols-2 md:grid-cols-4 gap-4",
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "数据库总大小" } p { class: "text-xs text-paper-secondary", "数据库总大小" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{format_bytes(s.db_size_bytes)}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{format_bytes(s.db_size_bytes)}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "连接数" } p { class: "text-xs text-paper-secondary", "连接数" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{s.total_connections} / {s.max_connections}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{s.total_connections} / {s.max_connections}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "表数量" } p { class: "text-xs text-paper-secondary", "表数量" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{s.tables.len()}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{s.tables.len()}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "迁移版本" } p { class: "text-xs text-paper-secondary", "迁移版本" }
@ -306,24 +329,48 @@ fn DbStatusTab() -> Element {
thead { thead {
tr { class: "border-b border-paper-border text-left text-paper-secondary", tr { class: "border-b border-paper-border text-left text-paper-secondary",
th { class: "px-4 py-2 font-medium", "表名" } th { class: "px-4 py-2 font-medium", "表名" }
th { class: "px-4 py-2 font-medium text-right", "行数" } th { class: "px-4 py-2 font-medium text-right",
th { class: "px-4 py-2 font-medium text-right", "表大小" } "行数"
th { class: "px-4 py-2 font-medium text-right", "索引大小" } }
th { class: "px-4 py-2 font-medium text-right", "总大小" } th { class: "px-4 py-2 font-medium text-right",
th { class: "px-4 py-2 font-medium text-right", "死元组" } "表大小"
}
th { class: "px-4 py-2 font-medium text-right",
"索引大小"
}
th { class: "px-4 py-2 font-medium text-right",
"总大小"
}
th { class: "px-4 py-2 font-medium text-right",
"死元组"
}
} }
} }
tbody { tbody {
for t in s.tables.iter() { for t in s.tables.iter() {
tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors", tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors",
td { class: "px-4 py-2 font-mono text-paper-primary", "{t.name}" } td { class: "px-4 py-2 font-mono text-paper-primary",
td { class: "px-4 py-2 text-right text-paper-secondary", "{t.name}"
if t.row_count_estimated { "~{t.row_count}" } else { "{t.row_count}" } }
td { class: "px-4 py-2 text-right text-paper-secondary",
if t.row_count_estimated {
"~{t.row_count}"
} else {
"{t.row_count}"
}
}
td { class: "px-4 py-2 text-right text-paper-secondary",
"{format_bytes(t.table_size_bytes)}"
}
td { class: "px-4 py-2 text-right text-paper-secondary",
"{format_bytes(t.index_size_bytes)}"
}
td { class: "px-4 py-2 text-right text-paper-primary font-medium",
"{format_bytes(t.total_size_bytes)}"
}
td { class: "px-4 py-2 text-right text-paper-secondary",
"{t.dead_tuples}"
} }
td { class: "px-4 py-2 text-right text-paper-secondary", "{format_bytes(t.table_size_bytes)}" }
td { class: "px-4 py-2 text-right text-paper-secondary", "{format_bytes(t.index_size_bytes)}" }
td { class: "px-4 py-2 text-right text-paper-primary font-medium", "{format_bytes(t.total_size_bytes)}" }
td { class: "px-4 py-2 text-right text-paper-secondary", "{t.dead_tuples}" }
} }
} }
} }
@ -343,15 +390,23 @@ fn DbStatusTab() -> Element {
tr { class: "border-b border-paper-border text-left text-paper-secondary", tr { class: "border-b border-paper-border text-left text-paper-secondary",
th { class: "px-4 py-2 font-medium", "索引名" } th { class: "px-4 py-2 font-medium", "索引名" }
th { class: "px-4 py-2 font-medium", "所属表" } th { class: "px-4 py-2 font-medium", "所属表" }
th { class: "px-4 py-2 font-medium text-right", "大小" } th { class: "px-4 py-2 font-medium text-right",
"大小"
}
} }
} }
tbody { tbody {
for i in s.top_indexes.iter() { for i in s.top_indexes.iter() {
tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors", tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors",
td { class: "px-4 py-2 font-mono text-paper-primary", "{i.name}" } td { class: "px-4 py-2 font-mono text-paper-primary",
td { class: "px-4 py-2 font-mono text-paper-secondary", "{i.table_name}" } "{i.name}"
td { class: "px-4 py-2 text-right text-paper-secondary", "{format_bytes(i.size_bytes)}" } }
td { class: "px-4 py-2 font-mono text-paper-secondary",
"{i.table_name}"
}
td { class: "px-4 py-2 text-right text-paper-secondary",
"{format_bytes(i.size_bytes)}"
}
} }
} }
} }
@ -372,20 +427,30 @@ fn DbStatusTab() -> Element {
th { class: "px-4 py-2 font-medium", "PID" } th { class: "px-4 py-2 font-medium", "PID" }
th { class: "px-4 py-2 font-medium", "用户" } th { class: "px-4 py-2 font-medium", "用户" }
th { class: "px-4 py-2 font-medium", "状态" } th { class: "px-4 py-2 font-medium", "状态" }
th { class: "px-4 py-2 font-medium text-right", "时长(秒)" } th { class: "px-4 py-2 font-medium text-right",
"时长(秒)"
}
th { class: "px-4 py-2 font-medium", "查询" } th { class: "px-4 py-2 font-medium", "查询" }
} }
} }
tbody { tbody {
for c in s.active_connections.iter() { for c in s.active_connections.iter() {
tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors", tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors",
td { class: "px-4 py-2 text-paper-secondary", "{c.pid}" } td { class: "px-4 py-2 text-paper-secondary",
td { class: "px-4 py-2 text-paper-secondary", "{c.user}" } "{c.pid}"
}
td { class: "px-4 py-2 text-paper-secondary",
"{c.user}"
}
td { class: "px-4 py-2 text-paper-secondary", td { class: "px-4 py-2 text-paper-secondary",
{c.state.clone().unwrap_or_else(|| "".to_string())} {c.state.clone().unwrap_or_else(|| "".to_string())}
} }
td { class: "px-4 py-2 text-right text-paper-secondary", td { class: "px-4 py-2 text-right text-paper-secondary",
{c.query_duration_secs.map(|d| format!("{:.1}", d)).unwrap_or_else(|| "".to_string())} {
c.query_duration_secs
.map(|d| format!("{:.1}", d))
.unwrap_or_else(|| "".to_string())
}
} }
td { class: "px-4 py-2 font-mono text-xs text-paper-secondary max-w-md truncate", td { class: "px-4 py-2 font-mono text-xs text-paper-secondary max-w-md truncate",
{c.query.clone().unwrap_or_else(|| "".to_string())} {c.query.clone().unwrap_or_else(|| "".to_string())}
@ -586,13 +651,16 @@ fn ServerStatusTab() -> Element {
value: "{refresh_ms().map(|s| s.to_string()).unwrap_or_default()}", value: "{refresh_ms().map(|s| s.to_string()).unwrap_or_default()}",
onchange: move |e| { onchange: move |e| {
let v = e.value(); let v = e.value();
refresh_ms.set(match v.as_str() { refresh_ms
.set(
match v.as_str() {
"500" => Some(500), "500" => Some(500),
"1000" => Some(1000), "1000" => Some(1000),
"2000" => Some(2000), "2000" => Some(2000),
"5000" => Some(5000), "5000" => Some(5000),
_ => None, _ => None,
}); },
);
}, },
option { value: "", "手动" } option { value: "", "手动" }
option { value: "500", "500ms" } option { value: "500", "500ms" }
@ -612,20 +680,30 @@ fn ServerStatusTab() -> Element {
div { class: "grid grid-cols-2 md:grid-cols-4 gap-4", div { class: "grid grid-cols-2 md:grid-cols-4 gap-4",
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "运行时间" } p { class: "text-xs text-paper-secondary", "运行时间" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{format_uptime(s.uptime_secs)}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{format_uptime(s.uptime_secs)}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "DB 连接池" } p { class: "text-xs text-paper-secondary", "DB 连接池" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{s.pool_size} / {s.pool_max_size}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
p { class: "text-xs text-paper-secondary", "空闲 {s.pool_available} · 等待 {s.pool_waiting}" } "{s.pool_size} / {s.pool_max_size}"
}
p { class: "text-xs text-paper-secondary",
"空闲 {s.pool_available} · 等待 {s.pool_waiting}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "活跃会话" } p { class: "text-xs text-paper-secondary", "活跃会话" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{s.active_sessions}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{s.active_sessions}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "CPU" } p { class: "text-xs text-paper-secondary", "CPU" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{cpu_pct}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{cpu_pct}"
}
} }
} }
@ -633,19 +711,27 @@ fn ServerStatusTab() -> Element {
div { class: "grid grid-cols-2 md:grid-cols-4 gap-4", div { class: "grid grid-cols-2 md:grid-cols-4 gap-4",
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "内存" } p { class: "text-xs text-paper-secondary", "内存" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{format_bytes(s.host.used_memory as i64)} / {format_bytes(s.host.total_memory as i64)}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{format_bytes(s.host.used_memory as i64)} / {format_bytes(s.host.total_memory as i64)}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "磁盘" } p { class: "text-xs text-paper-secondary", "磁盘" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{format_bytes((s.host.disk_total - s.host.disk_available) as i64)} / {format_bytes(s.host.disk_total as i64)}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{format_bytes((s.host.disk_total - s.host.disk_available) as i64)} / {format_bytes(s.host.disk_total as i64)}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "Load (1m)" } p { class: "text-xs text-paper-secondary", "Load (1m)" }
p { class: "mt-1 text-lg font-semibold text-paper-primary", "{load_1}" } p { class: "mt-1 text-lg font-semibold text-paper-primary",
"{load_1}"
}
} }
div { class: "{ADMIN_CARD_CLASS} p-4", div { class: "{ADMIN_CARD_CLASS} p-4",
p { class: "text-xs text-paper-secondary", "系统" } p { class: "text-xs text-paper-secondary", "系统" }
p { class: "mt-1 text-sm font-medium text-paper-primary truncate", "{s.host.os_name}" } p { class: "mt-1 text-sm font-medium text-paper-primary truncate",
"{s.host.os_name}"
}
} }
} }
@ -659,20 +745,38 @@ fn ServerStatusTab() -> Element {
thead { thead {
tr { class: "border-b border-paper-border text-left text-paper-secondary", tr { class: "border-b border-paper-border text-left text-paper-secondary",
th { class: "px-4 py-2 font-medium", "缓存" } th { class: "px-4 py-2 font-medium", "缓存" }
th { class: "px-4 py-2 font-medium text-right", "条目" } th { class: "px-4 py-2 font-medium text-right",
th { class: "px-4 py-2 font-medium text-right", "命中" } "条目"
th { class: "px-4 py-2 font-medium text-right", "未命中" } }
th { class: "px-4 py-2 font-medium text-right", "命中率" } th { class: "px-4 py-2 font-medium text-right",
"命中"
}
th { class: "px-4 py-2 font-medium text-right",
"未命中"
}
th { class: "px-4 py-2 font-medium text-right",
"命中率"
}
} }
} }
tbody { tbody {
for (name, entry_count, hits, misses, rate_pct) in cache_rows.iter() { for (name, entry_count, hits, misses, rate_pct) in cache_rows.iter() {
tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors", tr { class: "border-b border-paper-border last:border-0 hover:bg-paper-entry transition-colors",
td { class: "px-4 py-2 text-paper-primary", "{name}" } td { class: "px-4 py-2 text-paper-primary",
td { class: "px-4 py-2 text-right text-paper-secondary", "{entry_count}" } "{name}"
td { class: "px-4 py-2 text-right text-paper-secondary", "{hits}" } }
td { class: "px-4 py-2 text-right text-paper-secondary", "{misses}" } td { class: "px-4 py-2 text-right text-paper-secondary",
td { class: "px-4 py-2 text-right text-paper-primary font-medium", "{rate_pct}" } "{entry_count}"
}
td { class: "px-4 py-2 text-right text-paper-secondary",
"{hits}"
}
td { class: "px-4 py-2 text-right text-paper-secondary",
"{misses}"
}
td { class: "px-4 py-2 text-right text-paper-primary font-medium",
"{rate_pct}"
}
} }
} }
} }
@ -968,9 +1072,13 @@ fn SqlConsoleTab() -> Element {
div { class: "flex justify-between items-center px-4 py-2.5 border-b border-[var(--color-paper-border)] bg-[var(--color-paper-theme)]", div { class: "flex justify-between items-center px-4 py-2.5 border-b border-[var(--color-paper-border)] bg-[var(--color-paper-theme)]",
div { class: "flex items-center gap-2", div { class: "flex items-center gap-2",
span { class: "w-2 h-2 rounded-full bg-[var(--color-paper-accent)]" } span { class: "w-2 h-2 rounded-full bg-[var(--color-paper-accent)]" }
span { class: "font-mono text-sm font-semibold text-[var(--color-paper-primary)]", "SQL" } span { class: "font-mono text-sm font-semibold text-[var(--color-paper-primary)]",
"SQL"
}
}
span { class: "text-xs text-[var(--color-paper-tertiary)] font-mono",
"⌘↵ 执行"
} }
span { class: "text-xs text-[var(--color-paper-tertiary)] font-mono", "⌘↵ 执行" }
} }
// CodeMirror 容器:用 flex 让 .cm-editor(flex:1) 填满整个高度, // CodeMirror 容器:用 flex 让 .cm-editor(flex:1) 填满整个高度,
// 避免编辑器塌缩到内容高度、底部透出容器背景造成上下色差。 // 避免编辑器塌缩到内容高度、底部透出容器背景造成上下色差。
@ -1068,7 +1176,9 @@ fn SqlConsoleTab() -> Element {
div { class: "px-4 py-2 border-b border-paper-border text-sm font-medium text-paper-primary", div { class: "px-4 py-2 border-b border-paper-border text-sm font-medium text-paper-primary",
"执行计划" "执行计划"
} }
pre { class: "p-4 text-xs font-mono text-paper-secondary overflow-x-auto whitespace-pre m-0", "{explain}" } pre { class: "p-4 text-xs font-mono text-paper-secondary overflow-x-auto whitespace-pre m-0",
"{explain}"
}
} }
} }
} }
@ -1143,12 +1253,16 @@ fn ExportTab() -> Element {
value: "{table_name}", value: "{table_name}",
oninput: move |e| table_name.set(e.value()), oninput: move |e| table_name.set(e.value()),
} }
p { class: "text-xs text-paper-secondary mt-1", "仅支持 public schema 下的用户表,表名需为合法标识符" } p { class: "text-xs text-paper-secondary mt-1",
"仅支持 public schema 下的用户表,表名需为合法标识符"
}
} }
} else { } else {
// 查询输入 // 查询输入
div { div {
label { class: "block text-sm text-paper-secondary mb-1", "SELECT 查询(只读)" } label { class: "block text-sm text-paper-secondary mb-1",
"SELECT 查询(只读)"
}
textarea { textarea {
class: "w-full px-3 py-2 text-sm border border-paper-border rounded bg-paper-theme text-paper-primary font-mono", class: "w-full px-3 py-2 text-sm border border-paper-border rounded bg-paper-theme text-paper-primary font-mono",
rows: "4", rows: "4",
@ -1182,11 +1296,7 @@ fn ExportTab() -> Element {
} }
} }
button { button { class: "{BTN_PRIMARY_SM}", onclick: move |_| do_export(), "导出并下载" }
class: "{BTN_PRIMARY_SM}",
onclick: move |_| do_export(),
"导出并下载"
}
} }
p { class: "text-xs text-paper-secondary", p { class: "text-xs text-paper-secondary",
"导出走流式响应大表不会占满内存。SQL 格式仅含 INSERT 语句(不含 DDL/schema" "导出走流式响应大表不会占满内存。SQL 格式仅含 INSERT 语句(不含 DDL/schema"
@ -1357,7 +1467,10 @@ fn BackupTab() -> Element {
spawn(async move { spawn(async move {
match create_backup().await { match create_backup().await {
Ok(id) => active_task_id.set(Some(id)), Ok(id) => active_task_id.set(Some(id)),
Err(e) => { error.set(Some(e.to_string())); busy.set(false); } Err(e) => {
error.set(Some(e.to_string()));
busy.set(false);
}
} }
}); });
} }
@ -1379,13 +1492,18 @@ fn BackupTab() -> Element {
span { class: "text-sm text-paper-secondary", "{p.percent}%" } span { class: "text-sm text-paper-secondary", "{p.percent}%" }
} }
div { class: "w-full bg-paper-entry rounded-full h-2 overflow-hidden", div { class: "w-full bg-paper-entry rounded-full h-2 overflow-hidden",
div { class: "bg-paper-accent h-full transition-all", style: "width: {p.percent}%" } div {
class: "bg-paper-accent h-full transition-all",
style: "width: {p.percent}%",
}
} }
if let Some(detail) = p.detail { if let Some(detail) = p.detail {
p { class: "text-xs text-paper-secondary mt-2", "{detail}" } p { class: "text-xs text-paper-secondary mt-2", "{detail}" }
} }
if let Some(err) = p.error { if let Some(err) = p.error {
p { class: "text-xs text-red-600 dark:text-red-400 mt-2", "错误:{err}" } p { class: "text-xs text-red-600 dark:text-red-400 mt-2",
"错误:{err}"
}
} }
} }
} }
@ -1406,8 +1524,12 @@ fn BackupTab() -> Element {
tr { class: "border-b border-paper-border text-left text-paper-secondary", tr { class: "border-b border-paper-border text-left text-paper-secondary",
th { class: "px-4 py-2 font-medium", "文件名" } th { class: "px-4 py-2 font-medium", "文件名" }
th { class: "px-4 py-2 font-medium", "模式" } th { class: "px-4 py-2 font-medium", "模式" }
th { class: "px-4 py-2 font-medium text-right", "大小" } th { class: "px-4 py-2 font-medium text-right",
th { class: "px-4 py-2 font-medium text-right", "操作" } "大小"
}
th { class: "px-4 py-2 font-medium text-right",
"操作"
}
} }
} }
tbody { tbody {
@ -1572,7 +1694,9 @@ fn BackupRow(props: BackupRowProps) -> Element {
span { class: "font-mono text-xs break-all", "{props.filename}" } span { class: "font-mono text-xs break-all", "{props.filename}" }
"" ""
} }
p { class: "text-xs text-paper-secondary", "仅本系统生成的备份可恢复。" } p { class: "text-xs text-paper-secondary",
"仅本系统生成的备份可恢复。"
}
div { class: "flex justify-end gap-2 pt-1", div { class: "flex justify-end gap-2 pt-1",
button { button {
class: "px-3 py-1.5 text-xs text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer", class: "px-3 py-1.5 text-xs text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer",

View File

@ -491,7 +491,9 @@ fn write_editor(post_id: Option<i32>) -> Element {
div { class: "w-80 flex-shrink-0 min-h-0 overflow-y-auto border-l border-[var(--color-paper-border)] flex flex-col", div { class: "w-80 flex-shrink-0 min-h-0 overflow-y-auto border-l border-[var(--color-paper-border)] flex flex-col",
// Slug 节 // Slug 节
div { class: "p-6 border-b border-[var(--color-paper-border)]", div { class: "p-6 border-b border-[var(--color-paper-border)]",
label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3", "链接" } label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3",
"链接"
}
input { input {
class: "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-2xl px-3 py-2 transition-all", class: "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-2xl px-3 py-2 transition-all",
placeholder: "自动生成", placeholder: "自动生成",
@ -501,7 +503,9 @@ fn write_editor(post_id: Option<i32>) -> Element {
} }
// 标签节 // 标签节
div { class: "p-6 border-b border-[var(--color-paper-border)]", div { class: "p-6 border-b border-[var(--color-paper-border)]",
label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3", "标签" } label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3",
"标签"
}
input { input {
class: "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-2xl px-3 py-2 transition-all", class: "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-2xl px-3 py-2 transition-all",
placeholder: "逗号分隔...", placeholder: "逗号分隔...",
@ -511,7 +515,9 @@ fn write_editor(post_id: Option<i32>) -> Element {
} }
// 摘要节 // 摘要节
div { class: "p-6 border-b border-[var(--color-paper-border)]", div { class: "p-6 border-b border-[var(--color-paper-border)]",
label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3", "摘要" } label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3",
"摘要"
}
textarea { textarea {
class: "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-2xl px-3 py-2 transition-all resize-none leading-relaxed", class: "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-2xl px-3 py-2 transition-all resize-none leading-relaxed",
placeholder: "选填...", placeholder: "选填...",
@ -522,7 +528,9 @@ fn write_editor(post_id: Option<i32>) -> Element {
} }
// 封面图节 // 封面图节
div { class: "p-6", div { class: "p-6",
label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3", "封面图" } label { class: "block text-xs font-semibold uppercase tracking-wide text-[var(--color-paper-tertiary)] mb-3",
"封面图"
}
CoverUploader { cover_image, cover_uploading } CoverUploader { cover_image, cover_uploading }
} }
} // 右栏闭合 } // 右栏闭合
@ -681,7 +689,9 @@ 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-sm font-medium text-[var(--color-paper-secondary)]", "正在上传..." } span { class: "text-sm font-medium text-[var(--color-paper-secondary)]",
"正在上传..."
}
} }
} }
@ -735,7 +745,9 @@ 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-sm font-medium text-white/90 drop-shadow-md", "点击更换封面" } span { class: "text-sm font-medium text-white/90 drop-shadow-md",
"点击更换封面"
}
} }
} }

View File

@ -103,7 +103,9 @@ pub fn Archives() -> Element {
rsx! { rsx! {
div { class: "animate-page-enter", div { class: "animate-page-enter",
header { class: "page-header mb-6", header { class: "page-header mb-6",
h1 { class: "text-4xl font-bold text-paper-primary tracking-tight", "归档" } h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
"归档"
}
} }
ArchivesContent {} ArchivesContent {}
} }

View File

@ -121,9 +121,7 @@ pub fn PostDetail(slug: String) -> Element {
div { class: "mt-12 border-t border-gray-200 dark:border-gray-700 pt-8", div { class: "mt-12 border-t border-gray-200 dark:border-gray-700 pt-8",
SuspenseBoundary { SuspenseBoundary {
fallback: move |_| rsx! { fallback: move |_| rsx! {
DelayedSkeleton { DelayedSkeleton { crate::components::skeletons::comment_skeleton::CommentListSkeleton {} }
crate::components::skeletons::comment_skeleton::CommentListSkeleton {}
}
}, },
crate::components::comments::section::CommentSection { post_id: post.id } crate::components::comments::section::CommentSection { post_id: post.id }
} }

View File

@ -46,7 +46,9 @@ pub fn Search() -> Element {
rsx! { rsx! {
div { class: "animate-page-enter", div { class: "animate-page-enter",
header { class: "page-header mb-6", header { class: "page-header mb-6",
h1 { class: "text-4xl font-bold text-paper-primary tracking-tight", "搜索" } h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
"搜索"
}
} }
div { class: "mb-8", div { class: "mb-8",
div { class: "flex gap-2", div { class: "flex gap-2",

View File

@ -29,7 +29,9 @@ pub fn Tags() -> Element {
rsx! { rsx! {
div { class: "animate-page-enter", div { class: "animate-page-enter",
header { class: "page-header mb-6", header { class: "page-header mb-6",
h1 { class: "text-4xl font-bold text-paper-primary tracking-tight", "标签" } h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
"标签"
}
} }
TagsContent {} TagsContent {}
} }
@ -109,7 +111,9 @@ pub fn TagDetail(tag: String) -> Element {
rsx! { rsx! {
div { class: "animate-page-enter", div { class: "animate-page-enter",
header { class: "page-header mb-6", header { class: "page-header mb-6",
h1 { class: "text-4xl font-bold text-paper-primary tracking-tight", "{tag}" } h1 { class: "text-4xl font-bold text-paper-primary tracking-tight",
"{tag}"
}
} }
TagDetailContent { tag: tag.clone() } TagDetailContent { tag: tag.clone() }
} }

View File

@ -133,25 +133,29 @@ fn ErrorLayout() -> Element {
#[cfg(feature = "server")] #[cfg(feature = "server")]
{ {
if let Some(captured_error) = err.error() { if let Some(captured_error) = err.error() {
let _ = dioxus::fullstack::FullstackContext::commit_error_status(captured_error); let _ = dioxus::fullstack::FullstackContext::commit_error_status(
captured_error,
);
} }
} }
// Parse the captured error to detect 404 vs 500
let mut is_404 = false; let mut is_404 = false;
if let Some(captured_error) = err.error() { if let Some(captured_error) = err.error() {
if let Some(ServerFnError::ServerError { code, message, .. }) = captured_error.downcast_ref::<ServerFnError>() { if let Some(ServerFnError::ServerError { code, message, .. }) = captured_error
.downcast_ref::<ServerFnError>()
{
if *code == 404 || message == "not_found" { if *code == 404 || message == "not_found" {
is_404 = true; is_404 = true;
} }
} else { } else { // Warning / Alert Icon in a soft red container
let err_str = format!("{:?}", captured_error); let err_str = format!("{:?}", captured_error);
if err_str.contains("NotFound") || err_str.contains("404") || err_str.contains("not found") { if err_str.contains("NotFound") || err_str.contains("404")
|| err_str.contains("not found")
{
is_404 = true; is_404 = true;
} }
} }
} }
if is_404 { if is_404 {
rsx! { rsx! {
NotFound { segments: vec![] } NotFound { segments: vec![] }
@ -174,8 +178,18 @@ fn ErrorLayout() -> Element {
stroke_linecap: "round", stroke_linecap: "round",
stroke_linejoin: "round", stroke_linejoin: "round",
circle { cx: "12", cy: "12", r: "10" } circle { cx: "12", cy: "12", r: "10" }
line { x1: "12", y1: "8", x2: "12", y2: "12" } line {
line { x1: "12", y1: "16", x2: "12.01", y2: "16" } x1: "12",
y1: "8",
x2: "12",
y2: "12",
}
line {
x1: "12",
y1: "16",
x2: "12.01",
y2: "16",
}
} }
} }
} }