import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle, } from "react"; import { View, Text, Image, Input } from "@tarojs/components"; import Taro from "@tarojs/taro"; import dayjs from "dayjs"; import CommentServices from "@/services/commentServices"; import type { BaseComment, Comment, ReplyComment, } from "@/services/commentServices"; import { useUserInfo } from "@/store/userStore"; import sendImg from "@/static/detail/icon-sendup.svg"; import addComment from "@/static/detail/icon-write.svg"; import emptyComment from "@/static/emptyStatus/comment-empty.png"; import CommonPopup from "../CommonPopup"; import styles from "./index.module.scss"; // const PAGESIZE = 4; const PAGESIZE = 1000; function toast(msg) { Taro.showToast({ title: msg, icon: "none" }); } interface CommentInputProps { onConfirm?: ( value: { content: string } & Partial ) => void; } // 2️⃣ 定义通过 ref 暴露出去的方法类型 interface CommentInputRef { show: (params?: CommentInputReplyParamsType) => void; } interface CommentInputReplyParamsType { parent_id: number; reply_to_user_id: number; nickname: string; } const CommentInput = forwardRef(function ( props, ref ) { const { onConfirm } = props; const [visible, setVisible] = useState(false); const [value, setValue] = useState(""); const [params, setParams] = useState< CommentInputReplyParamsType | undefined >(); const inputDomRef = useRef(null); useImperativeHandle(ref, () => ({ show: (_params: CommentInputReplyParamsType | undefined) => { setVisible(true); setTimeout(() => { inputDomRef.current && inputDomRef.current?.focus(); }, 100); setParams(_params); }, })); function handleSend() { if (!value) { toast("评论内容不得为空"); return; } onConfirm?.({ content: value, ...params }); onClose(); } function onClose() { setVisible(false); setValue(""); inputDomRef.current && inputDomRef.current?.blur(); } return ( setValue(e.detail.value)} placeholder={ params?.reply_to_user_id ? `回复 @${params.nickname}` : "写评论" } focus maxlength={100} /> ); }); function isReplyComment(item: BaseComment): item is ReplyComment { return "reply_to_user" in item; } function getRelativeDay(time) { const theTime = dayjs(time); const isThisYear = dayjs().isSame(theTime, "year"); const diffDay = dayjs().startOf("day").diff(theTime, "day"); return diffDay <= 3 ? diffDay >= 1 ? `${diffDay}天前` : theTime.format("HH:mm:ss") : theTime.format(isThisYear ? "MM-DD HH:mm:ss" : "YYYY-MM-DD HH:mm:ss"); } function CommentItem(props: { level: number; publisher_id: number; comment: Comment | ReplyComment; loadMore: (c: Comment) => void; handleReply: (options: CommentInputReplyParamsType) => void; handleDelete: (options: { parent_id: number | null; id: number }) => void; }) { const { level, publisher_id, comment, loadMore: handleLoadMore, handleReply, handleDelete, } = props; const currentUserInfo = useUserInfo(); const isGamePublisher = publisher_id === comment.user.id; const isCommentPublisher = currentUserInfo.id === comment.user.id; return ( {comment.user.nickname} {isGamePublisher && ( 组织者 )} {isReplyComment(comment) && comment.reply_to_user ? `@${comment.reply_to_user.nickname} ` : ""} {comment.content} {getRelativeDay(comment.create_time)} 上海 handleReply({ parent_id: comment.parent_id || comment.id, reply_to_user_id: comment.user.id, nickname: comment.user.nickname, }) } > 回复 {isGamePublisher || isCommentPublisher} handleDelete({ parent_id: comment.parent_id, id: comment.id, }) } > 删除 {!isReplyComment(comment) && comment.replies.map((item: ReplyComment) => ( ))} {!isReplyComment(comment) && comment.replies.length !== comment.reply_count && ( handleLoadMore(comment)} > 展开更多评论 )} ); } export default forwardRef(function Comments( props: { game_id: number; publisher_id: number }, ref ) { const { game_id, publisher_id } = props; const [comments, setComments] = useState([]); const inputRef = useRef(null); useEffect(() => { getComments(1); }, [game_id]); useImperativeHandle(ref, () => ({ addComment: handleReply, getCommentCount: () => comments.length, })); async function getComments(page) { if (!game_id) return; const res = await CommentServices.getComments({ page, pageSize: PAGESIZE, game_id, }); if (res.code === 0) { const newComments: Comment[] = res.data.rows; setComments((prev) => { const res = [...prev]; res.splice(page * PAGESIZE - 1, newComments.length, ...newComments); return res; }); } } async function getReplies(c: Comment) { const { replies, id: comment_id } = c; const page = replies.length < PAGESIZE ? 1 : replies.length / PAGESIZE + 1; const res = await CommentServices.getReplies({ comment_id, page, pageSize: PAGESIZE, }); if (res.code === 0) { const newReplies = res.data.rows; setComments((prev) => { const newComments = [...prev]; newComments.forEach((item) => { if (item.id === comment_id) { item.replies.splice( page === 1 ? 0 : page * PAGESIZE - 1, newReplies.length, ...newReplies ); item.reply_count = res.data.count; } }); return newComments; }); } } function handleReply(options?: CommentInputReplyParamsType) { inputRef.current?.show(options); } function onSend({ content, parent_id, reply_to_user_id }) { if (!parent_id) { createComment(content); return; } replyComment({ content, parent_id, reply_to_user_id }); } async function createComment(val: string) { const res = await CommentServices.createComment({ game_id, content: val }); if (res.code === 0) { setComments((prev) => { return [{ ...res.data, replies: [] }, ...prev]; }); toast("发布成功"); } } async function replyComment({ parent_id, reply_to_user_id, content }) { const res = await CommentServices.replyComment({ parent_id, reply_to_user_id, content, }); if (res.code === 0) { setComments((prev) => { return prev.map((item) => { if (item.id === parent_id) { return { ...item, replies: [res.data, ...item.replies], reply_count: item.reply_count + 1, }; } return item; }); }); toast("回复成功"); } } async function deleteComment({ parent_id, id }) { const res = await CommentServices.deleteComment({ comment_id: id }); if (res.code === 0) { if (parent_id) { setComments((prev) => { return prev.map((item) => { if (item.id === parent_id) { return { ...item, replies: item.replies.filter( (replyItem) => replyItem.id !== id ), reply_count: item.reply_count - 1, }; } return item; }); }); } else { setComments((prev) => { console.log(prev, parent_id, id); return prev.filter((item) => item.id !== id); }); } toast("评论已删除"); } } return ( {comments.length > 0 ? `${comments.length} 条` : ""}评论 handleReply()}> 写评论 {comments.length > 0 ? ( {comments.map((comment) => { return ( ); })} ) : ( 快来发表第一条评论 )} ); });