refactor(admin): 统一按钮令牌,消除样式散落
将 7 个 admin 页面中复制粘贴/各自为政的按钮 class 收敛到 ui.rs 令牌: 主操作色系统一为主题绿(bg-paper-accent): - dashboard/posts「发布文章」、write「发布/更新」+「确认」、runner 语言切换选中态 改用 BTN_PRIMARY / BTN_PRIMARY_SM(原为深色 bg-paper-primary,现统一为绿) - system 4 处刷新/执行/创建备份、trash 保存设置、write 发布按钮改用 LoadingButton 统一 loading 态为 spinner 绝对居中叠加(按钮宽度不变,原 system 用文字切换、 write 用整体变灰,现全部一致) 描边/图标/行内按钮统一: - posts 重建、system 刷新列表 → BTN_OUTLINE - trash 清空回收站 → BTN_DANGER_OUTLINE - trash 步进 ± 按钮(2 处重复字符串)→ BTN_ICON - write 关闭 × 按钮(2 处重复)→ BTN_CLOSE_ICON - system BackupRow 恢复/删除绕过令牌 → BTN_TEXT_AMBER / BTN_TEXT_RED (删除色从 red-600 校正为令牌的 red-500,与其他行内删除一致) 消除约 15 处内联 class 字符串重复,system.rs 4 种主操作变体收敛为 1 种。
This commit is contained in:
parent
545651d304
commit
b207babdb6
@ -14,7 +14,7 @@ use crate::api::posts::{get_post_stats, list_posts};
|
|||||||
use crate::api::posts::{PostListResponse, PostStatsResponse};
|
use crate::api::posts::{PostListResponse, PostStatsResponse};
|
||||||
use crate::components::empty_state::{EmptyState, EmptyStateAction};
|
use crate::components::empty_state::{EmptyState, EmptyStateAction};
|
||||||
use crate::components::skeletons::atoms::SkeletonBox;
|
use crate::components::skeletons::atoms::SkeletonBox;
|
||||||
use crate::components::ui::{ADMIN_CARD_CLASS, ADMIN_TABLE_CLASS, BTN_SECONDARY};
|
use crate::components::ui::{ADMIN_CARD_CLASS, ADMIN_TABLE_CLASS, BTN_PRIMARY, BTN_SECONDARY};
|
||||||
use crate::models::post::{PostListItem, PostStats};
|
use crate::models::post::{PostListItem, PostStats};
|
||||||
use crate::router::Route;
|
use crate::router::Route;
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ pub fn Admin() -> Element {
|
|||||||
"管理文章"
|
"管理文章"
|
||||||
}
|
}
|
||||||
Link {
|
Link {
|
||||||
class: "px-6 py-2 rounded-full text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] hover:opacity-90 shadow-sm transition-all cursor-pointer",
|
class: "{BTN_PRIMARY}",
|
||||||
to: Route::Write {},
|
to: Route::Write {},
|
||||||
"发布文章"
|
"发布文章"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,8 @@ use crate::components::empty_state::{EmptyState, EmptyStateAction};
|
|||||||
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
||||||
use crate::components::skeletons::posts_skeleton::PostsSkeleton;
|
use crate::components::skeletons::posts_skeleton::PostsSkeleton;
|
||||||
use crate::components::ui::{
|
use crate::components::ui::{
|
||||||
Pagination, StatusBadge, Tooltip, ADMIN_ROW_HOVER, ADMIN_TABLE_CLASS, BTN_TEXT_ACCENT,
|
Pagination, StatusBadge, Tooltip, ADMIN_ROW_HOVER, ADMIN_TABLE_CLASS, BTN_OUTLINE,
|
||||||
BTN_TEXT_RED, SPINNER_SVG,
|
BTN_PRIMARY, BTN_TEXT_ACCENT, BTN_TEXT_RED, SPINNER_SVG,
|
||||||
};
|
};
|
||||||
use crate::hooks::query::use_paginated;
|
use crate::hooks::query::use_paginated;
|
||||||
use crate::models::post::PostListItem;
|
use crate::models::post::PostListItem;
|
||||||
@ -88,7 +88,7 @@ pub fn PostsPage(page: i32) -> Element {
|
|||||||
rebuild_result: rebuild_result,
|
rebuild_result: rebuild_result,
|
||||||
}
|
}
|
||||||
Link {
|
Link {
|
||||||
class: "px-6 py-2 rounded-full text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] hover:opacity-90 shadow-sm transition-all cursor-pointer",
|
class: "{BTN_PRIMARY}",
|
||||||
to: Route::Write {},
|
to: Route::Write {},
|
||||||
"发布文章"
|
"发布文章"
|
||||||
}
|
}
|
||||||
@ -244,7 +244,7 @@ fn RebuildCacheBar(
|
|||||||
tip: "重建 content_html 为空的文章渲染缓存".to_string(),
|
tip: "重建 content_html 为空的文章渲染缓存".to_string(),
|
||||||
placement: "bottom",
|
placement: "bottom",
|
||||||
button {
|
button {
|
||||||
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 { "px-4 py-2 rounded-full text-sm font-medium cursor-pointer text-paper-primary border border-paper-border hover:border-paper-accent hover:text-paper-accent transition-all" },
|
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 {
|
||||||
@ -263,7 +263,7 @@ fn RebuildCacheBar(
|
|||||||
tip: "重建所有文章的渲染缓存(含已有内容)".to_string(),
|
tip: "重建所有文章的渲染缓存(含已有内容)".to_string(),
|
||||||
placement: "bottom",
|
placement: "bottom",
|
||||||
button {
|
button {
|
||||||
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 { "px-4 py-2 rounded-full text-sm font-medium cursor-pointer text-paper-primary border border-paper-border hover:border-paper-accent hover:text-paper-accent transition-all" },
|
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 {
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::components::code_runner::CodeRunner;
|
use crate::components::code_runner::CodeRunner;
|
||||||
use crate::components::ui::ADMIN_CARD_CLASS;
|
use crate::components::ui::{ADMIN_CARD_CLASS, BTN_PRIMARY_SM};
|
||||||
use crate::infra::runner_config::ResourceLimits;
|
use crate::infra::runner_config::ResourceLimits;
|
||||||
|
|
||||||
/// 受支持的语言集合(与 LANGUAGES 注册表 / CODE_RUNNER_LANGUAGES 对齐)。
|
/// 受支持的语言集合(与 LANGUAGES 注册表 / CODE_RUNNER_LANGUAGES 对齐)。
|
||||||
@ -72,7 +72,7 @@ pub fn Runner() -> Element {
|
|||||||
button {
|
button {
|
||||||
key: "{l}",
|
key: "{l}",
|
||||||
class: (if lang() == *l {
|
class: (if lang() == *l {
|
||||||
"px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] hover:brightness-110 transition shadow-sm cursor-pointer"
|
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(),
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::components::ui::FilterTabs;
|
use crate::components::ui::{
|
||||||
|
BTN_OUTLINE, BTN_PRIMARY_SM, BTN_TEXT_AMBER, BTN_TEXT_RED, FilterTabs, LoadingButton,
|
||||||
|
};
|
||||||
|
|
||||||
/// 系统管理的 5 个功能 tab。
|
/// 系统管理的 5 个功能 tab。
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
@ -226,9 +228,10 @@ fn DbStatusTab() -> Element {
|
|||||||
div { class: "space-y-6",
|
div { class: "space-y-6",
|
||||||
// 工具栏:刷新按钮 + 自动刷新开关
|
// 工具栏:刷新按钮 + 自动刷新开关
|
||||||
div { class: "flex items-center justify-between",
|
div { class: "flex items-center justify-between",
|
||||||
button {
|
LoadingButton {
|
||||||
class: "px-3 py-1.5 text-sm bg-paper-accent text-paper-theme rounded hover:brightness-110 transition disabled:opacity-50",
|
label: "刷新".to_string(),
|
||||||
disabled: loading(),
|
loading: loading(),
|
||||||
|
variant: "sm",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
loading.set(true);
|
loading.set(true);
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@ -249,7 +252,6 @@ fn DbStatusTab() -> Element {
|
|||||||
loading.set(false);
|
loading.set(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
if loading() { "加载中..." } else { "刷新" }
|
|
||||||
}
|
}
|
||||||
div { class: "flex items-center gap-2",
|
div { class: "flex items-center gap-2",
|
||||||
span { class: "text-sm text-paper-secondary", "自动刷新" }
|
span { class: "text-sm text-paper-secondary", "自动刷新" }
|
||||||
@ -535,9 +537,10 @@ fn ServerStatusTab() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div { class: "space-y-6",
|
div { class: "space-y-6",
|
||||||
div { class: "flex items-center justify-between",
|
div { class: "flex items-center justify-between",
|
||||||
button {
|
LoadingButton {
|
||||||
class: "px-3 py-1.5 text-sm bg-paper-accent text-paper-theme rounded hover:brightness-110 transition disabled:opacity-50",
|
label: "刷新".to_string(),
|
||||||
disabled: loading(),
|
loading: loading(),
|
||||||
|
variant: "sm",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
loading.set(true);
|
loading.set(true);
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@ -558,7 +561,6 @@ fn ServerStatusTab() -> Element {
|
|||||||
loading.set(false);
|
loading.set(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
if loading() { "加载中..." } else { "刷新" }
|
|
||||||
}
|
}
|
||||||
div { class: "flex items-center gap-2",
|
div { class: "flex items-center gap-2",
|
||||||
span { class: "text-sm text-paper-secondary", "自动刷新" }
|
span { class: "text-sm text-paper-secondary", "自动刷新" }
|
||||||
@ -680,7 +682,7 @@ fn SqlConsoleTab() -> Element {
|
|||||||
use crate::api::database::sql_console::{execute_sql, ExecuteSqlOpts};
|
use crate::api::database::sql_console::{execute_sql, ExecuteSqlOpts};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::api::database::schema::get_db_schema;
|
use crate::api::database::schema::get_db_schema;
|
||||||
use crate::components::ui::{ADMIN_TABLE_CLASS, SPINNER_SVG};
|
use crate::components::ui::ADMIN_TABLE_CLASS;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::codemirror_bridge;
|
use crate::codemirror_bridge;
|
||||||
// use_resolved_theme 两种构建都用:resolved() 在 wasm 块内消费,非 wasm 仅引用避免警告。
|
// use_resolved_theme 两种构建都用:resolved() 在 wasm 块内消费,非 wasm 仅引用避免警告。
|
||||||
@ -917,18 +919,10 @@ fn SqlConsoleTab() -> Element {
|
|||||||
|
|
||||||
// 工具条:执行按钮 + 普通/危险选项分层
|
// 工具条:执行按钮 + 普通/危险选项分层
|
||||||
div { class: "flex flex-wrap items-center gap-x-4 gap-y-3",
|
div { class: "flex flex-wrap items-center gap-x-4 gap-y-3",
|
||||||
button {
|
LoadingButton {
|
||||||
class: "inline-flex items-center gap-1.5 px-5 py-2 text-sm font-medium rounded-full text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] hover:brightness-110 active:scale-[0.98] transition disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer",
|
label: "执行".to_string(),
|
||||||
disabled: running(),
|
loading: running(),
|
||||||
onclick: move |_| run_sql(),
|
onclick: move |_| run_sql(),
|
||||||
if running() {
|
|
||||||
span { class: "inline-block w-3.5 h-3.5 text-[var(--color-paper-theme)]",
|
|
||||||
dangerous_inner_html: SPINNER_SVG,
|
|
||||||
}
|
|
||||||
"执行中"
|
|
||||||
} else {
|
|
||||||
"执行"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 普通选项
|
// 普通选项
|
||||||
label { class: "flex items-center gap-1.5 text-sm text-[var(--color-paper-secondary)] cursor-pointer",
|
label { class: "flex items-center gap-1.5 text-sm text-[var(--color-paper-secondary)] cursor-pointer",
|
||||||
@ -1145,7 +1139,7 @@ fn ExportTab() -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
class: "px-4 py-1.5 text-sm bg-paper-accent text-paper-theme rounded hover:brightness-110 transition",
|
class: "{BTN_PRIMARY_SM}",
|
||||||
onclick: move |_| do_export(),
|
onclick: move |_| do_export(),
|
||||||
"导出并下载"
|
"导出并下载"
|
||||||
}
|
}
|
||||||
@ -1343,9 +1337,10 @@ fn BackupTab() -> Element {
|
|||||||
div { class: "space-y-4",
|
div { class: "space-y-4",
|
||||||
// 操作栏
|
// 操作栏
|
||||||
div { class: "flex items-center gap-3",
|
div { class: "flex items-center gap-3",
|
||||||
button {
|
LoadingButton {
|
||||||
class: "px-4 py-1.5 text-sm bg-paper-accent text-paper-theme rounded hover:brightness-110 transition disabled:opacity-50",
|
label: "创建备份".to_string(),
|
||||||
disabled: is_busy,
|
loading: is_busy,
|
||||||
|
variant: "sm",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
@ -1360,10 +1355,9 @@ fn BackupTab() -> Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
if is_busy { "处理中..." } else { "创建备份" }
|
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "px-3 py-1.5 text-sm border border-paper-border text-paper-primary rounded hover:bg-paper-entry transition disabled:opacity-50",
|
class: "{BTN_OUTLINE}",
|
||||||
disabled: loading() || is_busy,
|
disabled: loading() || is_busy,
|
||||||
onclick: move |_| refresh_list(),
|
onclick: move |_| refresh_list(),
|
||||||
"刷新列表"
|
"刷新列表"
|
||||||
@ -1502,13 +1496,13 @@ fn BackupRow(props: BackupRowProps) -> Element {
|
|||||||
"下载"
|
"下载"
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "text-xs text-amber-600 hover:text-amber-800 dark:text-amber-400 mr-3 disabled:opacity-50",
|
class: "{BTN_TEXT_AMBER} mr-3 disabled:opacity-50",
|
||||||
disabled: props.busy,
|
disabled: props.busy,
|
||||||
onclick: move |_| on_restore.call(fname_for_restore.clone()),
|
onclick: move |_| on_restore.call(fname_for_restore.clone()),
|
||||||
"恢复"
|
"恢复"
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "text-xs text-red-600 hover:text-red-800 dark:text-red-400 disabled:opacity-50",
|
class: "{BTN_TEXT_RED} disabled:opacity-50",
|
||||||
disabled: props.busy,
|
disabled: props.busy,
|
||||||
onclick: move |_| on_delete.call(fname_for_delete.clone()),
|
onclick: move |_| on_delete.call(fname_for_delete.clone()),
|
||||||
"删除"
|
"删除"
|
||||||
|
|||||||
@ -21,8 +21,9 @@ use crate::components::empty_state::EmptyState;
|
|||||||
use crate::components::skeletons::atoms::SkeletonBox;
|
use crate::components::skeletons::atoms::SkeletonBox;
|
||||||
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
||||||
use crate::components::ui::{
|
use crate::components::ui::{
|
||||||
Pagination, StatusBadge, ADMIN_CARD_CLASS, ADMIN_ROW_HOVER, ADMIN_TABLE_CLASS, BTN_SOLID_GREEN,
|
Pagination, StatusBadge, ADMIN_CARD_CLASS, ADMIN_ROW_HOVER, ADMIN_TABLE_CLASS,
|
||||||
BTN_SOLID_RED, BTN_TEXT_ACCENT, BTN_TEXT_RED, CHECKBOX_CLASS,
|
BTN_DANGER_OUTLINE, BTN_ICON, BTN_SOLID_GREEN, BTN_SOLID_RED, BTN_TEXT_ACCENT, BTN_TEXT_RED,
|
||||||
|
CHECKBOX_CLASS, LoadingButton,
|
||||||
};
|
};
|
||||||
use crate::hooks::query::use_paginated;
|
use crate::hooks::query::use_paginated;
|
||||||
use crate::models::post::PostListItem;
|
use crate::models::post::PostListItem;
|
||||||
@ -263,7 +264,7 @@ pub fn TrashPage(page: i32) -> Element {
|
|||||||
// 底部:清空回收站 + 分页
|
// 底部:清空回收站 + 分页
|
||||||
div { class: "flex items-center justify-between mt-4",
|
div { class: "flex items-center justify-between mt-4",
|
||||||
button {
|
button {
|
||||||
class: "px-4 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-red-300 dark:border-red-900/50 rounded-full hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors cursor-pointer",
|
class: "{BTN_DANGER_OUTLINE}",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
@ -450,7 +451,7 @@ fn AutoPurgeSettings(settings: Signal<TrashSettings>) -> Element {
|
|||||||
div { class: "flex items-center rounded-lg border border-paper-border bg-paper-entry overflow-hidden",
|
div { class: "flex items-center rounded-lg border border-paper-border bg-paper-entry overflow-hidden",
|
||||||
// 减号
|
// 减号
|
||||||
button {
|
button {
|
||||||
class: "w-9 h-9 flex items-center justify-center text-sm text-paper-secondary hover:text-paper-primary hover:bg-paper-theme cursor-pointer transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-paper-accent/40",
|
class: "{BTN_ICON}",
|
||||||
r#type: "button",
|
r#type: "button",
|
||||||
aria_label: "减少保留天数",
|
aria_label: "减少保留天数",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
@ -475,7 +476,7 @@ fn AutoPurgeSettings(settings: Signal<TrashSettings>) -> Element {
|
|||||||
}
|
}
|
||||||
// 加号
|
// 加号
|
||||||
button {
|
button {
|
||||||
class: "w-9 h-9 flex items-center justify-center text-sm text-paper-secondary hover:text-paper-primary hover:bg-paper-theme cursor-pointer transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-paper-accent/40",
|
class: "{BTN_ICON}",
|
||||||
r#type: "button",
|
r#type: "button",
|
||||||
aria_label: "增加保留天数",
|
aria_label: "增加保留天数",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
@ -517,10 +518,12 @@ fn AutoPurgeSettings(settings: Signal<TrashSettings>) -> Element {
|
|||||||
"·"
|
"·"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 保存按钮:启用主题色,禁用/保存中态灰化
|
// 保存按钮:主题绿主操作,saving 态显示 spinner,just_saved/无改动禁用
|
||||||
button {
|
LoadingButton {
|
||||||
class: if saving_settings() { "inline-flex items-center gap-1.5 px-4 py-1.5 text-sm font-medium cursor-not-allowed text-paper-secondary bg-paper-tertiary rounded-full" } else if just_saved() { "inline-flex items-center gap-1.5 px-4 py-1.5 text-sm font-medium cursor-not-allowed text-paper-secondary bg-paper-tertiary rounded-full" } else { "inline-flex items-center gap-1.5 px-4 py-1.5 text-sm font-medium text-paper-theme bg-paper-accent rounded-full hover:brightness-110 active:scale-[0.98] transition-all cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-paper-accent/40" },
|
label: "保存设置".to_string(),
|
||||||
disabled: saving_settings() || just_saved() || !dirty(),
|
loading: saving_settings(),
|
||||||
|
disabled: just_saved() || !dirty(),
|
||||||
|
variant: "sm",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
let days: i32 = settings_draft_days().parse().unwrap_or(30);
|
let days: i32 = settings_draft_days().parse().unwrap_or(30);
|
||||||
let enabled = settings_draft_enabled();
|
let enabled = settings_draft_enabled();
|
||||||
@ -533,11 +536,6 @@ fn AutoPurgeSettings(settings: Signal<TrashSettings>) -> Element {
|
|||||||
saving_settings.set(false);
|
saving_settings.set(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
if saving_settings() {
|
|
||||||
"保存中…"
|
|
||||||
} else {
|
|
||||||
"保存设置"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ use crate::api::posts::{
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::tiptap_bridge::{consume_upload_event, upload_image_file, EditorHandle};
|
use crate::tiptap_bridge::{consume_upload_event, upload_image_file, EditorHandle};
|
||||||
// 共享上传状态类型:两端都编译(rsx 在 server SSR 时也要渲染这些结构)。
|
// 共享上传状态类型:两端都编译(rsx 在 server SSR 时也要渲染这些结构)。
|
||||||
|
use crate::components::ui::{BTN_CLOSE_ICON, BTN_PRIMARY_SM, LoadingButton};
|
||||||
use crate::components::write_skeleton::WriteSkeleton;
|
use crate::components::write_skeleton::WriteSkeleton;
|
||||||
use crate::models::post::Post;
|
use crate::models::post::Post;
|
||||||
use crate::router::Route;
|
use crate::router::Route;
|
||||||
@ -241,7 +242,7 @@ fn write_editor(post_id: Option<i32>) -> Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 提交表单:校验标题与内容,读取 Tiptap 编辑器 Markdown,调用 create_post 或 update_post。
|
// 提交表单:校验标题与内容,读取 Tiptap 编辑器 Markdown,调用 create_post 或 update_post。
|
||||||
let on_submit = move |_| {
|
let mut on_submit = move |_| {
|
||||||
// 上传未完成/失败拦截:有占位符时阻止保存
|
// 上传未完成/失败拦截:有占位符时阻止保存
|
||||||
let in_flight = uploads_in_flight.read();
|
let in_flight = uploads_in_flight.read();
|
||||||
if in_flight.uploading > 0 || in_flight.error > 0 {
|
if in_flight.uploading > 0 || in_flight.error > 0 {
|
||||||
@ -504,7 +505,7 @@ fn write_editor(post_id: Option<i32>) -> Element {
|
|||||||
class: "flex-shrink-0 flex items-center justify-between gap-3 px-4 py-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-xl text-sm border border-red-100 dark:border-red-900/30 mb-2",
|
class: "flex-shrink-0 flex items-center justify-between gap-3 px-4 py-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-xl text-sm border border-red-100 dark:border-red-900/30 mb-2",
|
||||||
span { "图片上传失败: {err.file_name} — {err.message}" }
|
span { "图片上传失败: {err.file_name} — {err.message}" }
|
||||||
button {
|
button {
|
||||||
class: "shrink-0 text-red-400 hover:text-red-600 cursor-pointer text-lg leading-none",
|
class: "{BTN_CLOSE_ICON}",
|
||||||
aria_label: "关闭提示",
|
aria_label: "关闭提示",
|
||||||
onclick: {
|
onclick: {
|
||||||
// 捕获 owned id,避免借用临时值
|
// 捕获 owned id,避免借用临时值
|
||||||
@ -564,11 +565,10 @@ fn write_editor(post_id: Option<i32>) -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
div { class: "w-px h-5 bg-[var(--color-paper-border)]" }
|
div { class: "w-px h-5 bg-[var(--color-paper-border)]" }
|
||||||
button {
|
LoadingButton {
|
||||||
class: if saving() { "px-6 py-2 rounded-full text-sm font-medium bg-[var(--color-paper-tertiary)] text-[var(--color-paper-secondary)] cursor-not-allowed shadow-sm" } else { "px-6 py-2 rounded-full text-sm font-medium bg-[var(--color-paper-primary)] text-[var(--color-paper-theme)] hover:opacity-90 shadow-sm transition-all cursor-pointer" },
|
label: if is_edit { "更新文章".to_string() } else { "发布文章".to_string() },
|
||||||
disabled: saving(),
|
loading: saving(),
|
||||||
onclick: on_submit,
|
onclick: move |_| on_submit(()),
|
||||||
if saving() { "保存中..." } else if is_edit { "更新文章" } else { "发布文章" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -814,7 +814,7 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "shrink-0 px-4 py-1.5 rounded-full text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-primary)] hover:opacity-90 shadow-sm transition-all cursor-pointer",
|
class: "shrink-0 {BTN_PRIMARY_SM}",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
let v = cover_url_input().trim().to_string();
|
let v = cover_url_input().trim().to_string();
|
||||||
if !v.is_empty() {
|
if !v.is_empty() {
|
||||||
@ -841,7 +841,7 @@ fn CoverUploader(cover_image: Signal<String>, cover_uploading: Signal<bool>) ->
|
|||||||
div { class: "flex items-center justify-between gap-3 px-4 py-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-xl text-sm border border-red-100 dark:border-red-900/30 mt-2",
|
div { class: "flex items-center justify-between gap-3 px-4 py-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-xl text-sm border border-red-100 dark:border-red-900/30 mt-2",
|
||||||
span { "封面图: {err}" }
|
span { "封面图: {err}" }
|
||||||
button {
|
button {
|
||||||
class: "shrink-0 text-red-400 hover:text-red-600 cursor-pointer text-lg leading-none",
|
class: "{BTN_CLOSE_ICON}",
|
||||||
aria_label: "关闭提示",
|
aria_label: "关闭提示",
|
||||||
onclick: move |_| cover_error.set(None),
|
onclick: move |_| cover_error.set(None),
|
||||||
"×"
|
"×"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user