diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..e5cefe4
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,10 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(dir /s /b *.json)",
+ "Bash(findstr:*)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/_doc/前端红点功能接口文档.md b/_doc/前端红点功能接口文档.md
new file mode 100644
index 0000000..5250c2b
--- /dev/null
+++ b/_doc/前端红点功能接口文档.md
@@ -0,0 +1,568 @@
+# 前端红点功能接口文档
+
+## 一、功能概述
+
+消息红点功能用于提示用户有未读的评论/回复和新增关注消息。
+
+**红点显示条件**:
+- 有别人回复给我的评论/回复未读
+- 有新用户关注我未读
+
+---
+
+## 二、接口列表
+
+| 接口 | 用途 |
+|------|------|
+| `POST /api/message/reddot_info` | 获取红点信息(数量统计) |
+| `POST /api/message/mark_as_read` | 标记消息已读(统一接口) |
+| `POST /api/comments/my_activities` | 获取评论/回复消息列表 |
+| `POST /api/user_follow/new_fans_list` | 获取新增关注列表 |
+
+---
+
+## 三、核心接口详情
+
+### 1. 获取红点信息 ⭐
+
+**接口**: `POST /api/message/reddot_info`
+
+**请求参数**: 无
+
+**响应示例**:
+```json
+{
+ "code": 200,
+ "message": "获取成功",
+ "data": {
+ "comment_unread_count": 5, // 评论/回复未读数量
+ "follow_unread_count": 3, // 新增关注未读数量
+ "total_unread_count": 8, // 总未读数量
+ "has_reddot": true // 是否显示红点
+ }
+}
+```
+
+**字段说明**:
+- `comment_unread_count`: Number,评论/回复未读数量
+- `follow_unread_count`: Number,新增关注未读数量
+- `total_unread_count`: Number,总未读数量
+- `has_reddot`: Boolean,是否显示红点
+ - `true`: 有未读消息,显示红点
+ - `false`: 无未读消息,隐藏红点
+
+**使用场景**:
+- TabBar 显示红点和未读数
+- 消息页面显示各类消息的未读数
+- 应用启动时检查红点状态
+
+---
+
+### 2. 标记消息已读 ⭐
+
+**接口**: `POST /api/message/mark_as_read`
+
+**请求参数**:
+```json
+{
+ "type": "comment", // 消息类型: comment-评论, follow-关注, all-全部
+ "ids": [123, 456, 789] // 消息ID数组(type为all时可不传)
+}
+```
+
+**type 参数说明**:
+- `"comment"`: 标记指定评论/回复为已读,需传 `ids`(评论ID数组)
+- `"follow"`: 标记指定关注为已读,需传 `ids`(关注者用户ID数组)
+- `"all"`: 标记所有未读消息为已读,不需要传 `ids`
+
+**响应示例**:
+```json
+{
+ "code": 200,
+ "message": "获取成功",
+ "data": {
+ "updated_count": 8, // 总更新数量
+ "detail": {
+ "comment_count": 5, // 评论更新数量
+ "follow_count": 3 // 关注更新数量
+ },
+ "message": "标记成功"
+ }
+}
+```
+
+**使用示例**:
+
+1. 标记指定评论为已读:
+```json
+{
+ "type": "comment",
+ "ids": [123, 456]
+}
+```
+
+2. 标记指定关注为已读:
+```json
+{
+ "type": "follow",
+ "ids": [789, 012]
+}
+```
+
+3. 标记全部消息为已读:
+```json
+{
+ "type": "all"
+}
+```
+
+---
+
+## 四、辅助接口
+
+### 3. 获取评论/回复消息列表
+
+**接口**: `POST /api/comments/my_activities`
+
+**请求参数**:
+```json
+{
+ "page": 1,
+ "pageSize": 20
+}
+```
+
+**响应示例**:
+```json
+{
+ "code": 200,
+ "message": "获取成功",
+ "data": {
+ "rows": [
+ {
+ "id": 456,
+ "type": "reply",
+ "activity_type": "received_reply",
+ "content": "回复内容",
+ "create_time": "2025-11-20 10:00:00",
+ "is_read": 0, // 👈 0-未读, 1-已读
+ "user": {
+ "id": 789,
+ "nickname": "回复者昵称",
+ "avatar_url": "头像地址"
+ },
+ "game": {
+ "id": 101,
+ "title": "球局标题"
+ }
+ }
+ ],
+ "count": 50
+ }
+}
+```
+
+**字段说明**:
+- `is_read`: 0-未读, 1-已读
+- `activity_type`:
+ - `"my_activity"` - 我发表的
+ - `"received_reply"` - 别人回复我的(需要处理的)
+
+---
+
+### 4. 获取新增关注列表
+
+**接口**: `POST /api/user_follow/new_fans_list`
+
+**请求参数**:
+```json
+{
+ "page": 1,
+ "page_size": 20
+}
+```
+
+**响应示例**:
+```json
+{
+ "code": 200,
+ "message": "获取成功",
+ "data": {
+ "list": [
+ {
+ "id": 123,
+ "nickname": "粉丝昵称",
+ "avatar_url": "头像地址",
+ "follow_time": "2025-11-20 09:00:00",
+ "is_mutual": false,
+ "is_read": 0 // 👈 0-未读, 1-已读
+ }
+ ],
+ "total": 30
+ }
+}
+```
+
+---
+
+## 五、前端使用示例
+
+### 1. 显示 TabBar 红点
+
+```javascript
+// app.js 或全局方法
+async function checkAndShowReddot() {
+ const res = await request({
+ url: '/api/message/reddot_info',
+ method: 'POST'
+ })
+
+ const { has_reddot, total_unread_count } = res.data
+
+ if (has_reddot) {
+ // 显示红点和数字
+ wx.setTabBarBadge({
+ index: 2, // 消息Tab的索引
+ text: total_unread_count > 99 ? '99+' : String(total_unread_count)
+ })
+ } else {
+ // 隐藏红点
+ wx.removeTabBarBadge({
+ index: 2
+ })
+ }
+}
+
+// App 启动时检查
+App({
+ onLaunch() {
+ this.checkReddot()
+ },
+
+ checkReddot() {
+ checkAndShowReddot()
+ }
+})
+```
+
+---
+
+### 2. 消息页面显示分类红点
+
+```javascript
+// pages/message/message.js
+Page({
+ data: {
+ commentUnreadCount: 0,
+ followUnreadCount: 0
+ },
+
+ onShow() {
+ this.loadReddotInfo()
+ },
+
+ async loadReddotInfo() {
+ const res = await request({
+ url: '/api/message/reddot_info',
+ method: 'POST'
+ })
+
+ this.setData({
+ commentUnreadCount: res.data.comment_unread_count,
+ followUnreadCount: res.data.follow_unread_count
+ })
+ }
+})
+```
+
+```html
+
+
+
+
+
+
+
+
+```
+
+---
+
+### 3. 评论消息列表(进入时自动标记已读)
+
+```javascript
+// pages/message/comment-list.js
+Page({
+ data: {
+ commentList: []
+ },
+
+ async onLoad() {
+ await this.loadComments()
+ await this.markAllAsRead()
+ },
+
+ // 加载评论列表
+ async loadComments() {
+ const res = await request({
+ url: '/api/comments/my_activities',
+ method: 'POST',
+ data: { page: 1, pageSize: 20 }
+ })
+
+ // 筛选出别人回复给我的
+ const receivedReplies = res.data.rows.filter(
+ item => item.activity_type === 'received_reply'
+ )
+
+ this.setData({ commentList: receivedReplies })
+ },
+
+ // 标记所有评论为已读
+ async markAllAsRead() {
+ const unreadIds = this.data.commentList
+ .filter(item => item.is_read === 0)
+ .map(item => item.id)
+
+ if (unreadIds.length > 0) {
+ await request({
+ url: '/api/message/mark_as_read',
+ method: 'POST',
+ data: {
+ type: 'comment',
+ ids: unreadIds
+ }
+ })
+ }
+ },
+
+ onUnload() {
+ // 离开页面时刷新红点
+ getApp().checkReddot()
+ }
+})
+```
+
+---
+
+### 4. 关注消息列表(进入时自动标记已读)
+
+```javascript
+// pages/message/follow-list.js
+Page({
+ data: {
+ fansList: []
+ },
+
+ async onLoad() {
+ await this.loadFans()
+ await this.markAllAsRead()
+ },
+
+ async loadFans() {
+ const res = await request({
+ url: '/api/user_follow/new_fans_list',
+ method: 'POST',
+ data: { page: 1, page_size: 20 }
+ })
+
+ this.setData({ fansList: res.data.list })
+ },
+
+ async markAllAsRead() {
+ const unreadFanIds = this.data.fansList
+ .filter(item => item.is_read === 0)
+ .map(item => item.id)
+
+ if (unreadFanIds.length > 0) {
+ await request({
+ url: '/api/message/mark_as_read',
+ method: 'POST',
+ data: {
+ type: 'follow',
+ ids: unreadFanIds
+ }
+ })
+ }
+ },
+
+ onUnload() {
+ getApp().checkReddot()
+ }
+})
+```
+
+---
+
+### 5. 一键清空所有未读
+
+```javascript
+// 清空所有未读消息
+async function markAllMessagesAsRead() {
+ wx.showLoading({ title: '处理中...' })
+
+ try {
+ await request({
+ url: '/api/message/mark_as_read',
+ method: 'POST',
+ data: { type: 'all' }
+ })
+
+ wx.showToast({ title: '已全部标记为已读', icon: 'success' })
+
+ // 刷新红点
+ getApp().checkReddot()
+
+ } catch (err) {
+ wx.showToast({ title: '操作失败', icon: 'none' })
+ } finally {
+ wx.hideLoading()
+ }
+}
+```
+
+---
+
+## 六、最佳实践
+
+### 1. 红点刷新时机
+
+```javascript
+// 推荐刷新时机
+const REFRESH_TIMING = {
+ onLaunch: true, // App启动时
+ onShow: true, // App从后台进入前台
+ onTabSwitch: true, // 切换到消息Tab
+ afterMarkRead: true, // 标记已读后
+ onPullRefresh: true // 下拉刷新
+}
+```
+
+### 2. 防抖优化
+
+```javascript
+// utils/debounce.js
+let timer = null
+export function debounceCheckReddot() {
+ if (timer) clearTimeout(timer)
+ timer = setTimeout(() => {
+ getApp().checkReddot()
+ }, 300)
+}
+```
+
+### 3. 错误处理
+
+```javascript
+async function safeMarkAsRead(type, ids) {
+ try {
+ await request({
+ url: '/api/message/mark_as_read',
+ method: 'POST',
+ data: { type, ids }
+ })
+ } catch (err) {
+ // 标记已读失败不影响用户体验,静默处理
+ console.error('标记已读失败:', err)
+ }
+}
+```
+
+---
+
+## 七、完整流程图
+
+```
+用户打开App
+ ↓
+调用 /message/reddot_info
+ ↓
+获取红点数据
+ ↓
+┌─────────────────┐
+│ has_reddot? │
+└────┬────────┬───┘
+ │ │
+ true false
+ ↓ ↓
+显示红点 隐藏红点
+带数字
+ │
+ │ 用户点击
+ ↓
+进入消息页面
+ ↓
+显示各分类未读数
+ │
+ │ 点击某分类
+ ↓
+加载消息列表
+ ↓
+标记已读
+ ↓
+┌─────────────────┐
+│ 选择标记方式 │
+└─┬─────────┬─────┘
+ │ │
+按ID 全部
+标记 标记
+ ↓ ↓
+调用 mark_as_read
+type=comment/follow
+ids=[...]
+ │ │
+ └────┬────┘
+ ↓
+ 标记成功
+ ↓
+ 刷新红点
+```
+
+---
+
+## 八、接口对比说明
+
+### 旧方案 vs 新方案
+
+| 功能 | 旧方案 | 新方案 |
+|------|--------|--------|
+| 获取红点 | `/api/user/detail` | `/api/message/reddot_info` |
+| 标记评论已读 | `/api/comments/mark_as_read` | `/api/message/mark_as_read` (type=comment) |
+| 标记关注已读 | `/api/user_follow/mark_as_read` | `/api/message/mark_as_read` (type=follow) |
+| 标记全部已读 | ❌ 无 | `/api/message/mark_as_read` (type=all) |
+
+**新方案优势**:
+- ✅ 统一的接口设计
+- ✅ 减少接口数量
+- ✅ 支持一键清空所有未读
+- ✅ 返回详细的未读数统计
+- ✅ 更清晰的职责划分
+
+---
+
+## 九、注意事项
+
+1. **权限控制**: 只能标记属于自己的消息
+2. **分页处理**: 翻页时注意标记已读
+3. **实时性**: 标记后刷新红点状态
+4. **错误处理**: 标记失败可静默处理
+5. **type 参数**: 必须是 `comment`、`follow` 或 `all`
+
+---
+
+## 十、联系后端
+
+**相关文件**:
+- 消息接口: `api/controller_front/msg_message.js` ⭐
+- 评论接口: `api/controller_front/gme_comments.js`
+- 关注接口: `api/controller_front/user_follow.js`
diff --git a/src/components/GuideBar/index.scss b/src/components/GuideBar/index.scss
index bafea3e..020cc18 100644
--- a/src/components/GuideBar/index.scss
+++ b/src/components/GuideBar/index.scss
@@ -33,6 +33,7 @@
backdrop-filter: blur(16px);
&-item {
+ position: relative;
display: flex;
width: 76px;
height: 48px;
@@ -46,6 +47,21 @@
font-style: normal;
font-weight: 600;
line-height: 20px; /* 125% */
+
+ .reddot {
+ position: absolute;
+ top: 10px;
+ right: 16px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ // padding: 0 2px;
+ width: 8px;
+ height: 8px;
+ background: #FF2541;
+ border-radius: 10px;
+ }
}
&-item-active {
diff --git a/src/components/GuideBar/index.tsx b/src/components/GuideBar/index.tsx
index f5248ba..b41a269 100644
--- a/src/components/GuideBar/index.tsx
+++ b/src/components/GuideBar/index.tsx
@@ -1,12 +1,36 @@
+import { useState, useEffect } from "react";
import { View, Text } from "@tarojs/components";
-import Taro from "@tarojs/taro";
+import Taro, { useDidShow } from "@tarojs/taro";
import { redirectTo } from "@/utils/navigation";
+import messageService from "@/services/messageService";
import "./index.scss";
import PublishMenu from "../PublishMenu";
export type currentPageType = "games" | "message" | "personal";
const GuideBar = (props) => {
const { currentPage, guideBarClassName, onPublishMenuVisibleChange, onTabChange } = props;
+ const [hasReddot, setHasReddot] = useState(false);
+
+ // 获取红点状态
+ const checkReddot = async () => {
+ try {
+ const res = await messageService.getReddotInfo();
+ if (res.code === 0) {
+ setHasReddot(res.data.has_reddot || false);
+ }
+ } catch (e) {
+ console.error("获取红点状态失败:", e);
+ }
+ };
+
+ useEffect(() => {
+ checkReddot();
+ }, []);
+
+ // 每次页面显示时刷新红点状态
+ useDidShow(() => {
+ checkReddot();
+ });
const guideItems = [
{
@@ -64,8 +88,12 @@ const GuideBar = (props) => {
handlePageChange(item.code)}
+ key={item.code}
>
{item.text}
+ {/* {item.code === "message" && hasReddot && (
+
+ )} */}
))}
diff --git a/src/main_pages/components/MessagePageContent.tsx b/src/main_pages/components/MessagePageContent.tsx
index 3762905..dbd0563 100644
--- a/src/main_pages/components/MessagePageContent.tsx
+++ b/src/main_pages/components/MessagePageContent.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import { View, Text, Image, ScrollView } from "@tarojs/components";
import { EmptyState } from "@/components";
import noticeService from "@/services/noticeService";
+import messageService from "@/services/messageService";
import { formatRelativeTime } from "@/utils/timeUtils";
import Taro from "@tarojs/taro";
import { useGlobalState } from "@/store/global";
@@ -32,6 +33,8 @@ const MessagePageContent = () => {
const [loading, setLoading] = useState(false);
const [reachedBottom, setReachedBottom] = useState(false);
const [refreshing, setRefreshing] = useState(false);
+ const [commentUnreadCount, setCommentUnreadCount] = useState(0);
+ const [followUnreadCount, setFollowUnreadCount] = useState(0);
const getNoticeList = async () => {
if (loading) return;
@@ -52,8 +55,22 @@ const MessagePageContent = () => {
}
};
+ // 获取红点信息
+ const getReddotInfo = async () => {
+ try {
+ const res = await messageService.getReddotInfo();
+ if (res.code === 0) {
+ setCommentUnreadCount(res.data.comment_unread_count || 0);
+ setFollowUnreadCount(res.data.follow_unread_count || 0);
+ }
+ } catch (e) {
+ console.error("获取红点信息失败:", e);
+ }
+ };
+
useEffect(() => {
getNoticeList();
+ getReddotInfo();
}, []);
const filteredMessages = messageList;
@@ -127,22 +144,36 @@ const MessagePageContent = () => {
className={`tab-item ${activeTab === "comment" ? "active" : ""}`}
onClick={() => handleTabClick("comment")}
>
-
+
+
+ {commentUnreadCount > 0 && (
+
+ {commentUnreadCount > 99 ? '99+' : commentUnreadCount}
+
+ )}
+
评论和回复
handleTabClick("follow")}
>
-
+
+
+ {followUnreadCount > 0 && (
+
+ {followUnreadCount > 99 ? '99+' : followUnreadCount}
+
+ )}
+
新增关注
diff --git a/src/other_pages/comment_reply/index.tsx b/src/other_pages/comment_reply/index.tsx
index e7bdc41..d13bddc 100644
--- a/src/other_pages/comment_reply/index.tsx
+++ b/src/other_pages/comment_reply/index.tsx
@@ -3,6 +3,7 @@ import { View, Text, ScrollView, Image, Input } from "@tarojs/components";
import { withAuth, EmptyState, GeneralNavbar } from "@/components";
import { useGlobalState } from "@/store/global";
import commentService, { CommentActivity } from "@/services/commentService";
+import messageService from "@/services/messageService";
import { formatShortRelativeTime } from "@/utils/timeUtils";
import Taro from "@tarojs/taro";
import "./index.scss";
@@ -69,6 +70,18 @@ const CommentReply = () => {
}));
setCommentList(mappedList);
+
+ // 获取未读评论ID并标记已读
+ const unreadIds = res.data.rows
+ .filter((item: any) => item.is_read === 0 && item.activity_type === 'received_reply')
+ .map((item: any) => item.id);
+
+ if (unreadIds.length > 0) {
+ // 使用统一接口标记已读
+ messageService.markAsRead('comment', unreadIds).catch(e => {
+ console.error("标记评论已读失败:", e);
+ });
+ }
}
} catch (e) {
Taro.showToast({
diff --git a/src/other_pages/message/index.scss b/src/other_pages/message/index.scss
index 4a43415..f03a5a2 100644
--- a/src/other_pages/message/index.scss
+++ b/src/other_pages/message/index.scss
@@ -26,6 +26,40 @@
align-items: center;
width: 161px;
+ .tab-icon-wrapper {
+ position: relative;
+ width: 56px;
+ height: 56px;
+
+ .tab-icon {
+ width: 56px;
+ height: 56px;
+ border-radius: 56px;
+ transition: all 0.3s;
+ }
+
+ .badge {
+ position: absolute;
+ top: -4px;
+ right: -8px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 0 4.5px;
+ min-width: 20px;
+ height: 20px;
+ background: #FF2541;
+ border-radius: 10px;
+ font-family: "PingFang SC";
+ font-weight: 600;
+ font-size: 10px;
+ line-height: 20px;
+ color: #ffffff;
+ text-align: center;
+ box-sizing: border-box;
+ }
+ }
.tab-icon {
width: 56px;
diff --git a/src/other_pages/message/index.tsx b/src/other_pages/message/index.tsx
index db5e9bc..80d0963 100644
--- a/src/other_pages/message/index.tsx
+++ b/src/other_pages/message/index.tsx
@@ -3,8 +3,9 @@ import { View, Text, Image, ScrollView } from "@tarojs/components";
import GuideBar from "@/components/GuideBar";
import { withAuth, EmptyState, GeneralNavbar } from "@/components";
import noticeService from "@/services/noticeService";
+import messageService from "@/services/messageService";
import { formatRelativeTime } from "@/utils/timeUtils";
-import Taro from "@tarojs/taro";
+import Taro, { useDidShow } from "@tarojs/taro";
import "./index.scss";
// 消息类型定义
@@ -30,6 +31,8 @@ const Message = () => {
const [loading, setLoading] = useState(false);
const [reachedBottom, setReachedBottom] = useState(false);
const [refreshing, setRefreshing] = useState(false);
+ const [commentUnreadCount, setCommentUnreadCount] = useState(0);
+ const [followUnreadCount, setFollowUnreadCount] = useState(0);
// 获取消息列表
const getNoticeList = async () => {
@@ -51,10 +54,29 @@ const Message = () => {
}
};
+ // 获取红点信息
+ const getReddotInfo = async () => {
+ try {
+ const res = await messageService.getReddotInfo();
+ if (res.code === 0) {
+ setCommentUnreadCount(res.data.comment_unread_count || 0);
+ setFollowUnreadCount(res.data.follow_unread_count || 0);
+ }
+ } catch (e) {
+ console.error("获取红点信息失败:", e);
+ }
+ };
+
useEffect(() => {
getNoticeList();
+ getReddotInfo();
}, []);
+ // 每次页面显示时刷新红点信息
+ useDidShow(() => {
+ getReddotInfo();
+ });
+
// 过滤系统消息
const filteredMessages = messageList;
@@ -140,22 +162,36 @@ const Message = () => {
className={`tab-item ${activeTab === "comment" ? "active" : ""}`}
onClick={() => handleTabClick("comment")}
>
-
+
+
+ {commentUnreadCount > 0 && (
+
+ {commentUnreadCount > 99 ? '99+' : commentUnreadCount}
+
+ )}
+
评论和回复
handleTabClick("follow")}
>
-
+
+
+ {followUnreadCount > 0 && (
+
+ {followUnreadCount > 99 ? '99+' : followUnreadCount}
+
+ )}
+
新增关注
diff --git a/src/other_pages/new_follow/index.tsx b/src/other_pages/new_follow/index.tsx
index 3ebed4a..5d38102 100644
--- a/src/other_pages/new_follow/index.tsx
+++ b/src/other_pages/new_follow/index.tsx
@@ -3,6 +3,7 @@ import { View, Text, ScrollView, Image } from "@tarojs/components";
import { withAuth, EmptyState, GeneralNavbar } from "@/components";
import { useGlobalState } from "@/store/global";
import FollowService from "@/services/followService";
+import messageService from "@/services/messageService";
import { formatShortRelativeTime } from "@/utils/timeUtils";
import Taro from "@tarojs/taro";
import "./index.scss";
@@ -34,11 +35,11 @@ const NewFollow = () => {
// 获取新增关注列表
const getFollowList = async () => {
if (loading) return;
-
+
setLoading(true);
try {
const res = await FollowService.get_new_fans_list(1, 20);
-
+
if (res.list && res.list.length > 0) {
// 映射数据
const mappedList = res.list.map((item: any) => ({
@@ -52,8 +53,20 @@ const NewFollow = () => {
time: item.follow_time,
is_mutual: item.is_mutual || false,
}));
-
+
setFollowList(mappedList);
+
+ // 获取未读关注ID并标记已读
+ const unreadFanIds = res.list
+ .filter((item: any) => item.is_read === 0)
+ .map((item: any) => item.id);
+
+ if (unreadFanIds.length > 0) {
+ // 使用统一接口标记已读
+ messageService.markAsRead('follow', unreadFanIds).catch(e => {
+ console.error("标记关注已读失败:", e);
+ });
+ }
} else {
// 如果没有数据,设置为空数组以显示空状态
setFollowList([]);
diff --git a/src/services/messageService.ts b/src/services/messageService.ts
new file mode 100644
index 0000000..142dd23
--- /dev/null
+++ b/src/services/messageService.ts
@@ -0,0 +1,40 @@
+import httpService, { ApiResponse } from './httpService';
+
+// 红点信息响应接口
+export interface ReddotInfo {
+ comment_unread_count: number; // 评论/回复未读数量
+ follow_unread_count: number; // 新增关注未读数量
+ total_unread_count: number; // 总未读数量
+ has_reddot: boolean; // 是否显示红点
+}
+
+// 标记已读类型
+export type MarkAsReadType = 'comment' | 'follow' | 'all';
+
+// 标记已读响应
+export interface MarkAsReadResponse {
+ updated_count: number;
+ detail: {
+ comment_count: number;
+ follow_count: number;
+ };
+ message: string;
+}
+
+class MessageService {
+ // 获取红点信息
+ async getReddotInfo(): Promise> {
+ return httpService.post('/message/reddot_info', {}, { showLoading: false });
+ }
+
+ // 标记消息已读
+ async markAsRead(type: MarkAsReadType, ids?: number[]): Promise> {
+ const data: { type: MarkAsReadType; ids?: number[] } = { type };
+ if (ids && ids.length > 0) {
+ data.ids = ids;
+ }
+ return httpService.post('/message/mark_as_read', data, { showLoading: false });
+ }
+}
+
+export default new MessageService();