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 loading = paginated.loading;
|
||||||
let _error = paginated.error;
|
let _error = paginated.error;
|
||||||
|
|
||||||
// 删除中 ID、重建缓存状态与结果仍由本组件持有(业务逻辑不归 hook 管)。
|
// 删除中 / 重建中文章 ID 集合:均由本组件持有(业务逻辑不归 hook 管)。
|
||||||
let mut deleting = use_signal(|| None::<i32>);
|
// 改为非乐观删除后行会保留至请求完成,可并发点多个删除,故用 HashSet
|
||||||
|
// 与 rebuilding 同形,按行通过 contains 判断 loading 态。
|
||||||
|
let mut deleting = use_signal(|| std::collections::HashSet::<i32>::new());
|
||||||
// 重建中文章 ID 集合:支持多篇文章并发重建(行不会随点击消失,单值会被后点
|
// 重建中文章 ID 集合:支持多篇文章并发重建(行不会随点击消失,单值会被后点
|
||||||
// 的覆盖先点的,故用 HashSet),按行通过 contains 判断 loading 态。
|
// 的覆盖先点的,故用 HashSet),按行通过 contains 判断 loading 态。
|
||||||
let mut rebuilding = use_signal(|| std::collections::HashSet::<i32>::new());
|
let mut rebuilding = use_signal(|| std::collections::HashSet::<i32>::new());
|
||||||
@ -130,16 +132,18 @@ pub fn PostsPage(page: i32) -> Element {
|
|||||||
PostRow {
|
PostRow {
|
||||||
key: "{post.id}",
|
key: "{post.id}",
|
||||||
post: post.clone(),
|
post: post.clone(),
|
||||||
deleting: deleting() == Some(post.id),
|
deleting: deleting().contains(&post.id),
|
||||||
rebuilding: rebuilding().contains(&post.id),
|
rebuilding: rebuilding().contains(&post.id),
|
||||||
// 删除文章:先乐观更新本地列表,再调用 server function,失败时弹出浏览器提示。
|
// 删除文章:非乐观——先标记 deleting 让按钮显示 loading,
|
||||||
|
// 服务端成功后才移除行并减总数,失败则保留行并弹出浏览器提示。
|
||||||
on_delete: move |id| {
|
on_delete: move |id| {
|
||||||
deleting.set(Some(id));
|
deleting.write().insert(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));
|
|
||||||
spawn(async move {
|
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, .. }) => {
|
Ok(CreatePostResponse { success: false, message: _message, .. }) => {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
web_sys::window().map(|w| w.alert_with_message(&_message).ok());
|
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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
web_sys::window().map(|w| w.alert_with_message("删除失败").ok());
|
web_sys::window().map(|w| w.alert_with_message("删除失败").ok());
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
deleting.set(None);
|
deleting.write().remove(&id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 重建单篇文章内容:调用 server function 重新渲染 content_html。
|
// 重建单篇文章内容:调用 server function 重新渲染 content_html。
|
||||||
@ -334,14 +337,23 @@ fn PostRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
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,
|
disabled: deleting,
|
||||||
onclick: move |_| on_delete.call(post.id),
|
onclick: move |_| on_delete.call(post.id),
|
||||||
if deleting {
|
span {
|
||||||
"删除中..."
|
class: if deleting { "opacity-40" } else { "" },
|
||||||
} 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