132 lines
4.2 KiB
Rust
132 lines
4.2 KiB
Rust
//! 评论列表组件
|
||
//!
|
||
//! 将已审核评论与待审核评论合并成一棵树并按时间排序渲染。
|
||
|
||
use dioxus::prelude::*;
|
||
|
||
use crate::components::comments::item::CommentItem;
|
||
use crate::components::comments::pending_item::PendingCommentItem;
|
||
use crate::hooks::comment_storage::PendingComment;
|
||
use crate::models::comment::PublicComment;
|
||
|
||
/// 合并后的评论节点,可能是已审核或待审核评论。
|
||
#[derive(Clone)]
|
||
enum MergedComment {
|
||
Approved(PublicComment),
|
||
Pending(PendingComment),
|
||
}
|
||
|
||
/// 合并两类评论并构建成树形结构。
|
||
///
|
||
/// 处理逻辑:
|
||
/// - 将已审核与待审核评论统一为 `MergedComment`
|
||
/// - 若某条评论的 parent_id 不存在于当前集合中,则视为顶层评论
|
||
/// - 同一父节点下的子评论按时间排序
|
||
/// - 使用 DFS 前序遍历生成最终展示顺序
|
||
fn merge_and_treeify(
|
||
approved: Vec<PublicComment>,
|
||
pending: Vec<PendingComment>,
|
||
) -> Vec<MergedComment> {
|
||
use std::collections::{HashMap, HashSet};
|
||
|
||
let all: Vec<MergedComment> = approved
|
||
.into_iter()
|
||
.map(MergedComment::Approved)
|
||
.chain(pending.into_iter().map(MergedComment::Pending))
|
||
.collect();
|
||
|
||
let all_ids: HashSet<i64> = all
|
||
.iter()
|
||
.map(|c| match c {
|
||
MergedComment::Approved(c) => c.id,
|
||
MergedComment::Pending(c) => c.id,
|
||
})
|
||
.collect();
|
||
|
||
// 按 parent_id 分组,处理指向不存在父节点的 parent_id
|
||
let mut children_map: HashMap<Option<i64>, Vec<MergedComment>> = HashMap::new();
|
||
for comment in all {
|
||
let parent_id = match &comment {
|
||
MergedComment::Approved(c) => c.parent_id,
|
||
MergedComment::Pending(c) => c.parent_id,
|
||
};
|
||
let effective_parent = match parent_id {
|
||
Some(pid) if !all_ids.contains(&pid) => None,
|
||
_ => parent_id,
|
||
};
|
||
children_map
|
||
.entry(effective_parent)
|
||
.or_default()
|
||
.push(comment);
|
||
}
|
||
|
||
// 每个父节点下的子评论按创建时间排序
|
||
for children in children_map.values_mut() {
|
||
children.sort_by(|a, b| {
|
||
let time_a = match a {
|
||
MergedComment::Approved(c) => c.created_at_iso.as_str(),
|
||
MergedComment::Pending(c) => c.created_at.as_str(),
|
||
};
|
||
let time_b = match b {
|
||
MergedComment::Approved(c) => c.created_at_iso.as_str(),
|
||
MergedComment::Pending(c) => c.created_at.as_str(),
|
||
};
|
||
time_a.cmp(time_b)
|
||
});
|
||
}
|
||
|
||
// 深度优先遍历生成树形顺序
|
||
fn dfs(
|
||
parent_id: Option<i64>,
|
||
children_map: &HashMap<Option<i64>, Vec<MergedComment>>,
|
||
result: &mut Vec<MergedComment>,
|
||
) {
|
||
if let Some(children) = children_map.get(&parent_id) {
|
||
for child in children {
|
||
result.push(child.clone());
|
||
let child_id = match child {
|
||
MergedComment::Approved(c) => Some(c.id),
|
||
MergedComment::Pending(c) => Some(c.id),
|
||
};
|
||
dfs(child_id, children_map, result);
|
||
}
|
||
}
|
||
}
|
||
|
||
let mut result = Vec::new();
|
||
dfs(None, &children_map, &mut result);
|
||
result
|
||
}
|
||
|
||
/// 评论列表组件。
|
||
///
|
||
/// Props:
|
||
/// - `comments`:已审核评论列表
|
||
/// - `pending`:待审核评论列表
|
||
/// - `post_id`:所属文章 ID
|
||
///
|
||
/// 根据两类评论构建合并树,依次渲染为 `CommentItem` 或 `PendingCommentItem`。
|
||
#[component]
|
||
pub fn CommentList(
|
||
comments: Vec<PublicComment>,
|
||
pending: Vec<PendingComment>,
|
||
post_id: i32,
|
||
) -> Element {
|
||
let merged = merge_and_treeify(comments, pending);
|
||
|
||
rsx! {
|
||
div { class: "space-y-0 divide-y divide-gray-100 dark:divide-[#2a2a2a]",
|
||
for item in merged {
|
||
match item {
|
||
MergedComment::Approved(comment) => rsx! {
|
||
CommentItem { key: "{comment.id}", comment, post_id }
|
||
},
|
||
MergedComment::Pending(comment) => rsx! {
|
||
PendingCommentItem { key: "{comment.id}", comment, post_id }
|
||
},
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|