fix(admin): 评论管理页改为 CSR 避免按钮点击闪烁
This commit is contained in:
parent
569eec5bf8
commit
ea440cd61c
@ -4,13 +4,14 @@ use dioxus::prelude::*;
|
||||
use dioxus::router::components::Link;
|
||||
|
||||
use crate::api::comments::{
|
||||
approve_comment, batch_update_comment_status, get_all_comments, spam_comment,
|
||||
AllCommentsResponse,
|
||||
approve_comment, batch_update_comment_status, spam_comment,
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::api::comments::{get_all_comments, AllCommentsResponse};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::api::comments::trash_comment;
|
||||
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
||||
use crate::models::comment::CommentStatus;
|
||||
use crate::models::comment::{AdminComment, CommentStatus};
|
||||
use crate::router::Route;
|
||||
|
||||
const COMMENTS_PER_PAGE: i32 = 20;
|
||||
@ -43,12 +44,60 @@ pub fn AdminCommentsPage(page: i32) -> Element {
|
||||
String::new()
|
||||
});
|
||||
let mut selected_ids: Signal<HashSet<i64>> = use_signal(HashSet::new);
|
||||
let mut comments: Signal<Vec<AdminComment>> = use_signal(Vec::new);
|
||||
let mut total: Signal<i64> = use_signal(|| 0);
|
||||
#[allow(unused_mut)]
|
||||
let mut loading: Signal<bool> = use_signal(|| false);
|
||||
#[allow(unused_mut)]
|
||||
let mut error: Signal<Option<String>> = use_signal(|| None);
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let filter_status = move || {
|
||||
let f = active_filter();
|
||||
if f.is_empty() { None } else { Some(f) }
|
||||
};
|
||||
let mut comments_res =
|
||||
use_server_future(move || get_all_comments(current_page, filter_status()))?;
|
||||
|
||||
// 客户端(CSR)加载数据
|
||||
use_effect(move || {
|
||||
let _ = active_filter();
|
||||
let _ = current_page;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let page = current_page;
|
||||
let status = filter_status();
|
||||
spawn(async move {
|
||||
loading.set(true);
|
||||
error.set(None);
|
||||
match get_all_comments(page, status).await {
|
||||
Ok(AllCommentsResponse {
|
||||
comments: list,
|
||||
total: t,
|
||||
}) => {
|
||||
comments.set(list);
|
||||
total.set(t);
|
||||
}
|
||||
Err(e) => error.set(Some(e.to_string())),
|
||||
}
|
||||
loading.set(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut set_comment_status = move |id: i64, status: CommentStatus| {
|
||||
comments.with_mut(|list| {
|
||||
if let Some(c) = list.iter_mut().find(|c| c.id == id) {
|
||||
c.status = status;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
#[allow(unused_mut, unused_variables)]
|
||||
let mut remove_comment = move |id: i64| {
|
||||
comments.with_mut(|list| list.retain(|c| c.id != id));
|
||||
total.with_mut(|t| *t = t.saturating_sub(1));
|
||||
};
|
||||
|
||||
rsx! {
|
||||
div { class: "space-y-6",
|
||||
@ -80,11 +129,12 @@ pub fn AdminCommentsPage(page: i32) -> Element {
|
||||
class: "px-3 py-1.5 text-xs font-medium bg-green-600 text-white rounded hover:bg-green-700 transition-colors",
|
||||
onclick: move |_| {
|
||||
let ids: Vec<i64> = selected_ids().iter().copied().collect();
|
||||
let ids_for_api = ids.clone();
|
||||
spawn(async move {
|
||||
let _ = batch_update_comment_status(ids, "approved".to_string()).await;
|
||||
let _ = batch_update_comment_status(ids_for_api, "approved".to_string()).await;
|
||||
});
|
||||
for id in &ids { set_comment_status(*id, CommentStatus::Approved); }
|
||||
selected_ids.set(HashSet::new());
|
||||
comments_res.restart();
|
||||
},
|
||||
"批量通过"
|
||||
}
|
||||
@ -92,11 +142,12 @@ pub fn AdminCommentsPage(page: i32) -> Element {
|
||||
class: "px-3 py-1.5 text-xs font-medium bg-amber-600 text-white rounded hover:bg-amber-700 transition-colors",
|
||||
onclick: move |_| {
|
||||
let ids: Vec<i64> = selected_ids().iter().copied().collect();
|
||||
let ids_for_api = ids.clone();
|
||||
spawn(async move {
|
||||
let _ = batch_update_comment_status(ids, "spam".to_string()).await;
|
||||
let _ = batch_update_comment_status(ids_for_api, "spam".to_string()).await;
|
||||
});
|
||||
for id in &ids { set_comment_status(*id, CommentStatus::Spam); }
|
||||
selected_ids.set(HashSet::new());
|
||||
comments_res.restart();
|
||||
},
|
||||
"批量垃圾"
|
||||
}
|
||||
@ -110,11 +161,12 @@ pub fn AdminCommentsPage(page: i32) -> Element {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let ids: Vec<i64> = selected_ids().iter().copied().collect();
|
||||
let ids_for_api = ids.clone();
|
||||
spawn(async move {
|
||||
let _ = batch_update_comment_status(ids, "trash".to_string()).await;
|
||||
let _ = batch_update_comment_status(ids_for_api, "trash".to_string()).await;
|
||||
});
|
||||
for id in &ids { remove_comment(*id); }
|
||||
selected_ids.set(HashSet::new());
|
||||
comments_res.restart();
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -125,139 +177,125 @@ pub fn AdminCommentsPage(page: i32) -> Element {
|
||||
}
|
||||
|
||||
{
|
||||
let data = comments_res.read().as_ref().map(|r| match r {
|
||||
Ok(AllCommentsResponse { comments, total }) => Ok((comments.clone(), *total)),
|
||||
Err(e) => Err(e.to_string()),
|
||||
});
|
||||
match data {
|
||||
Some(Ok((comments, total))) => {
|
||||
if comments.is_empty() {
|
||||
rsx! {
|
||||
div { class: "text-center py-20 text-gray-500 dark:text-[#9b9c9d]",
|
||||
"暂无评论"
|
||||
if error().is_some() {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败"
|
||||
}
|
||||
}
|
||||
} else if loading() && comments().is_empty() {
|
||||
rsx! {
|
||||
DelayedSkeleton {
|
||||
div { class: "bg-white dark:bg-[#2e2e33] rounded-xl border border-gray-200 dark:border-[#333] p-6 space-y-4",
|
||||
for _ in 0..5 {
|
||||
div { class: "flex items-center gap-4",
|
||||
div { class: "h-4 w-4 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
div { class: "h-8 w-8 bg-gray-200 dark:bg-[#2a2a2a] rounded-full" }
|
||||
div { class: "h-4 w-32 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
div { class: "h-4 flex-1 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let all_selected = comments.iter().all(|c| selected_ids().contains(&c.id));
|
||||
let all_ids: Vec<i64> = comments.iter().map(|c| c.id).collect();
|
||||
rsx! {
|
||||
div { class: "bg-white dark:bg-[#2e2e33] rounded-xl border border-gray-200 dark:border-[#333] overflow-hidden",
|
||||
div { class: "overflow-x-auto",
|
||||
table { class: "w-full text-sm",
|
||||
thead {
|
||||
tr { class: "border-b border-gray-200 dark:border-[#333] text-left text-gray-500 dark:text-[#9b9c9d]",
|
||||
th { class: "px-4 py-3 font-medium w-10",
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
class: "rounded border-gray-300 dark:border-[#555]",
|
||||
checked: all_selected,
|
||||
onchange: {
|
||||
move |_| {
|
||||
let mut s = selected_ids();
|
||||
if all_selected {
|
||||
for id in &all_ids { s.remove(id); }
|
||||
} else {
|
||||
for id in &all_ids { s.insert(*id); }
|
||||
}
|
||||
selected_ids.set(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if comments().is_empty() {
|
||||
rsx! {
|
||||
div { class: "text-center py-20 text-gray-500 dark:text-[#9b9c9d]",
|
||||
"暂无评论"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let list = comments();
|
||||
let all_selected = list.iter().all(|c| selected_ids().contains(&c.id));
|
||||
let all_ids: Vec<i64> = list.iter().map(|c| c.id).collect();
|
||||
rsx! {
|
||||
div { class: "bg-white dark:bg-[#2e2e33] rounded-xl border border-gray-200 dark:border-[#333] overflow-hidden",
|
||||
div { class: "overflow-x-auto",
|
||||
table { class: "w-full text-sm",
|
||||
thead {
|
||||
tr { class: "border-b border-gray-200 dark:border-[#333] text-left text-gray-500 dark:text-[#9b9c9d]",
|
||||
th { class: "px-4 py-3 font-medium w-10",
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
class: "rounded border-gray-300 dark:border-[#555]",
|
||||
checked: all_selected,
|
||||
onchange: {
|
||||
move |_| {
|
||||
let mut s = selected_ids();
|
||||
if all_selected {
|
||||
for id in &all_ids { s.remove(id); }
|
||||
} else {
|
||||
for id in &all_ids { s.insert(*id); }
|
||||
}
|
||||
selected_ids.set(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
th { class: "px-4 py-3 font-medium", "作者" }
|
||||
th { class: "px-4 py-3 font-medium", "内容" }
|
||||
th { class: "px-4 py-3 font-medium", "文章" }
|
||||
th { class: "px-4 py-3 font-medium text-center", "状态" }
|
||||
th { class: "px-4 py-3 font-medium w-28", "日期" }
|
||||
th { class: "px-4 py-3 font-medium w-32 text-right", "操作" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for comment in list.iter() {
|
||||
CommentRow {
|
||||
key: "{comment.id}",
|
||||
comment: comment.clone(),
|
||||
selected: selected_ids().contains(&comment.id),
|
||||
on_select: {
|
||||
let id = comment.id;
|
||||
move |checked: bool| {
|
||||
let mut s = selected_ids();
|
||||
if checked { s.insert(id); } else { s.remove(&id); }
|
||||
selected_ids.set(s);
|
||||
}
|
||||
},
|
||||
on_approve: {
|
||||
let id = comment.id;
|
||||
move |_| {
|
||||
spawn(async move {
|
||||
let _ = approve_comment(id).await;
|
||||
});
|
||||
set_comment_status(id, CommentStatus::Approved);
|
||||
}
|
||||
},
|
||||
on_spam: {
|
||||
let id = comment.id;
|
||||
move |_| {
|
||||
spawn(async move {
|
||||
let _ = spam_comment(id).await;
|
||||
});
|
||||
set_comment_status(id, CommentStatus::Spam);
|
||||
}
|
||||
},
|
||||
on_trash: {
|
||||
let _id = comment.id;
|
||||
move |_| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
if web_sys::window()
|
||||
.and_then(|w| w.confirm_with_message("确定要删除这条评论吗?").ok())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
spawn(async move {
|
||||
let _ = trash_comment(_id).await;
|
||||
});
|
||||
remove_comment(_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
th { class: "px-4 py-3 font-medium", "作者" }
|
||||
th { class: "px-4 py-3 font-medium", "内容" }
|
||||
th { class: "px-4 py-3 font-medium", "文章" }
|
||||
th { class: "px-4 py-3 font-medium text-center", "状态" }
|
||||
th { class: "px-4 py-3 font-medium w-28", "日期" }
|
||||
th { class: "px-4 py-3 font-medium w-32 text-right", "操作" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for comment in comments.iter() {
|
||||
CommentRow {
|
||||
key: "{comment.id}",
|
||||
comment: comment.clone(),
|
||||
selected: selected_ids().contains(&comment.id),
|
||||
on_select: {
|
||||
let id = comment.id;
|
||||
move |checked: bool| {
|
||||
let mut s = selected_ids();
|
||||
if checked { s.insert(id); } else { s.remove(&id); }
|
||||
selected_ids.set(s);
|
||||
}
|
||||
},
|
||||
on_approve: {
|
||||
let mut comments_res = comments_res;
|
||||
let id = comment.id;
|
||||
move |_| {
|
||||
spawn(async move {
|
||||
let _ = approve_comment(id).await;
|
||||
comments_res.restart();
|
||||
});
|
||||
}
|
||||
},
|
||||
on_spam: {
|
||||
let mut comments_res = comments_res;
|
||||
let id = comment.id;
|
||||
move |_| {
|
||||
spawn(async move {
|
||||
let _ = spam_comment(id).await;
|
||||
comments_res.restart();
|
||||
});
|
||||
}
|
||||
},
|
||||
on_trash: {
|
||||
#[allow(unused_variables)]
|
||||
let mut comments_res = comments_res;
|
||||
let _id = comment.id;
|
||||
move |_| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
if web_sys::window()
|
||||
.and_then(|w| w.confirm_with_message("确定要删除这条评论吗?").ok())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let id = _id;
|
||||
spawn(async move {
|
||||
let _ = trash_comment(id).await;
|
||||
comments_res.restart();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CommentsPagination { current_page, total }
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(_e)) => {
|
||||
rsx! {
|
||||
div { class: "text-center text-red-500 dark:text-red-400 py-20",
|
||||
"加载失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rsx! {
|
||||
DelayedSkeleton {
|
||||
div { class: "bg-white dark:bg-[#2e2e33] rounded-xl border border-gray-200 dark:border-[#333] p-6 space-y-4",
|
||||
for _ in 0..5 {
|
||||
div { class: "flex items-center gap-4",
|
||||
div { class: "h-4 w-4 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
div { class: "h-8 w-8 bg-gray-200 dark:bg-[#2a2a2a] rounded-full" }
|
||||
div { class: "h-4 w-32 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
div { class: "h-4 flex-1 bg-gray-200 dark:bg-[#2a2a2a] rounded" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CommentsPagination { current_page, total: total() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,7 +305,7 @@ pub fn AdminCommentsPage(page: i32) -> Element {
|
||||
|
||||
#[component]
|
||||
fn CommentRow(
|
||||
comment: crate::models::comment::AdminComment,
|
||||
comment: AdminComment,
|
||||
selected: bool,
|
||||
on_select: EventHandler<bool>,
|
||||
on_approve: EventHandler,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user