style(admin/posts): 删除按钮 loading 用 spinner 覆盖文字
- 删除改为非乐观:点击后保留行并显示 loading,服务端成功才移除行、 失败保留行并弹提示(顺带修掉原先「删除失败行却没了」的隐患) - deleting 信号由 Option<i32> 改为 HashSet<i32>,支持并发删除 - 删除按钮 loading 样式与重建一致:文案恒为「删除」,spinner 居中覆盖、文字 opacity-40 衬底
This commit is contained in:
parent
1530d3a2f4
commit
b6fe0ae082
@ -57,8 +57,10 @@ pub fn PostsPage(page: i32) -> Element {
|
||||
let loading = paginated.loading;
|
||||
let _error = paginated.error;
|
||||
|
||||
// 删除中 ID、重建缓存状态与结果仍由本组件持有(业务逻辑不归 hook 管)。
|
||||
let mut deleting = use_signal(|| None::<i32>);
|
||||
// 删除中 / 重建中文章 ID 集合:均由本组件持有(业务逻辑不归 hook 管)。
|
||||
// 改为非乐观删除后行会保留至请求完成,可并发点多个删除,故用 HashSet
|
||||
// 与 rebuilding 同形,按行通过 contains 判断 loading 态。
|
||||
let mut deleting = use_signal(|| std::collections::HashSet::<i32>::new());
|
||||
// 重建中文章 ID 集合:支持多篇文章并发重建(行不会随点击消失,单值会被后点
|
||||
// 的覆盖先点的,故用 HashSet),按行通过 contains 判断 loading 态。
|
||||
let mut rebuilding = use_signal(|| std::collections::HashSet::<i32>::new());
|
||||
@ -130,16 +132,18 @@ pub fn PostsPage(page: i32) -> Element {
|
||||
PostRow {
|
||||
key: "{post.id}",
|
||||
post: post.clone(),
|
||||
deleting: deleting() == Some(post.id),
|
||||
deleting: deleting().contains(&post.id),
|
||||
rebuilding: rebuilding().contains(&post.id),
|
||||
// 删除文章:先乐观更新本地列表,再调用 server function,失败时弹出浏览器提示。
|
||||
// 删除文章:非乐观——先标记 deleting 让按钮显示 loading,
|
||||
// 服务端成功后才移除行并减总数,失败则保留行并弹出浏览器提示。
|
||||
on_delete: move |id| {
|
||||
deleting.set(Some(id));
|
||||
let id_for_api = id;
|
||||
posts.with_mut(|list| list.retain(|p| p.id != id));
|
||||
total.with_mut(|t| *t = t.saturating_sub(1));
|
||||
deleting.write().insert(id);
|
||||
spawn(async move {
|
||||
match delete_post(id_for_api).await {
|
||||
match delete_post(id).await {
|
||||
Ok(CreatePostResponse { success: true, .. }) => {
|
||||
posts.with_mut(|list| list.retain(|p| p.id != id));
|
||||
total.with_mut(|t| *t = t.saturating_sub(1));
|
||||
}
|
||||
Ok(CreatePostResponse { success: false, message: _message, .. }) => {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_sys::window().map(|w| w.alert_with_message(&_message).ok());
|
||||
@ -148,9 +152,8 @@ pub fn PostsPage(page: i32) -> Element {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_sys::window().map(|w| w.alert_with_message("删除失败").ok());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
deleting.set(None);
|
||||
deleting.write().remove(&id);
|
||||
});
|
||||
},
|
||||
// 重建单篇文章内容:调用 server function 重新渲染 content_html。
|
||||
@ -334,14 +337,23 @@ fn PostRow(
|
||||
}
|
||||
}
|
||||
button {
|
||||
class: if deleting { "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),
|
||||
if deleting {
|
||||
"删除中..."
|
||||
} else {
|
||||
span {
|
||||
class: if deleting { "opacity-40" } else { "" },
|
||||
"删除"
|
||||
}
|
||||
if deleting {
|
||||
span {
|
||||
class: "absolute inset-0 flex items-center justify-center",
|
||||
dangerous_inner_html: SPINNER_SVG,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user