From 4d55225bdf170e57d82e1eab88da1343b8a49f53 Mon Sep 17 00:00:00 2001 From: MaggieMii <1350383261@qq.com> Date: Sun, 4 Aug 2024 20:43:56 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(=E6=96=B0=E5=A2=9E=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E5=92=8C=E8=AF=84=E8=AE=BA):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.config.ts | 4 +- src/assets/types.ts | 37 +++++ src/assets/userService.ts | 12 ++ .../CommentComponent/CommentComponent.tsx | 136 ++++++++++++++++ src/components/CommentComponent/index.scss | 80 ++++++++++ src/components/comment/comment.scss | 4 + src/components/comment/comment.tsx | 11 +- src/components/label1/label1.tsx | 11 +- src/fetch.ts | 27 ++-- src/pages/evaluateInfo/index.config.ts | 3 + src/pages/evaluateInfo/index.scss | 15 ++ src/pages/evaluateInfo/index.tsx | 145 ++++++++++++++++++ src/pages/main/index.tsx | 29 ++-- src/pages/research/research.tsx | 3 +- 14 files changed, 481 insertions(+), 36 deletions(-) create mode 100644 src/assets/types.ts create mode 100644 src/assets/userService.ts create mode 100644 src/components/CommentComponent/CommentComponent.tsx create mode 100644 src/components/CommentComponent/index.scss create mode 100644 src/pages/evaluateInfo/index.config.ts create mode 100644 src/pages/evaluateInfo/index.scss create mode 100644 src/pages/evaluateInfo/index.tsx diff --git a/src/app.config.ts b/src/app.config.ts index 148b2b4..2f1d5bd 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,5 +1,8 @@ export default defineAppConfig({ pages: [ + 'pages/main/index', + 'pages/classInfo/index', + 'pages/evaluateInfo/index', 'pages/login/index', 'pages/personalPage/index', 'pages/myCollection/index', @@ -7,7 +10,6 @@ export default defineAppConfig({ 'pages/messageNotification/index', 'pages/officialNotification/index', 'pages/feedback/index', - 'pages/classInfo/index', 'pages/evaluate/evaluate', 'pages/myclass/myclass', 'pages/research/research', diff --git a/src/assets/types.ts b/src/assets/types.ts new file mode 100644 index 0000000..f6446b6 --- /dev/null +++ b/src/assets/types.ts @@ -0,0 +1,37 @@ +// types.ts +export interface Comment { + id: number; + commentator_id: number; + biz: string; + biz_id: number; + content: string; + root_comment_id: number; + parent_comment_id: number; + reply_to_uid: number; + utime: number; + ctime: number; + user?: User; // 存储用户信息 + replies?: Comment[]; // 存储二级评论 +} + +// 定义评论详情的类型 +export type CommentInfoType = { + nickname:string; + avatar:string; + id: number; + content : string; + class_name: string; + teacher: string; + star_rating: number; + total_support_count: number; + total_oppose_count: number; + total_comment_count: number; + utime: number; + ctime: number; +}; + +export interface User { + id: number; + avatar: string; + nickname: string; +} \ No newline at end of file diff --git a/src/assets/userService.ts b/src/assets/userService.ts new file mode 100644 index 0000000..ade29aa --- /dev/null +++ b/src/assets/userService.ts @@ -0,0 +1,12 @@ +// userService.ts +import { User } from './types'; +import { get } from '@/fetch'; // 确保这个路径正确 + +export const getUserInfo = (userId: number): Promise => { + return get(`/users/${userId}/profile`).then((res) => { + if (res.code === 0 && res.data) { + return res.data; + } + throw new Error('User not found'); + }); +}; \ No newline at end of file diff --git a/src/components/CommentComponent/CommentComponent.tsx b/src/components/CommentComponent/CommentComponent.tsx new file mode 100644 index 0000000..45c568e --- /dev/null +++ b/src/components/CommentComponent/CommentComponent.tsx @@ -0,0 +1,136 @@ +// Comment.tsx +import React, { useState, useEffect } from 'react'; +import { View, Text,Image } from '@tarojs/components'; +import { Comment } from '../../assets/types'; +import './index.scss' +import { getUserInfo } from '../../assets/userService'; +import { get } from '@/fetch'; + +interface CommentProps { + comments: Comment[]; + onCommentClick: (comment: Comment) => void; +} + +const CommentComponent: React.FC = ({ comments, onCommentClick }) => { + // console.log(comments); + const [allComments, setAllComments] = useState(comments); + + useEffect(() => { + const fetchAllReplies = async () => { + const topLevelComments = allComments.filter(c => c.parent_comment_id === 0 && c.root_comment_id === 0); + + const promises = topLevelComments.map(async (comment) => { + const res = await get(`/comments/replies/list?root_id=${comment.id}&cur_comment_id=0&limit=10`); + if (res.code === 0 && Array.isArray(res.data)) { + const replies = res.data.map((reply) => ({ + ...reply + })); + return { ...comment, replies }; + } + return { ...comment, replies: [] }; + }); + + const updatedComments = (await Promise.all(promises)); + + const commentsWithUserInfo = await Promise.all( + updatedComments.map(async (comment) => { + const user = await getUserInfo(comment.commentator_id); + return { ...comment, user }; + }) + ); + + const commentsWithRepliesAndUserInfo = await Promise.all( + commentsWithUserInfo.map(async (comment) => { + const replies = comment.replies || []; + const repliesWithUserInfo = await Promise.all( + replies.map(async (reply) => { + const user = await getUserInfo(reply.commentator_id); + return { ...reply, user }; + }) + ); + return { ...comment, replies: repliesWithUserInfo }; + }) + ); + + console.log(commentsWithRepliesAndUserInfo) + + setAllComments(commentsWithRepliesAndUserInfo); + }; + + fetchAllReplies(); + }, []); + + const ctimeToString = (ctime: number)=>{ + const ctimeDate = new Date(ctime); + return {ctimeDate.toLocaleString()} + } + + // 辅助函数:获取回复者的昵称 +const getReplyToNickname = (replyToUid: number): string => { + // 遍历所有评论的回复 + for (const comment of allComments) { + const reply = comment.replies?.find(r => r.commentator_id === replyToUid); + if (reply && reply.user) { + return reply.user.nickname; + } + } + return '未知用户'; // 如果没有找到回复者,返回默认昵称 +}; + + return ( + + {allComments.map((comment) => ( + { + onCommentClick(comment) + e.stopPropagation(); + } + }> + + + {comment.user?.nickname} + { + ctimeToString(comment.ctime) + } + + + {comment.content} + + + {comment.replies?.map((reply) => ( + { + onCommentClick(reply); + e.stopPropagation();} + }> + + + + {reply.user?.nickname} + {reply.root_comment_id !== reply.parent_comment_id ? ( + + 回复 {getReplyToNickname(reply.reply_to_uid)} + + ) : null} + + { + ctimeToString(reply.ctime) + } + + + {reply.content} + + + ))} + + + ))} + + ); +}; + +export default CommentComponent; diff --git a/src/components/CommentComponent/index.scss b/src/components/CommentComponent/index.scss new file mode 100644 index 0000000..e659c3d --- /dev/null +++ b/src/components/CommentComponent/index.scss @@ -0,0 +1,80 @@ +// style.scss +.comments { + padding: 10px; +} + +.acomment { + width: 659rpx; + margin: 30rpx 0; + background: #F9F9F2; + border-radius: 43rpx 43rpx 43rpx 43rpx; + border: 2rpx solid #F9F9F2; + padding: 30rpx; +} + +.comment-header { + display: flex; + align-items: center; + margin-bottom: 20rpx; +} + +.avatar { + width: 57.97rpx; + height: 57.97rpx; + border-radius: 100%; + margin-right: 10px; + border: #d2d5d8 solid 5rpx; +} + +.nickname { + font-weight: bold; + font-size: 22rpx; + color: #565552; +} + +.comment-content { + margin-top: 5px; + font-weight: 400; + font-size: 22rpx; + color: #565552; +} + +.replies { + margin-top: 20px; + padding-left: 10px; +} + +.reply .nickname { + font-weight: 400; + font-size: 22rpx; + color: #565552; +} + +.reply { + padding-left: 10px; + margin-bottom: 5px; +} + +.reply-header { + display: flex; + align-items: center; +} + +.reply-content { + margin-top: 5px; + font-weight: 400; + font-size: 22rpx; + color: #565552; + margin-left: 70rpx; +} + +.time{ + font-weight: 400; + font-size: 18rpx; + color: #A6A193; + margin-left:30rpx; +} + +.reply-indicator{ + margin-left: 10rpx; +} \ No newline at end of file diff --git a/src/components/comment/comment.scss b/src/components/comment/comment.scss index 4ab4e20..4afcbd0 100644 --- a/src/components/comment/comment.scss +++ b/src/components/comment/comment.scss @@ -34,6 +34,10 @@ padding-right: 25rpx; } +.comment{ + position: relative; +} + .comment .tx { width: 92.39rpx; height: 92.39rpx; diff --git a/src/components/comment/comment.tsx b/src/components/comment/comment.tsx index f6e363c..a7c5fe6 100644 --- a/src/components/comment/comment.tsx +++ b/src/components/comment/comment.tsx @@ -1,20 +1,19 @@ -import React, { useEffect } from "react"; +// import React, { useEffect } from "react"; import { Text, View, Image, Navigator } from "@tarojs/components"; import "./comment.scss"; import ShowStar from "../showStar/showStar"; export default function comment(props) { + - // let className = "课程名"; - // let teacherName = "老师名"; // 创建一个新的Date对象,传入时间戳 const ctimeDate = new Date(props.ctime); - const utimeDate = new Date(props.utime); + // const utimeDate = new Date(props.utime); return ( - + {props.class_name + " (" + props.teacher +") " } @@ -58,7 +57,7 @@ export default function comment(props) { - {props.dislike} + {props.total_oppose_count} ); diff --git a/src/components/label1/label1.tsx b/src/components/label1/label1.tsx index 5e57c94..6fd4e0d 100644 --- a/src/components/label1/label1.tsx +++ b/src/components/label1/label1.tsx @@ -1,14 +1,17 @@ import { Text, View } from '@tarojs/components'; +import { useCallback } from 'react'; import './label1.scss'; export default function Label1(props) { - - const handleClick = () => props.onClick(props.content); + const handleClick = useCallback((event) => { + event.stopPropagation(); // 阻止事件冒泡 + props.onClick(props.content); + }, [props.content, props.onClick]); return ( - + {props.content} ); -} +} \ No newline at end of file diff --git a/src/fetch.ts b/src/fetch.ts index b1c7884..9ded0ef 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -7,19 +7,28 @@ export async function post(url = '', data = {}, isToken = true) { 'Content-Type': 'application/json;charset=utf-8', }; - if (isToken) { - Taro.getStorage({ - key: 'token', - success: (res) => { - const token = res.data; - if (token) header['Authorization'] = token; - else { - Taro.navigateTo({ url: '/pages/login/index' }); + const getToken = () => { + return new Promise((resolve, reject) => { + Taro.getStorage({ + key: "token", + success: (res) => { + const token = res.data; + if (token) { + resolve(token); // 如果token存在,解析Promise + } else { + reject(new Error("No token found")); // 如果没有token,拒绝Promise + Taro.navigateTo({ url: "/pages/login/index" }); // 导航到登录页面 + } + }, + fail: (err) => { + reject(new Error(`Failed to get token: ${err}`)); // 存储操作失败时拒绝Promise } - }, + }); }); } + if (isToken) header["Authorization"] = `Bearer ${await getToken()}`; + try { const response = await Taro.request({ url: `${preUrl}${url}`, diff --git a/src/pages/evaluateInfo/index.config.ts b/src/pages/evaluateInfo/index.config.ts new file mode 100644 index 0000000..10e4e25 --- /dev/null +++ b/src/pages/evaluateInfo/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '首页', +}); diff --git a/src/pages/evaluateInfo/index.scss b/src/pages/evaluateInfo/index.scss new file mode 100644 index 0000000..29ba9ce --- /dev/null +++ b/src/pages/evaluateInfo/index.scss @@ -0,0 +1,15 @@ +.evaluateInfo{ + display: flex; + flex-direction: column; + align-items: center; + padding-bottom: 130rpx; +} + +.reply-input{ + position: fixed; + bottom: 0; + background-color: white; + width: 100%; + text-align: center; + height: 100rpx; +} \ No newline at end of file diff --git a/src/pages/evaluateInfo/index.tsx b/src/pages/evaluateInfo/index.tsx new file mode 100644 index 0000000..fae1f5d --- /dev/null +++ b/src/pages/evaluateInfo/index.tsx @@ -0,0 +1,145 @@ +import { View,Input } from '@tarojs/components'; +import Comment from '@/components/comment/comment'; +import CommentComponent from '@/components/CommentComponent/CommentComponent'; +import { useState, useEffect } from 'react'; +import { get,post } from '@/fetch'; +import Taro from '@tarojs/taro'; + +import './index.scss' +import { Comment as CommentType,CommentInfoType } from '../../assets/types'; + +export default function Index() { + const [allComments, setAllComments] = useState([]); + const [commentsLoaded, setCommentsLoaded] = useState(false); // 新增状态,标记评论是否已加载 + const [replyTo, setReplyTo] = useState(null); // 新增状态,存储被回复的评论 + const [replyContent, setReplyContent] = useState(''); // 存储回复内容 + const [placeholderContent, setplaceholderContent] = useState('写下你的评论...'); // 存储占位内容 + + const [comment, setComment] = useState(null);//获取课评信息 + // const biz_id = 1; + const [biz_id, setBiz_id] = useState(null); + + useEffect(() => { + + const handleQuery = () => { + const query = Taro.getCurrentInstance()?.router?.params; // 获取查询参数 + const serializedComment = query?.comment; + if (serializedComment) { + try { + // 解析字符串 + const parsedComment = JSON.parse(decodeURIComponent(serializedComment)); + setComment(parsedComment); + setBiz_id(parsedComment.id); + } catch (error) { + console.error('解析评论参数失败', error); + } + } + }; + + handleQuery(); + + const fetchComments = async () => { + + // console.log(biz_id) + try { + const res = await get(`/comments/list?biz=Evaluation&biz_id=${biz_id}&cur_comment_id=0&limit=100`); + // console.log(res.data); + setAllComments(res.data); + setCommentsLoaded(true); + } catch (error) { + console.error('加载评论失败', error); + } + }; + + // 确保 biz_id 设置后再调用 fetchComments + if (biz_id !== null) { + console.log(1); + fetchComments(); + } + + + + }, [biz_id,commentsLoaded]); // 依赖项中添加biz_id + + + const handleCommentClick = (comment: CommentType) => { + console.log(comment); + setReplyTo(comment); // 设置回复目标 + setplaceholderContent(`回复给${comment.user?.nickname}: `); // 初始化回复内容 + }; + + const handleReplyChange = (e: any) => { + setReplyContent(e.target.value); + }; + + const handleClearReply = () =>{ + console.log(2) + setReplyTo(null); + setReplyContent(''); + setplaceholderContent('写下你的评论...'); + } + + const handleReplySubmit = async () => { + if (!replyContent.trim()) return; // 忽略空内容 + // console.log(1); + + // console.log( + // { + // biz: "Evaluation", + // biz_id, + // content: replyContent, + // parent_id: replyTo?.id || 0, + // root_id: replyTo?.root_comment_id === 0 ? replyTo?.id : (replyTo?.root_comment_id || 0) + // } + // ) + + post('/comments/publish', { + biz: "Evaluation", + biz_id, + content: replyContent, + parent_id: replyTo?.id || 0, + root_id: replyTo?.root_comment_id === 0 ? replyTo?.id : (replyTo?.root_comment_id || 0) + }).then((res) => { + console.log('评论发布成功', res); + }); + + + + + + // try { + // const res = await post(`/comments/create`, { + // biz_id, + // content: replyContent, + // parent_comment_id: replyTo?.id || 0, + // reply_to_uid: replyTo?.commentator_id || 0, + // }); + // console.log('评论发布成功', res); + + // 清空回复目标和输入框 + setReplyTo(null); + setReplyContent(''); + + setCommentsLoaded(false); + }; + + + + // 仅当评论数据加载完成时渲染CommentComponent + return ( + + + {commentsLoaded && } + + {e.stopPropagation();}} + onInput={handleReplyChange} + onConfirm={handleReplySubmit} + /> + + + ); +} \ No newline at end of file diff --git a/src/pages/main/index.tsx b/src/pages/main/index.tsx index 90703e5..2fc7f20 100644 --- a/src/pages/main/index.tsx +++ b/src/pages/main/index.tsx @@ -6,21 +6,10 @@ import { View } from '@tarojs/components'; import SearchInput from '../../components/SearchInput/SearchInput'; import './index.scss' import { GuildLine } from "@/components"; +import { CommentInfoType } from '../../assets/types'; + -// 定义新对象的类型 -type NewObjectType = { - id: number; - content : string; - class_name: string; - teacher: string; - star_rating: number; - total_support_count: number; - total_oppose_count: number; - total_comment_count: number; - utime: number; - ctime: number; -}; type CourseDetailsType = { class_name: string; @@ -42,7 +31,7 @@ export default function Index() { }; - const [comments,setComments] = useState([]); + const [comments,setComments] = useState([]); // 修改 fetchCourseDetails 函数以返回正确的类型 async function fetchCourseDetails(courseId: number): Promise { @@ -86,7 +75,7 @@ async function fetchUserDetails(User: number): Promise { // 定义一个函数来处理数据并返回新的数组 async function processData(data) { - const processedData: NewObjectType[] = []; + const processedData: CommentInfoType[] = []; for (const item of data) { try { @@ -172,6 +161,15 @@ const getData = (type)=>{ }; + const handleCommentClick = (comment:CommentInfoType)=>{ + console.log(comment) + // 序列化对象 + const serializedComment = encodeURIComponent(JSON.stringify(comment)) + Taro.navigateTo({ + url: `/pages/evaluateInfo/index?comment=${serializedComment}` + }); + } + return ( @@ -218,6 +216,7 @@ const getData = (type)=>{ key={comment.id} // 使用唯一key值来帮助React识别哪些元素是不同的 {...comment} // 展开comment对象,将属性传递给Comment组件 type='inner' // 固定属性,不需要从数组中获取 + onClick={() => handleCommentClick(comment)} /> ))} diff --git a/src/pages/research/research.tsx b/src/pages/research/research.tsx index 475a4f0..eb37c84 100644 --- a/src/pages/research/research.tsx +++ b/src/pages/research/research.tsx @@ -103,7 +103,8 @@ const ConditionalRender = ({ isSpread, classes, hrs, handleSearch }) => { {hrs.map((hr) => ( - handleSearch(hr.keyword)} /> + handleSearch(hr.keyword)} + /> ))}