diff --git a/project.config.json b/project.config.json index eaad1b7..00f5065 100644 --- a/project.config.json +++ b/project.config.json @@ -1,46 +1,46 @@ { - "miniprogramRoot": "dist/", - "projectname": "playBallTogether", - "description": "playBallTogether", - "appid": "wx815b533167eb7b53", - "setting": { - "urlCheck": true, - "es6": true, - "enhance": true, - "postcss": false, - "preloadBackgroundData": false, - "minified": false, - "newFeature": true, - "coverView": true, - "nodeModules": false, - "autoAudits": false, - "showShadowRootInWxmlPanel": false, - "scopeDataCheck": false, - "uglifyFileName": false, - "checkInvalidKey": true, - "checkSiteMap": true, - "uploadWithSourceMap": true, - "compileHotReLoad": false, - "useMultiFrameRuntime": true, - "useApiHook": true, - "useApiHostProcess": false, - "babelSetting": { - "ignore": [], - "disablePlugins": [], - "outputPath": "" - }, - "enableEngineNative": false, - "useIsolateContext": true, - "useCompilerModule": false, - "userConfirmedUseCompilerModuleSwitch": false, - "userConfirmedBundleSwitch": false, - "packNpmManually": false, - "packNpmRelationList": [], - "minifyWXSS": true, - "minifyWXML": true - }, - "compileType": "miniprogram", - "simulatorType": "wechat", - "simulatorPluginLibVersion": {}, - "condition": {} - } + "miniprogramRoot": "dist/", + "projectname": "playBallTogether", + "description": "playBallTogether", + "appid": "wx815b533167eb7b53", + "setting": { + "urlCheck": true, + "es6": true, + "enhance": true, + "postcss": true, + "preloadBackgroundData": false, + "minified": true, + "newFeature": true, + "coverView": true, + "nodeModules": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": false, + "scopeDataCheck": false, + "uglifyFileName": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "compileHotReLoad": false, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": false, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "enableEngineNative": false, + "useIsolateContext": true, + "useCompilerModule": false, + "userConfirmedUseCompilerModuleSwitch": false, + "userConfirmedBundleSwitch": false, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "minifyWXML": true + }, + "compileType": "miniprogram", + "simulatorType": "wechat", + "simulatorPluginLibVersion": {}, + "condition": {} +} \ No newline at end of file diff --git a/project.private.config.json b/project.private.config.json index 2c19aa8..aaeb056 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -3,12 +3,12 @@ "projectname": "playBallTogether", "condition": {}, "setting": { - "urlCheck": true, + "urlCheck": false, "coverView": true, "lazyloadPlaceholderEnable": false, "skylineRenderEnable": false, "preloadBackgroundData": false, - "autoAudits": false, + "autoAudits": true, "useApiHook": true, "useApiHostProcess": true, "showShadowRootInWxmlPanel": false, diff --git a/src/app.config.ts b/src/app.config.ts index 9c3cf82..09a4ec6 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -54,6 +54,8 @@ export default defineAppConfig({ root: 'other_pages', pages: [ "message/index", + "comment_reply/index", // 收到的评论和回复 + "new_follow/index", // 新增关注 "favorites/index", // 收藏页 "ntrp-evaluate/index", // NTRP评估页 ], diff --git a/src/app.ts b/src/app.ts index 565dd5f..bf0017c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,7 @@ import { Component, ReactNode } from "react"; import "./nutui-theme.scss"; import "./app.scss"; -import "qweather-icons/font/qweather-icons.css"; +import "./scss/qweather-icons/qweather-icons.css"; import { useGlobalStore } from "./store/global"; interface AppProps { diff --git a/src/components/EmptyState/index.scss b/src/components/EmptyState/index.scss new file mode 100644 index 0000000..dd79951 --- /dev/null +++ b/src/components/EmptyState/index.scss @@ -0,0 +1,33 @@ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 124px 16px; + min-height: 400px; + + .empty-icon { + width: 221px; + height: 200px; + position: relative; + border-radius: 20px; + margin-bottom: 12px; + + .img { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; + height: 100%; + } + } + + .empty-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.71; + color: rgba(0, 0, 0, 0.35); + } +} diff --git a/src/components/EmptyState/index.tsx b/src/components/EmptyState/index.tsx new file mode 100644 index 0000000..f0d2f14 --- /dev/null +++ b/src/components/EmptyState/index.tsx @@ -0,0 +1,23 @@ +import { View, Text, Image } from "@tarojs/components"; +import "./index.scss"; + +interface EmptyStateProps { + text?: string; + icon?: any; // 图片资源 +} + +const EmptyState = ({ + text = "暂无数据", + icon = require("@/static/message/emi.svg") +}: EmptyStateProps) => { + return ( + + + + + {text} + + ); +}; + +export default EmptyState; diff --git a/src/components/index.ts b/src/components/index.ts index 785769a..6ad48dc 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -22,6 +22,7 @@ import FollowUserCard from './FollowUserCard/index'; import Comments from "./Comments"; import GeneralNavbar from "./GeneralNavbar"; import RadarChart from './Radar' +import EmptyState from './EmptyState'; export { ActivityTypeSwitch, @@ -49,4 +50,5 @@ export { Comments, GeneralNavbar, RadarChart, + EmptyState, }; diff --git a/src/other_pages/comment_reply/index.config.ts b/src/other_pages/comment_reply/index.config.ts new file mode 100644 index 0000000..82a32e0 --- /dev/null +++ b/src/other_pages/comment_reply/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationStyle: "custom", + navigationBarTitleText: "收到的评论和回复", +}); diff --git a/src/other_pages/comment_reply/index.scss b/src/other_pages/comment_reply/index.scss new file mode 100644 index 0000000..4d1bc60 --- /dev/null +++ b/src/other_pages/comment_reply/index.scss @@ -0,0 +1,246 @@ +@use '~@/scss/images.scss' as img; + +.comment-reply-container { + width: 100%; + height: 100vh; + box-sizing: border-box; + display: flex; + flex-direction: column; + background: #FFFFFF; + + // 顶部导航栏 + .navbar { + height: 100px; + background: #FFFFFF; + position: sticky; + top: 0; + z-index: 100; + + .navbar-content { + height: 56px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 15px; + margin-top: 44px; + position: relative; + + .back-button { + position: absolute; + left: 10px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + .back-icon { + width: 8px; + height: 16px; + position: relative; + + &::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); + } + } + } + + .navbar-title { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 20px; + line-height: 1.4; + letter-spacing: 0.019em; + color: #000000; + } + } + } + + // 评论列表滚动区域 + .comment-scroll { + flex: 1; + height: 100%; + padding: 0 15px; + } + + // 评论列表 + .comment-list { + padding-bottom: 20px; + } + + // 评论项 + .comment-item { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 16px 0; + border-bottom: 0.5px solid rgba(0, 0, 0, 0.08); + + .comment-left { + display: flex; + gap: 12px; + flex: 1; + min-width: 0; + + .user-avatar { + width: 48px; + height: 48px; + border-radius: 999px; + flex-shrink: 0; + } + + .comment-content { + display: flex; + flex-direction: column; + gap: 4px; + flex: 1; + min-width: 0; + + .user-nickname { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 14px; + line-height: 1.43; + color: #000000; + } + + .action-row { + display: flex; + align-items: center; + gap: 8px; + + .action-text, + .time-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 10px; + line-height: 1.6; + color: rgba(60, 60, 67, 0.6); + } + } + + .comment-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.5; + color: #000000; + word-break: break-word; + } + + // 被回复的评论 + .original-comment { + display: flex; + align-items: flex-start; + gap: 4px; + margin-top: 4px; + + .quote-line { + width: 2px; + height: 14px; + background: rgba(120, 120, 128, 0.12); + border-radius: 7px; + flex-shrink: 0; + } + + .original-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.5; + color: rgba(60, 60, 67, 0.6); + word-break: break-word; + flex: 1; + } + } + + // 回复按钮 + .reply-button { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + background: rgba(120, 120, 128, 0.12); + border-radius: 20px; + width: fit-content; + margin-top: 4px; + cursor: pointer; + + &:active { + opacity: 0.8; + } + + .reply-icon { + width: 12px; + height: 12px; + position: relative; + + // 绘制回复图标(使用SVG路径) + &::before, + &::after { + content: ''; + position: absolute; + background: #333333; + } + + &::before { + top: 1px; + left: 1px; + width: 10px; + height: 8.75px; + clip-path: polygon(0% 0%, 100% 0%, 100% 20%, 20% 20%, 20% 100%, 0% 100%); + } + + &::after { + width: 0; + height: 0.75px; + background: #333333; + } + } + + .reply-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 10px; + line-height: 1.6; + color: #000000; + } + } + } + } + + // 右侧球局图片 + .activity-image { + width: 48px; + height: 48px; + border-radius: 9px; + background: #F5F5F5; + flex-shrink: 0; + } + } + + // 到底了提示 + .bottom-tip { + display: flex; + justify-content: center; + align-items: center; + padding: 24px 0 12px; + + .tip-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.71; + color: rgba(0, 0, 0, 0.35); + } + } +} diff --git a/src/other_pages/comment_reply/index.tsx b/src/other_pages/comment_reply/index.tsx new file mode 100644 index 0000000..7742e58 --- /dev/null +++ b/src/other_pages/comment_reply/index.tsx @@ -0,0 +1,188 @@ +import { useState, useEffect } from "react"; +import { View, Text, ScrollView, Image } from "@tarojs/components"; +import { Avatar } from "@nutui/nutui-react-taro"; +import { withAuth, EmptyState } from "@/components"; +import noticeService from "@/services/noticeService"; +import Taro from "@tarojs/taro"; +import "./index.scss"; + +// 评论/回复类型定义 +interface CommentReplyItem { + id: string; + user_avatar: string; + user_nickname: string; + action_type: "comment" | "reply"; // 评论了你的球局 / 回复了你的评论 + time: string; + content: string; + original_comment?: string; // 被回复的评论内容 + activity_image: string; + activity_id?: string; + is_read: number; +} + +const CommentReply = () => { + const [commentList, setCommentList] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + getCommentReplyList(); + }, []); + + // 获取评论和回复列表 + const getCommentReplyList = async () => { + if (loading) return; + + setLoading(true); + try { + const res = await noticeService.getNotificationList({ + notification_type: "comment", // 筛选评论类型 + }); + + if (res.code === 0) { + // 映射数据 + const mappedList = res.data.list.map((item: any) => ({ + id: item.id, + user_avatar: item.related_user_avatar || "", + user_nickname: item.related_user_nickname || "匿名用户", + action_type: (item.notification_type === "reply" ? "reply" : "comment") as "comment" | "reply", + time: item.created_at, + content: item.content || "", + original_comment: item.original_content || "", + activity_image: item.activity_image || "", + activity_id: item.related_activity_id || "", + is_read: item.is_read, + })); + + setCommentList(mappedList); + } + } catch (e) { + Taro.showToast({ + title: "获取列表失败", + icon: "none", + duration: 2000, + }); + } finally { + setLoading(false); + } + }; + + // 格式化时间显示 + 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) { + return `${hours}小时前`; + } else if (days === 1) { + return "1天前"; + } else if (days < 7) { + return `${days}天前`; + } else { + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${month}月${day}日`; + } + }; + + // 处理回复 + const handleReply = (item: CommentReplyItem) => { + console.log("回复:", item); + // TODO: 跳转到回复页面或弹出回复框 + }; + + // 处理返回 + const handleBack = () => { + Taro.navigateBack(); + }; + + // 渲染评论/回复项 + const renderCommentItem = (item: CommentReplyItem) => { + const actionText = item.action_type === "comment" ? "评论了你的球局" : "回复了你的评论"; + + return ( + + + + + + {item.user_nickname} + + + {actionText} + {formatTime(item.time)} + + + {item.content} + + {/* 如果是回复,显示被回复的评论 */} + {item.action_type === "reply" && item.original_comment && ( + + + {item.original_comment} + + )} + + {/* 回复按钮 */} + handleReply(item)}> + + 回复 + + + + + {/* 右侧球局图片 */} + + + ); + }; + + return ( + + {/* 顶部导航栏 */} + + + + + + 收到的评论和回复 + + + + {/* 评论列表 */} + + {commentList.length > 0 ? ( + + {commentList.map(renderCommentItem)} + + {/* 到底了提示 */} + + 到底了 + + + ) : ( + + )} + + + ); +}; + +export default withAuth(CommentReply); diff --git a/src/other_pages/message/index.scss b/src/other_pages/message/index.scss index d2730dc..b644b3d 100644 --- a/src/other_pages/message/index.scss +++ b/src/other_pages/message/index.scss @@ -6,23 +6,22 @@ box-sizing: border-box; display: flex; flex-direction: column; + background: #FFFFFF; - // 导航栏 + // 顶部导航栏 .navbar { - height: 56px; + height: 100px; background: #FFFFFF; - padding-top: 44px; position: sticky; top: 0; z-index: 100; - box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.06); .navbar-content { height: 56px; display: flex; align-items: center; - justify-content: space-between; padding: 0 15px; + margin-top: 44px; .navbar-left { display: flex; @@ -39,52 +38,109 @@ font-weight: 600; font-size: 20px; line-height: 1.4; + letter-spacing: 0.019em; color: #000000; } } } } + // 分类标签区 + .category-tabs { + display: flex; + align-items: stretch; + gap: 20px; + padding: 6px 24px; + background: #FFFFFF; - // 消息列表 - .message-list { - flex: 1; - overflow: hidden; - box-sizing: border-box; - // margin-bottom:100px; - background-color: none !important; - - .message-list-content { + .tab-item { display: flex; flex-direction: column; - padding: 12px 12px 112px; + justify-content: center; + align-items: center; gap: 8px; + padding: 12px 15px; + flex: 1; + cursor: pointer; + transition: all 0.2s; + + .tab-icon { + width: 56px; + height: 56px; + border-radius: 56px; + transition: all 0.3s; + } + + .tab-text { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 16px; + line-height: 1.5; + color: #000000; + } + + &.active { + .tab-icon { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + transform: scale(1.05); + } + } + + &:active { + .tab-icon { + transform: scale(0.95); + } + } + } + } + + // 消息滚动区域 + .message-scroll { + flex: 1; + overflow: hidden; + background: #FFFFFF; + + .message-cards { + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; + padding-bottom: 120px; } - // 系统消息样式 - .system-message { + // 系统消息卡片 + .message-card { background: #FFFFFF; border: 0.5px solid rgba(0, 0, 0, 0.08); border-radius: 20px; - padding: 0 0 12px; box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06); - box-sizing: border-box; + padding: 0 0 12px; + transition: all 0.2s; - .message-header { - display: flex; - align-items: center; + &:active { + transform: scale(0.98); + opacity: 0.95; + } + + .card-title-row { padding: 12px 15px 0; - .message-title { + .card-title { font-family: 'PingFang SC'; font-weight: 600; font-size: 16px; line-height: 1.5; color: #000000; - flex: 1; } + } - .message-time { + .card-time-row { + padding: 4px 15px 0; + display: flex; + align-items: center; + gap: 2px; + + .card-time { font-family: 'PingFang SC'; font-weight: 400; font-size: 12px; @@ -93,31 +149,37 @@ } } - .message-content { + .card-content-row { padding: 8px 15px 0; + display: flex; + align-items: stretch; + gap: 2px; - .message-text { + .card-content { font-family: 'PingFang SC'; font-weight: 400; font-size: 14px; line-height: 1.43; color: rgba(0, 0, 0, 0.7); + flex: 1; } } - .message-action { + .card-footer { padding: 12px 15px 0; - .action-divider { + .footer-divider { height: 0.5px; background: rgba(0, 0, 0, 0.08); margin-bottom: 12px; + width: 100%; } - .action-button { + .footer-action { display: flex; justify-content: space-between; align-items: center; + cursor: pointer; .action-text { font-family: 'PingFang SC'; @@ -128,151 +190,94 @@ } .action-arrow { - width: 16px; - height: 16px; + position: relative; + .img { + position: absolute; + left: -16px; + top: -9px; + width: 14px;; + height: 14px; + } + } + + &:active { + opacity: 0.7; } } } } - // 用户消息样式 - .user-message { + // 到底了提示 + .bottom-tip { display: flex; + justify-content: center; align-items: center; - gap: 12px; - padding: 12px 15px; - background: #FFFFFF; - border: 0.5px solid rgba(0, 0, 0, 0.08); - border-radius: 20px; - box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06); - box-sizing: border-box; + padding: 24px 0 12px; - .message-avatar { - position: relative; - flex-shrink: 0; - - .unread-dot { - position: absolute; - top: -2px; - right: -2px; - width: 10px; - height: 10px; - background: #FF4848; - border-radius: 50%; - } - } - - .message-info { - flex: 1; - min-width: 0; - - .message-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 4px; - - .message-title { - font-family: 'PingFang SC'; - font-weight: 600; - font-size: 16px; - line-height: 1.5; - color: #000000; - } - - .message-time { - font-family: 'PingFang SC'; - font-weight: 400; - font-size: 14px; - line-height: 1.71; - color: rgba(0, 0, 0, 0.35); - } - } - - .message-content { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; - - .message-text { - font-family: 'PingFang SC'; - font-weight: 400; - font-size: 14px; - line-height: 1.29; - color: rgba(0, 0, 0, 0.35); - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .unread-indicator { - width: 10px; - height: 10px; - background: #FF4848; - border-radius: 50%; - flex-shrink: 0; - } - } + .tip-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.71; + color: rgba(0, 0, 0, 0.35); } } } - // 空状态 - .empty-state { + // 悬浮新建消息按钮 + .floating-button { + position: fixed; + right: 12px; + bottom: 132px; + width: 60px; + height: 60px; + border-radius: 50%; + background: radial-gradient(circle at 27% 8%, rgba(189, 255, 74, 1) 17%, rgba(149, 242, 62, 1) 54%, rgba(50, 216, 56, 1) 100%); + border: 2px solid rgba(0, 0, 0, 0.06); + box-shadow: 0px 4px 48px 0px rgba(0, 0, 0, 0.08); display: flex; - flex-direction: column; align-items: center; - padding: 124px 16px; - height: 746px; + justify-content: center; + cursor: pointer; + z-index: 99; + transition: all 0.2s; - .empty-icon { - width: 300px; - height: 225px; - margin-bottom: 12px; - position: relative; - - .empty-message-icon { - width: 100%; - height: 100%; - background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%); - border-radius: 12px; - position: relative; - - &::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 60px; - height: 60px; - background: #d0d0d0; - border-radius: 50%; - } - - &::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 30px; - height: 30px; - background: #a0a0a0; - border-radius: 50%; - } - } + &:active { + transform: scale(0.92); } - .empty-text { - font-family: 'PingFang SC'; - font-weight: 500; - font-size: 14px; - line-height: 1.71; - color: rgba(0, 0, 0, 0.85); + .button-icon { + width: 36px; + height: 36px; + position: relative; + + // 加号图标 - 竖线 + &::before { + content: ''; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 4px; + height: 25px; + background: #FFFFFF; + border-radius: 2px; + box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.2); + } + + // 加号图标 - 横线 + &::after { + content: ''; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 25px; + height: 4px; + background: #FFFFFF; + border-radius: 2px; + box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.2); + } } } } \ No newline at end of file diff --git a/src/other_pages/message/index.tsx b/src/other_pages/message/index.tsx index e4a6653..60441df 100644 --- a/src/other_pages/message/index.tsx +++ b/src/other_pages/message/index.tsx @@ -1,136 +1,226 @@ import { useState, useEffect } from "react"; -import { View, Text, ScrollView, Image } from "@tarojs/components"; +import { View, Text, Image, ScrollView } from "@tarojs/components"; import { Avatar } from "@nutui/nutui-react-taro"; import GuideBar from "@/components/GuideBar"; -import { withAuth } from "@/components"; +import { withAuth, EmptyState } from "@/components"; import noticeService from "@/services/noticeService"; -import Taro, { useRouter } from "@tarojs/taro"; +import { useUserInfo } from "@/store/userStore"; +import Taro from "@tarojs/taro"; import "./index.scss"; // 消息类型定义 interface MessageItem { id: string; - type: "system" | "user" | "like" | "comment" | "follow"; + notification_type: string; title: string; content: string; - time: string; - avatar?: string; - isRead: boolean; - hasAction?: boolean; - actionText?: string; + create_time: string; + is_read: number; + related_user_avatar?: string; + related_user_nickname?: string; + activity_image?: string; } +// 消息分类类型 +type MessageCategory = "comment" | "follow"; + const Message = () => { - const [activeTab] = useState<"all" | "like" | "comment" | "follow">("all"); + const userInfo = useUserInfo() as any; + const [activeTab, setActiveTab] = useState(null); const [messageList, setMessageList] = useState([]); + const [loading, setLoading] = useState(false); + const [reachedBottom, setReachedBottom] = useState(false); + + // 获取消息列表 + const getNoticeList = async () => { + if (loading) return; + setLoading(true); + try { + const res = await noticeService.getNotificationList({}); + if (res.code === 0) { + setMessageList(res.data.list || []); + } + } catch (e) { + Taro.showToast({ + title: "获取列表失败,请重试", + icon: "none", + duration: 2000, + }); + } finally { + setLoading(false); + } + }; useEffect(() => { - const getNoticeList = async () => { - try { - const res = await noticeService.getNotificationList({}); - if (res.code === 0) { - setMessageList(res.data.list); - } - } catch (e) { - Taro.showToast({ - title: "获取列表失败,请重试", - icon: "none", - duration: 2000, - }); - } finally { - } - }; getNoticeList(); }, []); - // 过滤消息 - const filteredMessages = messageList.filter((message) => { - if (activeTab === "all") return true; - return message.type === activeTab; - }); + // 过滤系统消息 + const filteredMessages = messageList; - // 渲染消息项 - const renderMessageItem = (message: MessageItem) => { - if (message.type === "system") { - return ( - - - {message.title} - {message.time} - - - {message.content} - - {message.hasAction && ( - - - - {message.actionText} - - - - )} - - ); + // 处理分类标签点击 + const handleTabClick = (tab: MessageCategory) => { + // 点击评论标签跳转到评论和回复页面 + if (tab === "comment") { + Taro.navigateTo({ + url: "/other_pages/comment_reply/index", + }); + return; } + + // 点击关注标签跳转到新增关注页面 + if (tab === "follow") { + Taro.navigateTo({ + url: "/other_pages/new_follow/index", + }); + return; + } + + setActiveTab(activeTab === tab ? null : tab); + }; - return ( - - - - - - - {message.title} - {message.time} - - - {message.content} - {!message.isRead && } - - - - ); + // 处理查看详情 + const handleViewDetail = (messageId: string) => { + console.log("查看详情:", messageId); + // TODO: 根据消息类型跳转到对应详情页 + }; + + // 处理滚动到底部 + const handleScrollToLower = () => { + if (!reachedBottom && filteredMessages.length > 0) { + setReachedBottom(true); + // 2秒后隐藏提示 + setTimeout(() => { + setReachedBottom(false); + }, 2000); + } + }; + + // 格式化时间显示 + const formatTime = (timeStr: string) => { + if (!timeStr) return ""; + const date = new Date(timeStr); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + + if (days === 0) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `今天 ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; + } else if (days === 1) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `昨天 ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; + } else if (days < 3) { + return `${days}天前`; + } else { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; + } }; return ( - {/* 导航栏 */} + {/* 顶部导航栏 */} 消息 - {/* 消息列表 */} - + {/* 分类标签 */} + + handleTabClick("comment")} + > + + 评论和回复 + + handleTabClick("follow")} + > + + 新增关注 + + + + {/* 系统消息卡片列表 */} + {filteredMessages.length > 0 ? ( - - {filteredMessages.map(renderMessageItem)} + + {filteredMessages.map((message) => ( + + + {message.title} + + + {formatTime(message.create_time)} + + + {message.content} + + + + handleViewDetail(message.id)}> + 查看详情 + + + + + + + + + ))} + {/* 到底了提示 */} + {filteredMessages.length > 0 && ( + + 到底了 + + )} ) : ( - - - - - 暂无消息 - + )} + {/* 悬浮新建按钮 */} + console.log("新建消息")}> + + + {/* 底部导航 */} ); }; -export default withAuth(Message); +export default withAuth(Message); \ No newline at end of file diff --git a/src/other_pages/new_follow/index.config.ts b/src/other_pages/new_follow/index.config.ts new file mode 100644 index 0000000..4626cb8 --- /dev/null +++ b/src/other_pages/new_follow/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationStyle: "custom", + navigationBarTitleText: "新增关注", +}); diff --git a/src/other_pages/new_follow/index.scss b/src/other_pages/new_follow/index.scss new file mode 100644 index 0000000..5e8dc98 --- /dev/null +++ b/src/other_pages/new_follow/index.scss @@ -0,0 +1,199 @@ +@use '~@/scss/images.scss' as img; + +.new-follow-container { + width: 100%; + height: 100vh; + box-sizing: border-box; + display: flex; + flex-direction: column; + background: #FFFFFF; + + // 顶部导航栏 + .navbar { + height: 100px; + background: #FFFFFF; + position: sticky; + top: 0; + z-index: 100; + + .navbar-content { + height: 56px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 15px; + margin-top: 44px; + position: relative; + + .back-button { + position: absolute; + left: 10px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + .back-icon { + width: 8px; + height: 16px; + position: relative; + + &::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); + } + } + } + + .navbar-title { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 20px; + line-height: 1.4; + letter-spacing: 0.019em; + color: #000000; + } + } + } + + // 关注列表滚动区域 + .follow-scroll { + flex: 1; + height: 100%; + } + + // 关注列表 + .follow-list { + display: flex; + flex-direction: column; + gap: 12px; + padding: 0 0 20px; + } + + // 关注项 + .follow-item { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding: 8px 20px; + + .follow-left { + display: flex; + align-items: center; + gap: 12px; + flex: 1; + min-width: 0; + cursor: pointer; + + .user-avatar { + width: 40px; + height: 40px; + border-radius: 999px; + flex-shrink: 0; + } + + .user-info { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-width: 0; + height: 40px; + justify-content: center; + + .user-nickname { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 14px; + line-height: 1.14; + color: rgba(0, 0, 0, 0.8); + } + + .user-signature { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.33; + color: rgba(60, 60, 67, 0.6); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .action-row { + display: flex; + align-items: center; + gap: 8px; + + .action-text, + .time-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.33; + color: rgba(60, 60, 67, 0.6); + } + } + } + } + + // 回关按钮 + .follow-button { + display: flex; + justify-content: center; + align-items: center; + padding: 4px 16px; + border: 0.5px solid #000000; + border-radius: 20px; + cursor: pointer; + flex-shrink: 0; + + &:active { + opacity: 0.8; + } + + &.mutual { + border-color: rgba(120, 120, 128, 0.12); + background: transparent; + } + + .button-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.33; + color: #000000; + } + + &.mutual .button-text { + color: rgba(0, 0, 0, 0.8); + } + } + } + + // 到底了提示 + .bottom-tip { + display: flex; + justify-content: center; + align-items: center; + padding: 24px 0 12px; + + .tip-text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.71; + color: rgba(0, 0, 0, 0.35); + } + } +} diff --git a/src/other_pages/new_follow/index.tsx b/src/other_pages/new_follow/index.tsx new file mode 100644 index 0000000..1d44b56 --- /dev/null +++ b/src/other_pages/new_follow/index.tsx @@ -0,0 +1,206 @@ +import { useState, useEffect } from "react"; +import { View, Text, ScrollView } from "@tarojs/components"; +import { Avatar } from "@nutui/nutui-react-taro"; +import { withAuth, EmptyState } from "@/components"; +import noticeService from "@/services/noticeService"; +import Taro from "@tarojs/taro"; +import "./index.scss"; + +// 关注项类型定义 +interface FollowItem { + id: string; + user_id: string; + user_avatar: string; + user_nickname: string; + user_signature?: string; + time: string; + is_mutual: boolean; // 是否互相关注 + is_read: number; +} + +const NewFollow = () => { + const [followList, setFollowList] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + getFollowList(); + }, []); + + // 获取新增关注列表 + const getFollowList = async () => { + if (loading) return; + + setLoading(true); + try { + const res = await noticeService.getNotificationList({ + notification_type: "follow", // 筛选关注类型 + }); + + if (res.code === 0) { + // 映射数据 + const mappedList = res.data.list.map((item: any) => ({ + id: item.id, + user_id: item.related_user_id || "", + user_avatar: item.related_user_avatar || "", + user_nickname: item.related_user_nickname || "匿名用户", + user_signature: item.related_user_signature || "", + time: item.created_at, + is_mutual: item.is_mutual_follow || false, + is_read: item.is_read, + })); + + setFollowList(mappedList); + } + } catch (e) { + Taro.showToast({ + title: "获取列表失败", + icon: "none", + duration: 2000, + }); + } finally { + setLoading(false); + } + }; + + // 格式化时间显示 + const formatTime = (timeStr: string) => { + if (!timeStr) return ""; + + const date = new Date(timeStr); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const hours = Math.floor(diff / (1000 * 60 * 60)); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + + if (hours < 24) { + return `${hours}小时前`; + } else if (days === 1) { + return "1天前"; + } else if (days < 7) { + return `${days}天前`; + } else { + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${month}月${day}日`; + } + }; + + // 处理回关 + const handleFollowBack = async (item: FollowItem) => { + if (item.is_mutual) { + // 已经互相关注,无需操作 + return; + } + + try { + // TODO: 调用关注接口 + // await userService.followUser({ user_id: item.user_id }); + + Taro.showToast({ + title: "关注成功", + icon: "success", + duration: 2000, + }); + + // 更新列表 + setFollowList(prevList => + prevList.map(followItem => + followItem.id === item.id + ? { ...followItem, is_mutual: true } + : followItem + ) + ); + } catch (e) { + Taro.showToast({ + title: "关注失败", + icon: "none", + duration: 2000, + }); + } + }; + + // 处理返回 + const handleBack = () => { + Taro.navigateBack(); + }; + + // 处理点击用户 + const handleUserClick = (userId: string) => { + Taro.navigateTo({ + url: `/user_pages/other/index?user_id=${userId}`, + }); + }; + + // 渲染关注项 + const renderFollowItem = (item: FollowItem) => { + return ( + + handleUserClick(item.user_id)}> + + + + {item.user_nickname} + + {item.user_signature ? ( + {item.user_signature} + ) : ( + + 开始关注你了,期待你的回关 + {formatTime(item.time)} + + )} + + + + handleFollowBack(item)} + > + {item.is_mutual ? "互相关注" : "回关"} + + + ); + }; + + return ( + + {/* 顶部导航栏 */} + + + + + + 新增关注 + + + + {/* 关注列表 */} + + {followList.length > 0 ? ( + + {followList.map(renderFollowItem)} + + {/* 到底了提示 */} + + 到底了 + + + ) : ( + + )} + + + ); +}; + +export default withAuth(NewFollow); diff --git a/src/scss/qweather-icons/fonts/qweather-icons.ttf b/src/scss/qweather-icons/fonts/qweather-icons.ttf new file mode 100644 index 0000000..2a47642 Binary files /dev/null and b/src/scss/qweather-icons/fonts/qweather-icons.ttf differ diff --git a/src/scss/qweather-icons/fonts/qweather-icons.woff2 b/src/scss/qweather-icons/fonts/qweather-icons.woff2 new file mode 100644 index 0000000..e1f8f48 Binary files /dev/null and b/src/scss/qweather-icons/fonts/qweather-icons.woff2 differ diff --git a/src/scss/qweather-icons/qweather-icons.css b/src/scss/qweather-icons/qweather-icons.css new file mode 100644 index 0000000..6dc64d9 --- /dev/null +++ b/src/scss/qweather-icons/qweather-icons.css @@ -0,0 +1,92 @@ + + +@font-face { + font-family: "qweather-icons"; + src: url("./fonts/qweather-icons.woff2?3696017a726a77099c2617f87a3367ac") format("woff2") + +} + +[class^="qi-"]::before, +[class*=" qi-"]::before { + display: inline-block; + font-family: "qweather-icons" !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.qi-100::before { content: "\f101"; } +.qi-101::before { content: "\f102"; } +.qi-102::before { content: "\f103"; } +.qi-103::before { content: "\f104"; } +.qi-104::before { content: "\f105"; } +.qi-150::before { content: "\f106"; } +.qi-151::before { content: "\f107"; } +.qi-152::before { content: "\f108"; } +.qi-153::before { content: "\f109"; } +.qi-300::before { content: "\f10a"; } +.qi-301::before { content: "\f10b"; } +.qi-302::before { content: "\f10c"; } +.qi-303::before { content: "\f10d"; } +.qi-304::before { content: "\f10e"; } +.qi-305::before { content: "\f10f"; } +.qi-306::before { content: "\f110"; } +.qi-307::before { content: "\f111"; } +.qi-308::before { content: "\f112"; } +.qi-309::before { content: "\f113"; } +.qi-310::before { content: "\f114"; } +.qi-311::before { content: "\f115"; } +.qi-312::before { content: "\f116"; } +.qi-313::before { content: "\f117"; } +.qi-314::before { content: "\f118"; } +.qi-315::before { content: "\f119"; } +.qi-316::before { content: "\f11a"; } +.qi-317::before { content: "\f11b"; } +.qi-318::before { content: "\f11c"; } +.qi-350::before { content: "\f11d"; } +.qi-351::before { content: "\f11e"; } +.qi-399::before { content: "\f11f"; } +.qi-400::before { content: "\f120"; } +.qi-401::before { content: "\f121"; } +.qi-402::before { content: "\f122"; } +.qi-403::before { content: "\f123"; } +.qi-404::before { content: "\f124"; } +.qi-405::before { content: "\f125"; } +.qi-406::before { content: "\f126"; } +.qi-407::before { content: "\f127"; } +.qi-408::before { content: "\f128"; } +.qi-409::before { content: "\f129"; } +.qi-410::before { content: "\f12a"; } +.qi-456::before { content: "\f12b"; } +.qi-457::before { content: "\f12c"; } +.qi-499::before { content: "\f12d"; } +.qi-500::before { content: "\f12e"; } +.qi-501::before { content: "\f12f"; } +.qi-502::before { content: "\f130"; } +.qi-503::before { content: "\f131"; } +.qi-504::before { content: "\f132"; } +.qi-507::before { content: "\f133"; } +.qi-508::before { content: "\f134"; } +.qi-509::before { content: "\f135"; } +.qi-510::before { content: "\f136"; } +.qi-511::before { content: "\f137"; } +.qi-512::before { content: "\f138"; } +.qi-513::before { content: "\f139"; } +.qi-514::before { content: "\f13a"; } +.qi-515::before { content: "\f13b"; } +.qi-800::before { content: "\f13c"; } +.qi-801::before { content: "\f13d"; } +.qi-802::before { content: "\f13e"; } +.qi-803::before { content: "\f13f"; } +.qi-804::before { content: "\f140"; } +.qi-805::before { content: "\f141"; } +.qi-806::before { content: "\f142"; } +.qi-807::before { content: "\f143"; } +.qi-900::before { content: "\f144"; } +.qi-901::before { content: "\f145"; } +.qi-999::before { content: "\f146"; } diff --git a/src/static/message/ar-right.svg b/src/static/message/ar-right.svg new file mode 100644 index 0000000..412901b --- /dev/null +++ b/src/static/message/ar-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/message/comment-icon.svg b/src/static/message/comment-icon.svg new file mode 100644 index 0000000..69a4659 --- /dev/null +++ b/src/static/message/comment-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/static/message/emi.svg b/src/static/message/emi.svg new file mode 100644 index 0000000..6a526d1 --- /dev/null +++ b/src/static/message/emi.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/message/follow-icon.svg b/src/static/message/follow-icon.svg new file mode 100644 index 0000000..b6fffff --- /dev/null +++ b/src/static/message/follow-icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + +