diff --git a/src/components/admin_layout.rs b/src/components/admin_layout.rs index 69367a9..9ebe846 100644 --- a/src/components/admin_layout.rs +++ b/src/components/admin_layout.rs @@ -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" }; rsx! { - Link { - key: "{label}", - class: "{base_class} {text_class}", - to: dest, - "{label}" - } + Link { 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", { match route { - Route::Write {} => rsx! { WriteSkeleton {} }, - _ => rsx! { AdminDashboardSkeleton {} }, + Route::Write {} => rsx! { + WriteSkeleton {} + }, + _ => rsx! { + AdminDashboardSkeleton {} + }, } } } diff --git a/src/components/code_runner/runner.rs b/src/components/code_runner/runner.rs index 0d108e8..534d0ee 100644 --- a/src/components/code_runner/runner.rs +++ b/src/components/code_runner/runner.rs @@ -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 items-center gap-3", 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 { class: format!( "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" } else { "bg-transparent text-[var(--color-paper-tertiary)] border-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)]" - } + }, ), onclick: toggle_vim, "Vim" @@ -506,7 +508,8 @@ pub fn CodeRunner( disabled: running(), onclick: run_code, 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, } "{stage()}" @@ -523,14 +526,28 @@ pub fn CodeRunner( // editor_ready 由挂载 effect 置 true 后,此处 if 分支消失,骨架屏从 DOM 移除。 // 用绝对定位覆盖在(始终存在的)容器上方,不影响 CodeMirror 的 getElementById 挂载。 if !editor_ready() { - 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: 90%" } - 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%" } + div { + class: "h-3 rounded bg-[var(--color-paper-tertiary)]/25 dark:bg-gray-600/50 animate-pulse", + style: "width: 90%", + } + 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 时,显示骨架屏占位。 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: "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: 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%", + } } } } diff --git a/src/components/comments/section.rs b/src/components/comments/section.rs index 00f4d37..0251b23 100644 --- a/src/components/comments/section.rs +++ b/src/components/comments/section.rs @@ -119,10 +119,9 @@ pub fn CommentSection(post_id: i32) -> Element { let has_any = approved_count > 0 || pending_count > 0; if !has_any { rsx! { - p { class: "text-paper-tertiary text-center py-8", - "暂无评论,成为第一个评论的人吧!" - } + p { class: "text-paper-tertiary text-center py-8", "暂无评论,成为第一个评论的人吧!" } } + } } else { rsx! { CommentList { diff --git a/src/components/post/post_content.rs b/src/components/post/post_content.rs index 293db4c..675c098 100644 --- a/src/components/post/post_content.rs +++ b/src/components/post/post_content.rs @@ -170,23 +170,25 @@ pub fn PostContent(content_html: String) -> Element { rsx! { div { class: "post-content md-content", for (i, fragment) in fragments.iter().enumerate() { - {match fragment { - ContentFragment::Html(html) => rsx! { - div { key: "html-{i}", dangerous_inner_html: "{html}" } - }, - ContentFragment::Runnable { lang, source, overrides } => rsx! { - CodeRunner { - key: "runner-{i}", - source: source.clone(), - language: lang.clone(), - overrides: overrides.clone(), - // i 是片段序列中的确定性索引(来自纯函数 split_content_fragments - // 对同一 content_html 的解析),SSR 与 hydration 一致,用作容器 - // id 后缀保证 hydration 时 CodeMirror 能找到 SSR 渲染的容器。 - instance_id: i, - } - }, - }} + { + match fragment { + ContentFragment::Html(html) => rsx! { + div { key: "html-{i}", dangerous_inner_html: "{html}" } + }, + ContentFragment::Runnable { lang, source, overrides } => rsx! { // i 是片段序列中的确定性索引(来自纯函数 split_content_fragments + CodeRunner { + key: "runner-{i}", + source: source.clone(), + language: lang.clone(), + overrides: overrides.clone(), + // i 是片段序列中的确定性索引(来自纯函数 split_content_fragments + // 对同一 content_html 的解析),SSR 与 hydration 一致,用作容器 + // id 后缀保证 hydration 时 CodeMirror 能找到 SSR 渲染的容器。 + instance_id: i, + } + }, + } + } } } } diff --git a/src/components/post_card.rs b/src/components/post_card.rs index 65cef0d..13940b5 100644 --- a/src/components/post_card.rs +++ b/src/components/post_card.rs @@ -59,23 +59,23 @@ pub fn PostCard(post: PostListItem) -> Element { } div { class: "mt-4 flex flex-wrap items-center gap-3 text-sm font-medium text-[var(--color-paper-tertiary)]", span { class: "tracking-wide", "{date_str}" } - if !post.tags.is_empty() { - span { "·" } - for tag in post.tags.clone().into_iter() { - span { key: "{tag}", - Link { - class: "relative z-10 px-3 py-1 rounded-full border border-[var(--color-paper-border)] hover:bg-[var(--color-paper-accent)] hover:border-[var(--color-paper-accent)] hover:text-white transition-all duration-200", - to: Route::TagDetail { - tag: tag.clone(), - }, - onclick: move |evt: dioxus::events::MouseEvent| evt.stop_propagation(), - "{tag}" + if !post.tags.is_empty() { + span { "·" } + for tag in post.tags.clone().into_iter() { + span { key: "{tag}", + Link { + class: "relative z-10 px-3 py-1 rounded-full border border-[var(--color-paper-border)] hover:bg-[var(--color-paper-accent)] hover:border-[var(--color-paper-accent)] hover:text-white transition-all duration-200", + to: Route::TagDetail { + tag: tag.clone(), + }, + onclick: move |evt: dioxus::events::MouseEvent| evt.stop_propagation(), + "{tag}" + } } } } } } - } // 覆盖层链接:铺满卡片,承担整卡跳转。 // z-[2] 高于封面完整图 (.blur-img-full z-index:1, input.css:611), // 使鼠标 hover 封面时命中本 而非 :光标变 pointer 且点击可跳转; diff --git a/src/components/sql_result_table.rs b/src/components/sql_result_table.rs index 4f3ffeb..9793f5a 100644 --- a/src/components/sql_result_table.rs +++ b/src/components/sql_result_table.rs @@ -47,8 +47,7 @@ fn render_cell(value: &serde_json::Value) -> Element { } }, Value::String(s) => rsx! { - span { - class: "block font-mono text-xs text-[var(--color-paper-secondary)] truncate {TEXT_CELL_MAX_WIDTH_CLASS}", + span { class: "block font-mono text-xs text-[var(--color-paper-secondary)] truncate {TEXT_CELL_MAX_WIDTH_CLASS}", "{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]", "{col}" } - span { class: "font-mono text-xs text-[var(--color-paper-secondary)]", - "{display}" - } + span { class: "font-mono text-xs text-[var(--color-paper-secondary)]", "{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", th { class: "w-8 px-2 py-2", "" } for col in props.result.columns.iter() { - th { - class: "px-4 py-2 text-left font-medium whitespace-nowrap text-[var(--color-paper-secondary)]", + th { class: "px-4 py-2 text-left font-medium whitespace-nowrap text-[var(--color-paper-secondary)]", "{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", - if expanded_row() == Some(row_idx) { "▾" } else { "▸" } + if expanded_row() == Some(row_idx) { + "▾" + } else { + "▸" + } } for cell in row.iter() { - td { - class: "px-4 py-2 align-top", - {render_cell(cell)} - } + td { class: "px-4 py-2 align-top", {render_cell(cell)} } } } // 展开详情行(跨列) if expanded_row() == Some(row_idx) { tr { - td { colspan: "{expand_colspan}", + td { + colspan: "{expand_colspan}", class: "px-4 py-3 bg-[var(--color-paper-theme)]", - div { - class: "{EXPAND_MAX_HEIGHT_CLASS} overflow-y-auto space-y-0.5 whitespace-pre-wrap break-all", + div { 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() { {render_expanded_value(col, row.get(ci).unwrap_or(&serde_json::Value::Null))} } diff --git a/src/components/ui.rs b/src/components/ui.rs index cb10ed1..59927a7 100644 --- a/src/components/ui.rs +++ b/src/components/ui.rs @@ -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" }, if has_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} } } else { - Link { class: "{link_class}", to: prev_route, - {prev_inner} - } + Link { class: "{link_class}", to: prev_route, {prev_inner} } } } else if is_admin { span { class: "{disabled_class}", @@ -193,11 +193,15 @@ pub fn Pagination( if has_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} } } else { - Link { class: "{link_class} {link_extra_next}", to: next_route, + Link { + class: "{link_class} {link_extra_next}", + to: next_route, {next_inner} } } @@ -386,11 +390,7 @@ pub fn Popover( onclick: move |_| on_close.call(()), } // 面板:fixed 定位逃出 overflow-hidden 容器。 - div { - class: "{POPOVER_PANEL_CLASS}", - style: "{style}", - {children} - } + div { class: "{POPOVER_PANEL_CLASS}", style: "{style}", {children} } } } diff --git a/src/pages/about.rs b/src/pages/about.rs index 4018ea8..41f5bab 100644 --- a/src/pages/about.rs +++ b/src/pages/about.rs @@ -15,7 +15,9 @@ pub fn About() -> Element { rsx! { div { class: "animate-page-enter", 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", p { "世界……遗忘我……" } diff --git a/src/pages/admin/comments.rs b/src/pages/admin/comments.rs index dd55b6c..98e3d3c 100644 --- a/src/pages/admin/comments.rs +++ b/src/pages/admin/comments.rs @@ -129,8 +129,12 @@ pub fn AdminCommentsPage(page: i32) -> Element { div { class: "w-full max-w-7xl mx-auto space-y-6", div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-6 border-b border-[var(--color-paper-border)] mb-6", div { - h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "评论管理" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "所有文章评论" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", + "评论管理" + } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", + "所有文章评论" + } } } diff --git a/src/pages/admin/dashboard.rs b/src/pages/admin/dashboard.rs index 7f435ae..69d2efe 100644 --- a/src/pages/admin/dashboard.rs +++ b/src/pages/admin/dashboard.rs @@ -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 { - h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "仪表盘" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据概览与近期活动" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", + "仪表盘" + } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", + "数据概览与近期活动" + } } div { class: "flex items-center gap-3", - Link { - class: "{BTN_SECONDARY}", - to: Route::Posts {}, - "管理文章" - } - Link { - class: "{BTN_PRIMARY}", - to: Route::Write {}, - "发布文章" - } + Link { class: "{BTN_SECONDARY}", to: Route::Posts {}, "管理文章" } + Link { class: "{BTN_PRIMARY}", to: Route::Write {}, "发布文章" } } } @@ -77,9 +73,21 @@ pub fn Admin() -> Element { match stats() { Some(s) => { rsx! { - StatCard { value: s.total.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() } + StatCard { + value: s.total.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 => { @@ -103,13 +111,19 @@ pub fn Admin() -> Element { let (color_class, text_class) = if count > 0 { ("text-amber-500", "text-amber-500") } else { - ("text-[var(--color-paper-secondary)]", "text-[var(--color-paper-primary)]") - }; + ( + "text-[var(--color-paper-secondary)]", + "text-[var(--color-paper-primary)]", + ) + } + } rsx! { div { class: "text-sm font-medium {color_class}", "待审评论" } div { class: "flex items-baseline justify-between mt-4", 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: "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() { // 空库 / 无文章:展示空状态占位(与 posts.rs 列表页一致)。 @@ -180,10 +196,16 @@ fn StatCard(value: String, label: String, trend: String) -> Element { 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: "flex justify-between items-start", - div { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "{label}" } - div { class: "text-xs px-2 py-0.5 rounded-full border border-[var(--color-paper-border)] text-[var(--color-paper-tertiary)]", "{trend}" } + div { class: "text-sm font-medium text-[var(--color-paper-secondary)]", + "{label}" + } + div { class: "text-xs px-2 py-0.5 rounded-full border border-[var(--color-paper-border)] text-[var(--color-paper-tertiary)]", + "{trend}" + } + } + 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! { div { class: "flex flex-col sm:flex-row sm:justify-between sm:items-center px-8 py-5 hover:bg-[var(--color-paper-accent-soft)] transition-colors cursor-pointer group", div { class: "flex items-center gap-6", - span { class: "text-xs font-mono text-[var(--color-paper-tertiary)] w-12 hidden sm:block", "#{post.id:04}" } - span { class: "text-base font-semibold text-[var(--color-paper-primary)] group-hover:text-[var(--color-paper-accent)] transition-colors", "{post.title}" } - span { class: "text-xs px-3 py-1 font-medium rounded-full {status_class}", "{status_label}" } + span { class: "text-xs font-mono text-[var(--color-paper-tertiary)] w-12 hidden sm:block", + "#{post.id:04}" + } + span { class: "text-base font-semibold text-[var(--color-paper-primary)] group-hover:text-[var(--color-paper-accent)] transition-colors", + "{post.title}" + } + span { class: "text-xs px-3 py-1 font-medium rounded-full {status_class}", + "{status_label}" + } + } + span { class: "text-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}" } } } } diff --git a/src/pages/admin/posts.rs b/src/pages/admin/posts.rs index 358bbba..166f3a5 100644 --- a/src/pages/admin/posts.rs +++ b/src/pages/admin/posts.rs @@ -106,11 +106,7 @@ pub fn Posts() -> Element { if active_tab() == PostsTab::All { div { class: "flex items-center gap-3", RebuildCacheBar {} - Link { - class: "{BTN_PRIMARY}", - to: Route::Write {}, - "发布文章" - } + Link { class: "{BTN_PRIMARY}", to: Route::Write {}, "发布文章" } } } } @@ -125,8 +121,12 @@ pub fn Posts() -> Element { // key 化条件渲染:切 tab 完全卸载/重挂,隔离各自 use_paginated 状态。 div { key: "{active_tab().as_str()}", match active_tab() { - PostsTab::All => rsx! { AllPostsList {} }, - PostsTab::Trash => rsx! { PostsTrashPanel {} }, + PostsTab::All => rsx! { + AllPostsList {} + }, + PostsTab::Trash => rsx! { + PostsTrashPanel {} + }, } } } @@ -184,13 +184,9 @@ fn AllPostsList() -> Element { thead { 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 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-44 text-right", - "操作" - } + th { class: "px-4 py-3 font-medium w-44 text-right", "操作" } } } 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 }, disabled: rebuilding(), onclick: move |_| do_rebuild(false), - span { - class: if rebuilding() { "opacity-40" } else { "" }, - "重建内容" - } + span { class: if rebuilding() { "opacity-40" } else { "" }, "重建内容" } if rebuilding() { span { 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 }, disabled: rebuilding(), onclick: move |_| do_rebuild(true), - span { - class: if rebuilding() { "opacity-40" } else { "" }, - "重建全部" - } + span { class: if rebuilding() { "opacity-40" } else { "" }, "重建全部" } if rebuilding() { span { class: "absolute inset-0 flex items-center justify-center", @@ -344,9 +334,7 @@ fn RebuildCacheBar() -> Element { } // 重建结果消息:独立成行,右对齐,与按钮行同属本组件。 if let Some(msg) = rebuild_result() { - div { class: "text-xs text-paper-secondary whitespace-pre-line", - "{msg}" - } + div { class: "text-xs text-paper-secondary whitespace-pre-line", "{msg}" } } } } @@ -388,20 +376,12 @@ fn PostRow( to: Route::WriteEdit { id: post.id }, "编辑" } - Tooltip { - tip: "重新渲染这篇文章的 HTML".to_string(), + Tooltip { tip: "重新渲染这篇文章的 HTML".to_string(), button { - class: if rebuilding { - "relative inline-flex items-center text-xs text-paper-accent cursor-not-allowed" - } else { - BTN_TEXT_ACCENT - }, + class: if rebuilding { "relative inline-flex items-center text-xs text-paper-accent cursor-not-allowed" } else { BTN_TEXT_ACCENT }, disabled: rebuilding, onclick: move |_| on_rebuild.call(post.id), - span { - class: if rebuilding { "opacity-40" } else { "" }, - "重建" - } + span { class: if rebuilding { "opacity-40" } else { "" }, "重建" } if rebuilding { span { class: "absolute inset-0 flex items-center justify-center", @@ -411,17 +391,10 @@ fn PostRow( } } button { - class: if deleting { - "relative inline-flex items-center text-xs text-paper-secondary cursor-not-allowed" - } else { - BTN_TEXT_RED - }, + class: if deleting { "relative inline-flex items-center text-xs text-paper-secondary cursor-not-allowed" } else { BTN_TEXT_RED }, disabled: deleting, onclick: move |_| on_delete.call(post.id), - span { - class: if deleting { "opacity-40" } else { "" }, - "删除" - } + span { class: if deleting { "opacity-40" } else { "" }, "删除" } if deleting { span { 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", button { id: "posts-tab-{id_prefix}-all", - 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" - }, + 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" }, onclick: move |_| on_change.call(PostsTab::All), "全部文章" } button { id: "posts-tab-{id_prefix}-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" - }, + 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" }, onclick: move |_| on_change.call(PostsTab::Trash), "回收站" // 数量角标:有数据才显示。0 显示中性灰,>0 用主题强调色提醒。 if let Some(count) = trash_count() { - 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" - }, + 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" }, "{count}" } } diff --git a/src/pages/admin/runner.rs b/src/pages/admin/runner.rs index 8b19c1c..d3e349e 100644 --- a/src/pages/admin/runner.rs +++ b/src/pages/admin/runner.rs @@ -57,7 +57,9 @@ pub fn Runner() -> Element { // 页头:与 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 { - 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", "在此快速试运行代码,验证文章中可运行代码块的预期输出。资源钳制与读者侧一致,速率限制对 admin 放行。" } @@ -68,7 +70,9 @@ pub fn Runner() -> Element { div { class: "{ADMIN_CARD_CLASS} p-8 flex flex-col gap-6", // 语言切换 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", for l in SUPPORTED_LANGS { button { @@ -77,7 +81,8 @@ pub fn Runner() -> Element { BTN_PRIMARY_SM } 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" - }).to_string(), + }) + .to_string(), onclick: { let ll = (*l).to_string(); move |_| { @@ -119,7 +124,7 @@ pub fn Runner() -> Element { CodeRunner { source: source(), language: lang(), - overrides: overrides, + overrides, instance_id: 0, } } diff --git a/src/pages/admin/system.rs b/src/pages/admin/system.rs index 267d787..5208c4b 100644 --- a/src/pages/admin/system.rs +++ b/src/pages/admin/system.rs @@ -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 { - h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "系统面板" } - p { class: "text-base text-[var(--color-paper-secondary)] mt-2", "数据库与服务器诊断" } + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", + "系统面板" + } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", + "数据库与服务器诊断" + } } } @@ -92,11 +96,21 @@ pub fn System() -> Element { // 避免 hook slot 复用导致 DelayedSkeleton 的 visible 信号残留为 true。 div { key: "{active_tab().as_str()}", match active_tab() { - SystemTab::DbStatus => rsx! { DbStatusTab {} }, - SystemTab::ServerStatus => rsx! { ServerStatusTab {} }, - SystemTab::SqlConsole => rsx! { SqlConsoleTab {} }, - SystemTab::Export => rsx! { ExportTab {} }, - SystemTab::Backup => rsx! { BackupTab {} }, + SystemTab::DbStatus => rsx! { + DbStatusTab {} + }, + SystemTab::ServerStatus => rsx! { + 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()}", onchange: move |e| { let v = e.value(); - refresh_interval.set(match v.as_str() { - "1" => Some(1), - "2" => Some(2), - "5" => Some(5), - "30" => Some(30), - _ => None, - }); + refresh_interval + .set( + match v.as_str() { + "1" => Some(1), + "2" => Some(2), + "5" => Some(5), + "30" => Some(30), + _ => None, + }, + ); }, option { value: "", "手动" } 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: "{ADMIN_CARD_CLASS} p-4", 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", 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", 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", p { class: "text-xs text-paper-secondary", "迁移版本" } @@ -306,24 +329,48 @@ fn DbStatusTab() -> Element { thead { 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 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 { for t in s.tables.iter() { 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 text-right text-paper-secondary", - if t.row_count_estimated { "~{t.row_count}" } else { "{t.row_count}" } + td { class: "px-4 py-2 font-mono text-paper-primary", + "{t.name}" + } + 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", 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 { for i in s.top_indexes.iter() { 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-secondary", "{i.table_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-primary", + "{i.name}" + } + 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", "用户" } 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", "查询" } } } tbody { for c in s.active_connections.iter() { 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", "{c.user}" } + td { class: "px-4 py-2 text-paper-secondary", + "{c.pid}" + } + td { class: "px-4 py-2 text-paper-secondary", + "{c.user}" + } td { class: "px-4 py-2 text-paper-secondary", {c.state.clone().unwrap_or_else(|| "—".to_string())} } 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", {c.query.clone().unwrap_or_else(|| "—".to_string())} @@ -553,7 +618,7 @@ fn ServerStatusTab() -> Element { rsx! { div { class: "space-y-6", - div { class: "flex items-center justify-between", + div { class: "flex items-center justify-between", LoadingButton { label: "刷新".to_string(), loading: loading(), @@ -586,13 +651,16 @@ fn ServerStatusTab() -> Element { value: "{refresh_ms().map(|s| s.to_string()).unwrap_or_default()}", onchange: move |e| { let v = e.value(); - refresh_ms.set(match v.as_str() { - "500" => Some(500), - "1000" => Some(1000), - "2000" => Some(2000), - "5000" => Some(5000), - _ => None, - }); + refresh_ms + .set( + match v.as_str() { + "500" => Some(500), + "1000" => Some(1000), + "2000" => Some(2000), + "5000" => Some(5000), + _ => None, + }, + ); }, option { value: "", "手动" } 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: "{ADMIN_CARD_CLASS} p-4", 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", 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: "text-xs text-paper-secondary", "空闲 {s.pool_available} · 等待 {s.pool_waiting}" } + p { class: "mt-1 text-lg font-semibold text-paper-primary", + "{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", 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", 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: "{ADMIN_CARD_CLASS} p-4", 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", 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", 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", 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 { 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 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 { 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", - td { class: "px-4 py-2 text-paper-primary", "{name}" } - td { class: "px-4 py-2 text-right text-paper-secondary", "{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}" } + td { class: "px-4 py-2 text-paper-primary", + "{name}" + } + td { class: "px-4 py-2 text-right text-paper-secondary", + "{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 items-center gap-2", 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) 填满整个高度, // 避免编辑器塌缩到内容高度、底部透出容器背景造成上下色差。 @@ -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", "执行计划" } - 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}", 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 { // 查询输入 div { - label { class: "block text-sm text-paper-secondary mb-1", "SELECT 查询(只读)" } + label { class: "block text-sm text-paper-secondary mb-1", + "SELECT 查询(只读)" + } textarea { class: "w-full px-3 py-2 text-sm border border-paper-border rounded bg-paper-theme text-paper-primary font-mono", rows: "4", @@ -1182,11 +1296,7 @@ fn ExportTab() -> Element { } } - button { - class: "{BTN_PRIMARY_SM}", - onclick: move |_| do_export(), - "导出并下载" - } + button { class: "{BTN_PRIMARY_SM}", onclick: move |_| do_export(), "导出并下载" } } p { class: "text-xs text-paper-secondary", "导出走流式响应,大表不会占满内存。SQL 格式仅含 INSERT 语句(不含 DDL/schema)。" @@ -1357,7 +1467,10 @@ fn BackupTab() -> Element { spawn(async move { match create_backup().await { 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}%" } } 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 { p { class: "text-xs text-paper-secondary mt-2", "{detail}" } } 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", 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 { @@ -1572,7 +1694,9 @@ fn BackupRow(props: BackupRowProps) -> Element { 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", button { class: "px-3 py-1.5 text-xs text-paper-secondary hover:text-paper-primary transition-colors cursor-pointer", diff --git a/src/pages/admin/write.rs b/src/pages/admin/write.rs index e6a7747..f05437a 100644 --- a/src/pages/admin/write.rs +++ b/src/pages/admin/write.rs @@ -491,7 +491,9 @@ fn write_editor(post_id: Option) -> Element { div { class: "w-80 flex-shrink-0 min-h-0 overflow-y-auto border-l border-[var(--color-paper-border)] flex flex-col", // Slug 节 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 { 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: "自动生成", @@ -501,7 +503,9 @@ fn write_editor(post_id: Option) -> Element { } // 标签节 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 { 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: "逗号分隔...", @@ -511,7 +515,9 @@ fn write_editor(post_id: Option) -> Element { } // 摘要节 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 { 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: "选填...", @@ -522,7 +528,9 @@ fn write_editor(post_id: Option) -> Element { } // 封面图节 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 } } } // 右栏闭合 @@ -681,7 +689,9 @@ fn CoverUploader(cover_image: Signal, cover_uploading: Signal) -> // —— 上传中:骨架占位 + 文案 —— 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", - 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, cover_uploading: Signal) -> } // 底部渐变遮罩 + "更换封面"提示(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", - span { class: "text-sm font-medium text-white/90 drop-shadow-md", "点击更换封面" } + span { class: "text-sm font-medium text-white/90 drop-shadow-md", + "点击更换封面" + } } } diff --git a/src/pages/archives.rs b/src/pages/archives.rs index 9839fe0..5247e25 100644 --- a/src/pages/archives.rs +++ b/src/pages/archives.rs @@ -103,7 +103,9 @@ pub fn Archives() -> Element { rsx! { div { class: "animate-page-enter", 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 {} } diff --git a/src/pages/post_detail.rs b/src/pages/post_detail.rs index 9ee226a..b64f95e 100644 --- a/src/pages/post_detail.rs +++ b/src/pages/post_detail.rs @@ -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", SuspenseBoundary { fallback: move |_| rsx! { - DelayedSkeleton { - crate::components::skeletons::comment_skeleton::CommentListSkeleton {} - } + DelayedSkeleton { crate::components::skeletons::comment_skeleton::CommentListSkeleton {} } }, crate::components::comments::section::CommentSection { post_id: post.id } } diff --git a/src/pages/search.rs b/src/pages/search.rs index e70e02f..77bf9d4 100644 --- a/src/pages/search.rs +++ b/src/pages/search.rs @@ -46,7 +46,9 @@ pub fn Search() -> Element { rsx! { div { class: "animate-page-enter", 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: "flex gap-2", diff --git a/src/pages/tags.rs b/src/pages/tags.rs index b42511d..96bbe5e 100644 --- a/src/pages/tags.rs +++ b/src/pages/tags.rs @@ -29,7 +29,9 @@ pub fn Tags() -> Element { rsx! { div { class: "animate-page-enter", 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 {} } @@ -109,7 +111,9 @@ pub fn TagDetail(tag: String) -> Element { rsx! { div { class: "animate-page-enter", 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() } } diff --git a/src/router.rs b/src/router.rs index b369d39..e732194 100644 --- a/src/router.rs +++ b/src/router.rs @@ -133,25 +133,29 @@ fn ErrorLayout() -> Element { #[cfg(feature = "server")] { 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; if let Some(captured_error) = err.error() { - if let Some(ServerFnError::ServerError { code, message, .. }) = captured_error.downcast_ref::() { + if let Some(ServerFnError::ServerError { code, message, .. }) = captured_error + .downcast_ref::() + + { if *code == 404 || message == "not_found" { is_404 = true; } - } else { + } else { // Warning / Alert Icon in a soft red container 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; } } } - if is_404 { rsx! { NotFound { segments: vec![] } @@ -174,8 +178,18 @@ fn ErrorLayout() -> Element { stroke_linecap: "round", stroke_linejoin: "round", circle { cx: "12", cy: "12", r: "10" } - line { x1: "12", y1: "8", x2: "12", y2: "12" } - line { x1: "12", y1: "16", x2: "12.01", y2: "16" } + line { + x1: "12", + y1: "8", + x2: "12", + y2: "12", + } + line { + x1: "12", + y1: "16", + x2: "12.01", + y2: "16", + } } } }