diff --git a/src/other_pages/comment_reply/index.scss b/src/other_pages/comment_reply/index.scss index 92755ea..20046e5 100644 --- a/src/other_pages/comment_reply/index.scss +++ b/src/other_pages/comment_reply/index.scss @@ -12,44 +12,43 @@ .navbar { height: 100px; background: #FFFFFF; - position: sticky; - top: 0; + flex-shrink: 0; z-index: 100; .navbar-content { height: 56px; display: flex; align-items: center; - justify-content: center; padding: 0 15px; margin-top: 44px; - position: relative; + gap: 12px; .back-button { - position: absolute; - left: 10px; - width: 32px; - height: 32px; + width: 24px; + height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; + flex-shrink: 0; + margin-top: 2px; .back-icon { - width: 8px; - height: 16px; + width: 10px; + height: 10px; position: relative; + display: flex; + align-items: center; + justify-content: center; &::before { content: ''; position: absolute; - top: 0; - left: 0; - width: 8px; - height: 8px; - border-left: 2.67px solid #000000; - border-bottom: 2.67px solid #000000; - transform: rotate(45deg); + width: 10px; + height: 10px; + border-left: 2px solid #000000; + border-bottom: 2px solid #000000; + transform: rotate(45deg) translateY(-1px); } } } @@ -61,6 +60,7 @@ line-height: 1.4; letter-spacing: 0.019em; color: #000000; + flex: 1; } } } @@ -68,8 +68,10 @@ // 评论列表滚动区域 .comment-scroll { flex: 1; - height: 100%; + height: 0; padding: 0 15px; + box-sizing: border-box; + overflow-y: auto; } // 评论列表 @@ -84,6 +86,7 @@ gap: 12px; padding: 16px 0; border-bottom: 0.5px solid rgba(0, 0, 0, 0.08); + box-sizing: border-box; .comment-left { display: flex; @@ -96,6 +99,11 @@ height: 48px; border-radius: 999px; flex-shrink: 0; + cursor: pointer; + + &:active { + opacity: 0.8; + } } .comment-content { @@ -111,6 +119,12 @@ font-size: 14px; line-height: 1.43; color: #000000; + cursor: pointer; + width: fit-content; + + &:active { + opacity: 0.6; + } } .action-row { @@ -225,4 +239,73 @@ color: rgba(0, 0, 0, 0.35); } } -} + + // 回复输入框 + .reply-input-container { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #F8F8F8; + border-top: 0.5px solid rgba(0, 0, 0, 0.06); + padding: 8px 12px; + padding-bottom: calc(20px + env(safe-area-inset-bottom)); + z-index: 200; + + .reply-input-wrapper { + display: flex; + align-items: center; + gap: 0; + background: #FFFFFF; + border-radius: 20px; + + .input-row { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + + height: 36px; + padding-left: 16px; + margin: 12px 0px; + + + .reply-input { + flex: 1; + border: none; + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 15px; + line-height: 1.4; + color: #000000; + + &::placeholder { + color: rgba(60, 60, 67, 0.3); + font-size: 15px; + } + } + + .input-actions { + display: flex; + align-items: center; + + .send-btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + + .send-icon { + width: 100%; + height: 100%; + } + + } + } + } + } + } +} \ No newline at end of file diff --git a/src/other_pages/comment_reply/index.tsx b/src/other_pages/comment_reply/index.tsx index 8e94dd3..432ed52 100644 --- a/src/other_pages/comment_reply/index.tsx +++ b/src/other_pages/comment_reply/index.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { View, Text, ScrollView, Image } from "@tarojs/components"; +import { View, Text, ScrollView, Image, Input } from "@tarojs/components"; import { Avatar } from "@nutui/nutui-react-taro"; import { withAuth, EmptyState } from "@/components"; import commentService, { CommentActivity } from "@/services/commentService"; @@ -9,6 +9,7 @@ import "./index.scss"; // 评论/回复类型定义 interface CommentReplyItem { id: number; + user_id: number; user_avatar: string; user_nickname: string; action_type: "comment" | "reply"; // 评论了你的球局 / 回复了你的评论 @@ -18,12 +19,18 @@ interface CommentReplyItem { activity_image: string; activity_id: number; activity_title: string; + parent_id: number | null; // 父评论ID,用于回复 + game_id: number; // 球局ID } const CommentReply = () => { const [commentList, setCommentList] = useState([]); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); + const [showReplyInput, setShowReplyInput] = useState(false); + const [replyTarget, setReplyTarget] = useState(null); + const [replyContent, setReplyContent] = useState(""); + const [inputFocus, setInputFocus] = useState(false); useEffect(() => { getCommentReplyList(); @@ -32,29 +39,32 @@ const CommentReply = () => { // 获取评论和回复列表 const getCommentReplyList = async () => { if (loading) return; - + setLoading(true); try { const res = await commentService.getMyActivities({ page: 1, pageSize: 20, }); - + if (res.code === 0 && res.data) { // 映射数据 const mappedList = res.data.rows.map((item: CommentActivity) => ({ id: item.id, + user_id: item.user?.id || 0, user_avatar: item.user?.avatar_url || "", user_nickname: item.user?.nickname || "匿名用户", action_type: item.type === "reply" ? "reply" as const : "comment" as const, time: item.create_time, content: item.content || "", original_comment: item.parent_comment?.content || "", - activity_image: item.game?.image_list?.[0] || "", + activity_image: item.game?.image || "", activity_id: item.game_id, activity_title: item.game?.title || "", + parent_id: item.parent_id, + game_id: item.game_id, })); - + setCommentList(mappedList); } } catch (e) { @@ -71,14 +81,14 @@ const CommentReply = () => { // 格式化时间显示 const formatTime = (timeStr: string) => { if (!timeStr) return ""; - + const date = new Date(timeStr); const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / (1000 * 60)); const hours = Math.floor(diff / (1000 * 60 * 60)); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - + if (minutes < 60) { return `${minutes}分钟前`; } else if (hours < 24) { @@ -95,18 +105,95 @@ const CommentReply = () => { }; // 处理回复 - const handleReply = (item: CommentReplyItem) => { - console.log("回复:", item); - // TODO: 跳转到回复页面或弹出回复框 + const handleReply = (e: any, item: CommentReplyItem) => { + e.stopPropagation(); // 阻止事件冒泡 + setReplyTarget(item); + setShowReplyInput(true); + setInputFocus(true); + setReplyContent(""); }; - // 处理点击球局 - const handleGameClick = (gameId: number) => { + // 处理点击评论项(跳转到球局详情) + const handleCommentClick = (item: CommentReplyItem) => { Taro.navigateTo({ - url: `/game_pages/detail/index?id=${gameId}`, + url: `/game_pages/detail/index?id=${item.activity_id}&message_id=${item.id}`, }); }; + // 处理点击用户(跳转到个人页) + const handleUserClick = (e: any, userId: number) => { + e.stopPropagation(); // 阻止事件冒泡 + Taro.navigateTo({ + url: `/user_pages/other/index?user_id=${userId}`, + }); + }; + + // 发送回复 + const handleSendReply = async () => { + if (!replyContent.trim() || !replyTarget) { + Taro.showToast({ + title: "请输入回复内容", + icon: "none", + duration: 2000, + }); + return; + } + + try { + // 调用回复接口 + // 如果是回复评论,parent_id 使用评论的 parent_id 或 id + // 如果是顶级评论,parent_id 为 null,则使用评论的 id 作为 parent_id + const parentId = replyTarget.parent_id || replyTarget.id; + + const res = await commentService.replyComment( + parentId, + replyTarget.user_id, + replyContent.trim() + ); + + if (res.code === 0) { + Taro.showToast({ + title: "回复成功", + icon: "success", + duration: 2000, + }); + + // 关闭输入框 + setShowReplyInput(false); + setReplyTarget(null); + setReplyContent(""); + setInputFocus(false); + + // 刷新列表 + getCommentReplyList(); + } else { + throw new Error(res.message || "回复失败"); + } + } catch (e: any) { + Taro.showToast({ + title: e.message || "回复失败", + icon: "none", + duration: 2000, + }); + } + }; + + // 取消回复 + const handleCancelReply = () => { + setShowReplyInput(false); + setReplyTarget(null); + setReplyContent(""); + setInputFocus(false); + }; + + // 输入框失去焦点 + const handleInputBlur = () => { + // 延迟执行,避免点击发送按钮时输入框先失焦导致发送失败 + setTimeout(() => { + handleCancelReply(); + }, 200); + }; + // 处理返回 const handleBack = () => { Taro.navigateBack(); @@ -120,21 +207,24 @@ const CommentReply = () => { page: 1, pageSize: 20, }); - + if (res.code === 0 && res.data) { const mappedList = res.data.rows.map((item: CommentActivity) => ({ id: item.id, + user_id: item.user?.id || 0, user_avatar: item.user?.avatar_url || "", user_nickname: item.user?.nickname || "匿名用户", action_type: item.type === "reply" ? "reply" as const : "comment" as const, time: item.create_time, content: item.content || "", original_comment: item.parent_comment?.content || "", - activity_image: item.game?.image_list?.[0] || "", + activity_image: item.game?.image || "", activity_id: item.game_id, activity_title: item.game?.title || "", + parent_id: item.parent_id, + game_id: item.game_id, })); - + setCommentList(mappedList); } } catch (e) { @@ -151,26 +241,36 @@ const CommentReply = () => { // 渲染评论/回复项 const renderCommentItem = (item: CommentReplyItem) => { const actionText = item.action_type === "comment" ? "评论了你的球局" : "回复了你的评论"; - + return ( - + handleCommentClick(item)} + > handleUserClick(e, item.user_id)} /> - + - {item.user_nickname} - + handleUserClick(e, item.user_id)} + > + {item.user_nickname} + + {actionText} {formatTime(item.time)} - + {item.content} - + {/* 如果是回复,显示被回复的评论 */} {item.action_type === "reply" && item.original_comment && ( @@ -178,24 +278,29 @@ const CommentReply = () => { {item.original_comment} )} - + {/* 回复按钮 */} - handleReply(item)}> - handleReply(e, item)}> + 回复 - + {/* 右侧球局图片 */} - handleGameClick(item.activity_id)} + onClick={(e) => { + e.stopPropagation(); + Taro.navigateTo({ + url: `/game_pages/detail/index?id=${item.game_id}`, + }); + }} /> ); @@ -227,7 +332,7 @@ const CommentReply = () => { {commentList.length > 0 ? ( {commentList.map(renderCommentItem)} - + {/* 到底了提示 */} 到底了 @@ -237,6 +342,46 @@ const CommentReply = () => { )} + + {/* 回复输入框 */} + {showReplyInput && replyTarget && ( + <> + {/* 遮罩层 */} + + + + + + setReplyContent(e.detail.value)} + onBlur={handleInputBlur} + confirmType="send" + onConfirm={handleSendReply} + /> + + + + + + + + + + )} ); }; diff --git a/src/other_pages/message/index.tsx b/src/other_pages/message/index.tsx index e3cae79..1f9dd92 100644 --- a/src/other_pages/message/index.tsx +++ b/src/other_pages/message/index.tsx @@ -19,6 +19,7 @@ interface MessageItem { related_user_avatar?: string; related_user_nickname?: string; activity_image?: string; + jump_url?: string; } // 消息分类类型 @@ -68,7 +69,7 @@ const Message = () => { }); return; } - + // 点击关注标签跳转到新增关注页面 if (tab === "follow") { Taro.navigateTo({ @@ -76,14 +77,27 @@ const Message = () => { }); return; } - + setActiveTab(activeTab === tab ? null : tab); }; // 处理查看详情 - const handleViewDetail = (messageId: string) => { - console.log("查看详情:", messageId); - // TODO: 根据消息类型跳转到对应详情页 + const handleViewDetail = (message: MessageItem) => { + + if (!message.jump_url) { + console.log("暂无跳转链接"); + return; + } + + Taro.navigateTo({ + url: message.jump_url, + }).catch(() => { + Taro.showToast({ + title: "页面不存在", + icon: "none", + duration: 2000, + }); + }); }; // 处理滚动到底部 @@ -152,7 +166,7 @@ const Message = () => { 消息 @@ -166,9 +180,9 @@ const Message = () => { className={`tab-item ${activeTab === "comment" ? "active" : ""}`} onClick={() => handleTabClick("comment")} > - 评论和回复 @@ -176,9 +190,9 @@ const Message = () => { className={`tab-item ${activeTab === "follow" ? "active" : ""}`} onClick={() => handleTabClick("follow")} > - 新增关注 @@ -212,7 +226,7 @@ const Message = () => { - handleViewDetail(message.id)}> + handleViewDetail(message)}> 查看详情 diff --git a/src/other_pages/new_follow/index.scss b/src/other_pages/new_follow/index.scss index 5e8dc98..c0d331b 100644 --- a/src/other_pages/new_follow/index.scss +++ b/src/other_pages/new_follow/index.scss @@ -12,44 +12,43 @@ .navbar { height: 100px; background: #FFFFFF; - position: sticky; - top: 0; + flex-shrink: 0; z-index: 100; .navbar-content { height: 56px; display: flex; align-items: center; - justify-content: center; padding: 0 15px; margin-top: 44px; - position: relative; + gap: 12px; .back-button { - position: absolute; - left: 10px; - width: 32px; - height: 32px; + width: 24px; + height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; + flex-shrink: 0; + margin-top: 2px; .back-icon { - width: 8px; - height: 16px; + width: 10px; + height: 10px; position: relative; + display: flex; + align-items: center; + justify-content: center; &::before { content: ''; position: absolute; - top: 0; - left: 0; - width: 8px; - height: 8px; - border-left: 2.67px solid #000000; - border-bottom: 2.67px solid #000000; - transform: rotate(45deg); + width: 10px; + height: 10px; + border-left: 2px solid #000000; + border-bottom: 2px solid #000000; + transform: rotate(45deg) translateY(-1px); } } } @@ -61,6 +60,7 @@ line-height: 1.4; letter-spacing: 0.019em; color: #000000; + flex: 1; } } } @@ -68,7 +68,8 @@ // 关注列表滚动区域 .follow-scroll { flex: 1; - height: 100%; + height: 0; + overflow-y: auto; } // 关注列表 diff --git a/src/services/commentService.ts b/src/services/commentService.ts index 88913c6..2d8ba04 100644 --- a/src/services/commentService.ts +++ b/src/services/commentService.ts @@ -23,6 +23,7 @@ export interface CommentActivity { title: string; start_time: string; location_name: string; + image?: string; image_list?: string[]; }; reply_to_user: { diff --git a/src/static/message/send-icon.svg b/src/static/message/send-icon.svg new file mode 100644 index 0000000..df171b0 --- /dev/null +++ b/src/static/message/send-icon.svg @@ -0,0 +1,6 @@ + + + + + +