添加红点
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) => {
|
||||
<View
|
||||
className={`guide-bar-pages-item ${currentPage === item.code ? "guide-bar-pages-item-active" : ""}`}
|
||||
onClick={() => handlePageChange(item.code)}
|
||||
key={item.code}
|
||||
>
|
||||
<Text>{item.text}</Text>
|
||||
{/* {item.code === "message" && hasReddot && (
|
||||
<View className="reddot"></View>
|
||||
)} */}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@@ -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")}
|
||||
>
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/comment-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<View className="tab-icon-wrapper">
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/comment-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
{commentUnreadCount > 0 && (
|
||||
<View className="badge">
|
||||
{commentUnreadCount > 99 ? '99+' : commentUnreadCount}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text className="tab-text">评论和回复</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`tab-item ${activeTab === "follow" ? "active" : ""}`}
|
||||
onClick={() => handleTabClick("follow")}
|
||||
>
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/follow-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<View className="tab-icon-wrapper">
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/follow-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
{followUnreadCount > 0 && (
|
||||
<View className="badge">
|
||||
{followUnreadCount > 99 ? '99+' : followUnreadCount}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text className="tab-text">新增关注</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")}
|
||||
>
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/comment-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<View className="tab-icon-wrapper">
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/comment-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
{commentUnreadCount > 0 && (
|
||||
<View className="badge">
|
||||
{commentUnreadCount > 99 ? '99+' : commentUnreadCount}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text className="tab-text">评论和回复</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`tab-item ${activeTab === "follow" ? "active" : ""}`}
|
||||
onClick={() => handleTabClick("follow")}
|
||||
>
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/follow-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<View className="tab-icon-wrapper">
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/follow-icon.svg')}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
{followUnreadCount > 0 && (
|
||||
<View className="badge">
|
||||
{followUnreadCount > 99 ? '99+' : followUnreadCount}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text className="tab-text">新增关注</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
40
src/services/messageService.ts
Normal file
40
src/services/messageService.ts
Normal file
@@ -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<ApiResponse<ReddotInfo>> {
|
||||
return httpService.post('/message/reddot_info', {}, { showLoading: false });
|
||||
}
|
||||
|
||||
// 标记消息已读
|
||||
async markAsRead(type: MarkAsReadType, ids?: number[]): Promise<ApiResponse<MarkAsReadResponse>> {
|
||||
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();
|
||||
Reference in New Issue
Block a user