fix(admin/posts): 重建支持并发 loading,spinner 覆盖文字
- rebuilding 信号由 Option<i32> 改为 HashSet<i32>,连续点击多篇 文章的重建按钮可同时处于 loading 态(单值会被后点的覆盖) - loading 样式调整:按钮文案恒为「重建」,spinner 绝对定位覆盖在 文字上方居中,文字 opacity 调至 40% 衬底,不再切换文案
This commit is contained in:
parent
a5c00caa80
commit
db87d3c886
@ -59,8 +59,9 @@ pub fn PostsPage(page: i32) -> Element {
|
|||||||
|
|
||||||
// 删除中 ID、重建缓存状态与结果仍由本组件持有(业务逻辑不归 hook 管)。
|
// 删除中 ID、重建缓存状态与结果仍由本组件持有(业务逻辑不归 hook 管)。
|
||||||
let mut deleting = use_signal(|| None::<i32>);
|
let mut deleting = use_signal(|| None::<i32>);
|
||||||
// 单篇重建中 ID:与 deleting 同形,按行禁用按钮并切换文案。
|
// 重建中文章 ID 集合:支持多篇文章并发重建(行不会随点击消失,单值会被后点
|
||||||
let mut rebuilding = use_signal(|| None::<i32>);
|
// 的覆盖先点的,故用 HashSet),按行通过 contains 判断 loading 态。
|
||||||
|
let mut rebuilding = use_signal(|| std::collections::HashSet::<i32>::new());
|
||||||
// 重建缓存的状态由本组件持有并下发给 RebuildCacheBar:结果消息也在本组件
|
// 重建缓存的状态由本组件持有并下发给 RebuildCacheBar:结果消息也在本组件
|
||||||
// 渲染(header 与表格之间的独立行),既不撑高 header 触发 items-center 重排,
|
// 渲染(header 与表格之间的独立行),既不撑高 header 触发 items-center 重排,
|
||||||
// 也不脱离文档流溢进表格。rebuilding 仅按钮态用,不在此渲染。
|
// 也不脱离文档流溢进表格。rebuilding 仅按钮态用,不在此渲染。
|
||||||
@ -130,7 +131,7 @@ pub fn PostsPage(page: i32) -> Element {
|
|||||||
key: "{post.id}",
|
key: "{post.id}",
|
||||||
post: post.clone(),
|
post: post.clone(),
|
||||||
deleting: deleting() == Some(post.id),
|
deleting: deleting() == Some(post.id),
|
||||||
rebuilding: rebuilding() == Some(post.id),
|
rebuilding: rebuilding().contains(&post.id),
|
||||||
// 删除文章:先乐观更新本地列表,再调用 server function,失败时弹出浏览器提示。
|
// 删除文章:先乐观更新本地列表,再调用 server function,失败时弹出浏览器提示。
|
||||||
on_delete: move |id| {
|
on_delete: move |id| {
|
||||||
deleting.set(Some(id));
|
deleting.set(Some(id));
|
||||||
@ -154,11 +155,12 @@ pub fn PostsPage(page: i32) -> Element {
|
|||||||
},
|
},
|
||||||
// 重建单篇文章内容:调用 server function 重新渲染 content_html。
|
// 重建单篇文章内容:调用 server function 重新渲染 content_html。
|
||||||
// 静默执行,仅按行切换 rebuilding 按钮态,不弹窗。
|
// 静默执行,仅按行切换 rebuilding 按钮态,不弹窗。
|
||||||
|
// 用 HashSet 记录在途 ID,支持多篇并发重建。
|
||||||
on_rebuild: move |id| {
|
on_rebuild: move |id| {
|
||||||
rebuilding.set(Some(id));
|
rebuilding.write().insert(id);
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let _ = rebuild_post_content_html(id).await;
|
let _ = rebuild_post_content_html(id).await;
|
||||||
rebuilding.set(None);
|
rebuilding.write().remove(&id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -304,20 +306,22 @@ fn PostRow(
|
|||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: if rebuilding {
|
class: if rebuilding {
|
||||||
"inline-flex items-center gap-1 text-xs text-paper-secondary cursor-not-allowed"
|
"relative inline-flex items-center text-xs text-paper-accent cursor-not-allowed"
|
||||||
} else {
|
} else {
|
||||||
BTN_TEXT_ACCENT
|
BTN_TEXT_ACCENT
|
||||||
},
|
},
|
||||||
disabled: rebuilding,
|
disabled: rebuilding,
|
||||||
onclick: move |_| on_rebuild.call(post.id),
|
onclick: move |_| on_rebuild.call(post.id),
|
||||||
if rebuilding {
|
span {
|
||||||
{rsx! {
|
class: if rebuilding { "opacity-40" } else { "" },
|
||||||
span { dangerous_inner_html: SPINNER_SVG }
|
|
||||||
"重建中"
|
|
||||||
}}
|
|
||||||
} else {
|
|
||||||
"重建"
|
"重建"
|
||||||
}
|
}
|
||||||
|
if rebuilding {
|
||||||
|
span {
|
||||||
|
class: "absolute inset-0 flex items-center justify-center",
|
||||||
|
dangerous_inner_html: SPINNER_SVG,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: if deleting { "text-xs text-paper-secondary cursor-not-allowed" } else { BTN_TEXT_RED },
|
class: if deleting { "text-xs text-paper-secondary cursor-not-allowed" } else { BTN_TEXT_RED },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user