新增好友添加任务列表API,优化NavCommon组件,移除未使用的消息处理逻辑,简化代码结构,提升可读性和维护性。

This commit is contained in:
超级老白兔
2025-11-25 15:36:43 +08:00
parent d8d1003d7a
commit 78de6c92b7
3 changed files with 440 additions and 207 deletions

View File

@@ -0,0 +1,431 @@
import React, { useEffect, useState } from "react";
import { Drawer, Avatar, Space, Button, Badge, Empty, Tabs, Tag } from "antd";
import { BellOutlined } from "@ant-design/icons";
import {
noticeList,
readMessage,
readAll,
friendRequestList as fetchFriendRequestListApi,
} from "./api";
import styles from "./index.module.scss";
interface MessageItem {
id: number;
type: number;
companyId: number;
userId: number;
bindId: number;
title: string;
message: string;
isRead: number;
createTime: string;
readTime: string;
friendData: {
nickname: string;
avatar: string;
};
}
interface FriendRequestItem {
taskId: number;
phone: string;
wechatId: string;
adder?: {
avatar?: string;
nickname?: string;
username?: string;
accountNickname?: string;
accountRealName?: string;
};
status?: {
code?: number;
text?: string;
};
time?: {
addTime?: string;
addTimeStamp?: number;
updateTime?: string;
updateTimeStamp?: number;
passTime?: string;
passTimeStamp?: number;
};
friend?: {
nickname?: string;
isPassed?: boolean;
};
other?: {
msgContent?: string;
remark?: string;
from?: string;
labels?: string[];
};
}
const DEFAULT_QUERY = { page: 1, limit: 20 };
const Notice: React.FC = () => {
const [messageDrawerVisible, setMessageDrawerVisible] = useState(false);
const [activeTab, setActiveTab] = useState("messages");
const [messageList, setMessageList] = useState<MessageItem[]>([]);
const [messageCount, setMessageCount] = useState(0);
const [loading, setLoading] = useState(false);
const [friendRequestList, setFriendRequestList] = useState<
FriendRequestItem[]
>([]);
const [friendRequestLoading, setFriendRequestLoading] = useState(false);
const fetchMessageList = async () => {
try {
setLoading(true);
const response = await noticeList(DEFAULT_QUERY);
if (response?.list) {
setMessageList(response.list);
const unreadCount = response.list.filter(
(item: MessageItem) => item.isRead === 0,
).length;
setMessageCount(unreadCount);
}
} catch (error) {
console.error("获取消息列表失败:", error);
} finally {
setLoading(false);
}
};
const refreshUnreadCount = async () => {
try {
const response = await noticeList(DEFAULT_QUERY);
if (response && typeof response.noRead === "number") {
setMessageCount(response.noRead);
}
} catch (error) {
console.error("获取未读消息数失败:", error);
}
};
useEffect(() => {
fetchMessageList();
const timer = window.setInterval(refreshUnreadCount, 30 * 1000);
return () => {
window.clearInterval(timer);
};
}, []);
const handleMessageClick = () => {
setMessageDrawerVisible(true);
fetchMessageList();
fetchFriendRequestList();
};
const handleTabChange = (key: string) => {
setActiveTab(key);
if (key === "friendRequests") {
fetchFriendRequestList();
}
};
const handleMessageDrawerClose = () => {
setMessageDrawerVisible(false);
};
const handleReadMessage = async (messageId: number) => {
try {
await readMessage({ id: messageId });
setMessageList(prev => {
const updated = prev.map(item =>
item.id === messageId ? { ...item, isRead: 1 } : item,
);
const unreadCount = updated.filter(item => item.isRead === 0).length;
setMessageCount(unreadCount);
return updated;
});
} catch (error) {
console.error("标记消息已读失败:", error);
}
};
const handleReadAll = async () => {
try {
await readAll();
setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 })));
setMessageCount(0);
} catch (error) {
console.error("全部已读失败:", error);
}
};
const fetchFriendRequestList = async () => {
try {
setFriendRequestLoading(true);
const response = await fetchFriendRequestListApi(DEFAULT_QUERY);
if (response?.list) {
setFriendRequestList(response.list);
}
} catch (error) {
console.error("获取好友添加记录失败:", error);
} finally {
setFriendRequestLoading(false);
}
};
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) {
return date.toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
});
} else if (days === 1) {
return "昨天";
} else if (days < 7) {
return `${days}天前`;
} else {
return date.toLocaleDateString("zh-CN", {
month: "2-digit",
day: "2-digit",
});
}
};
const getStatusText = (statusCode?: number, statusText?: string) => {
if (statusText) {
return statusText;
}
switch (statusCode) {
case 0:
return "待处理";
case 1:
return "已同意";
case 2:
return "已拒绝";
default:
return "未知";
}
};
const getStatusColor = (statusCode?: number) => {
switch (statusCode) {
case 0:
return "#1890ff";
case 1:
return "#52c41a";
case 2:
return "#ff4d4f";
default:
return "#999";
}
};
const getFriendRequestKey = (item: FriendRequestItem) => {
return (
item.taskId?.toString() ||
item.wechatId ||
item.phone ||
`${item.adder?.username || "unknown"}-${item.time?.addTime || "time"}`
);
};
const getAddedUserName = (item: FriendRequestItem) => {
return (
item.friend?.nickname ||
item.phone ||
item.wechatId ||
item.adder?.nickname ||
"未知好友"
);
};
const getAdderName = (item: FriendRequestItem) => {
return (
item.adder?.nickname ||
item.adder?.username ||
item.adder?.accountNickname ||
item.adder?.accountRealName ||
"未知添加人"
);
};
return (
<>
<div className={styles.messageButton} onClick={handleMessageClick}>
<Badge count={messageCount} size="small">
<BellOutlined style={{ fontSize: 20 }} />
</Badge>
</div>
<Drawer
title="通知中心"
placement="right"
onClose={handleMessageDrawerClose}
open={messageDrawerVisible}
width={400}
className={styles.messageDrawer}
extra={
activeTab === "messages" && (
<Space>
<Button type="text" size="small" onClick={handleReadAll}>
</Button>
</Space>
)
}
>
<div style={{ padding: "0 20px" }}>
<Tabs
activeKey={activeTab}
onChange={handleTabChange}
items={[
{
key: "messages",
label: "消息列表",
children: (
<div className={styles.messageContent}>
{loading ? (
<div style={{ textAlign: "center", padding: "20px" }}>
...
</div>
) : messageList.length === 0 ? (
<Empty description="暂无消息" />
) : (
messageList.map(item => (
<div
key={item.id}
className={`${styles.messageItem} ${
item.isRead === 0 ? styles.unread : ""
}`}
onClick={() => handleReadMessage(item.id)}
>
<div className={styles.messageAvatar}>
<Avatar
size={40}
src={item.friendData?.avatar}
style={{ backgroundColor: "#87d068" }}
>
{item.friendData?.nickname?.charAt(0) || "U"}
</Avatar>
</div>
<div className={styles.messageInfo}>
<div className={styles.messageTitle}>
<span className={styles.messageType}>
{item.title}
</span>
{item.isRead === 0 && (
<div className={styles.messageStatus}></div>
)}
</div>
<div className={styles.messageText}>
{item.message}
</div>
{item.isRead === 0 && (
<div className={styles.messageTime}>
{formatTime(item.createTime)}
<Button
type="link"
size="small"
onClick={event => {
event.stopPropagation();
handleReadMessage(item.id);
}}
>
</Button>
</div>
)}
</div>
</div>
))
)}
</div>
),
},
{
key: "friendRequests",
label: "好友添加记录",
children: (
<div className={styles.messageContent}>
{friendRequestLoading ? (
<div style={{ textAlign: "center", padding: "20px" }}>
...
</div>
) : friendRequestList.length === 0 ? (
<Empty description="暂无好友添加记录" />
) : (
friendRequestList.map(item => (
<div
key={getFriendRequestKey(item)}
className={styles.messageItem}
>
<div className={styles.messageAvatar}>
<Avatar
size={40}
src={item.adder?.avatar}
style={{ backgroundColor: "#87d068" }}
>
{item.adder?.nickname?.charAt(0) || "U"}
</Avatar>
</div>
<div className={styles.messageInfo}>
<div className={styles.messageTitle}>
<span className={styles.messageType}>
<Tag color="blue">{getAddedUserName(item)}</Tag>
</span>
<span
style={{
fontSize: "12px",
color: getStatusColor(item.status?.code),
fontWeight: 500,
}}
>
{getStatusText(
item.status?.code,
item.status?.text,
)}
</span>
</div>
<div
className={styles.messageText}
style={{ color: "#595959" }}
>
{getAdderName(item)}
</div>
<div className={styles.messageText}>
{item.other?.msgContent || "无"}
</div>
<div
className={styles.messageText}
style={{ color: "#999", fontSize: 12 }}
>
{item.other?.remark && (
<Tag color="orange" style={{ marginTop: 4 }}>
{item.other.remark}
</Tag>
)}
</div>
<div className={styles.messageTime}>
{formatTime(item.time?.addTime)}
</div>
</div>
</div>
))
)}
</div>
),
},
]}
/>
</div>
</Drawer>
</>
);
};
export default Notice;

View File

@@ -14,3 +14,8 @@ export const readMessage = (params: { id: number }) => {
export const readAll = () => {
return request(`/v1/kefu/notice/readAll`, undefined, "PUT");
};
// 好友添加任务列表
export const friendRequestList = (params: { page: number; limit: number }) => {
return request(`/v1/kefu/wechatFriend/addTaskList`, params, "GET");
};

View File

@@ -1,29 +1,18 @@
import React, { useState, useEffect } from "react";
import {
Layout,
Drawer,
Avatar,
Space,
Button,
Badge,
Dropdown,
Empty,
message,
} from "antd";
import React, { useState } from "react";
import { Layout, Avatar, Space, Button, Dropdown, message } from "antd";
import {
BarChartOutlined,
UserOutlined,
BellOutlined,
LogoutOutlined,
ThunderboltOutlined,
SettingOutlined,
SendOutlined,
ClearOutlined,
} from "@ant-design/icons";
import { noticeList, readMessage, readAll } from "./api";
import { useUserStore } from "@/store/module/user";
import { useNavigate, useLocation } from "react-router-dom";
import styles from "./index.module.scss";
import Notice from "./Notice";
const { Header } = Layout;
@@ -32,40 +21,12 @@ interface NavCommonProps {
onMenuClick?: () => void;
}
// 消息数据类型
interface MessageItem {
id: number;
type: number;
companyId: number;
userId: number;
bindId: number;
title: string;
message: string;
isRead: number;
createTime: string;
readTime: string;
friendData: {
nickname: string;
avatar: string;
};
}
const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
const [messageDrawerVisible, setMessageDrawerVisible] = useState(false);
const [messageList, setMessageList] = useState<MessageItem[]>([]);
const [messageCount, setMessageCount] = useState(0);
const [loading, setLoading] = useState(false);
const [clearingCache, setClearingCache] = useState(false);
const navigate = useNavigate();
const location = useLocation();
const { user, logout } = useUserStore();
// 初始化时获取消息列表
useEffect(() => {
fetchMessageList();
setInterval(IntervalMessageCount, 30 * 1000);
}, []);
// 处理菜单图标点击:在两个路由之间切换
const handleMenuClick = () => {
if (!location.pathname.startsWith("/pc/powerCenter")) {
@@ -74,48 +35,6 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
navigate("/pc/weChat");
}
};
// 定时器获取消息条数
const IntervalMessageCount = async () => {
try {
const response = await noticeList({ page: 1, limit: 20 });
if (response && response.noRead) {
setMessageCount(response.noRead);
}
} catch (error) {
console.error("获取消息列表失败:", error);
}
};
// 获取消息列表
const fetchMessageList = async () => {
try {
setLoading(true);
const response = await noticeList({ page: 1, limit: 20 });
if (response && response.list) {
setMessageList(response.list);
// 计算未读消息数量
const unreadCount = response.list.filter(
(item: MessageItem) => item.isRead === 0,
).length;
setMessageCount(unreadCount);
}
} catch (error) {
console.error("获取消息列表失败:", error);
} finally {
setLoading(false);
}
};
// 处理消息中心点击
const handleMessageClick = () => {
setMessageDrawerVisible(true);
fetchMessageList();
};
// 处理消息抽屉关闭
const handleMessageDrawerClose = () => {
setMessageDrawerVisible(false);
};
// 处理退出登录
const handleLogout = () => {
logout(); // 清除localStorage中的token和用户状态
@@ -215,61 +134,6 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
}
};
// 处理消息已读
const handleReadMessage = async (messageId: number) => {
try {
await readMessage({ id: messageId }); // 这里需要根据实际API调整参数
// 更新本地状态
setMessageList(prev =>
prev.map(item =>
item.id === messageId ? { ...item, isRead: 1 } : item,
),
);
// 重新计算未读数量
const unreadCount =
messageList.filter(item => item.isRead === 0).length - 1;
setMessageCount(Math.max(0, unreadCount));
} catch (error) {
console.error("标记消息已读失败:", error);
}
};
// 处理全部已读
const handleReadAll = async () => {
try {
await readAll(); // 这里需要根据实际API调整参数
// 更新本地状态
setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 })));
setMessageCount(0);
} catch (error) {
console.error("全部已读失败:", error);
}
};
// 格式化时间
const formatTime = (timeStr: string) => {
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) {
return date.toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
});
} else if (days === 1) {
return "昨天";
} else if (days < 7) {
return `${days}天前`;
} else {
return date.toLocaleDateString("zh-CN", {
month: "2-digit",
day: "2-digit",
});
}
};
// 用户菜单项
const userMenuItems = [
{
@@ -333,11 +197,7 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
</span>
{user?.tokens}
</span>
<div className={styles.messageButton} onClick={handleMessageClick}>
<Badge count={messageCount} size="small">
<BellOutlined style={{ fontSize: 20 }} />
</Badge>
</div>
<Notice />
<Dropdown
menu={{ items: userMenuItems }}
@@ -361,69 +221,6 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
</Space>
</div>
</Header>
<Drawer
title="通知中心"
placement="right"
onClose={handleMessageDrawerClose}
open={messageDrawerVisible}
width={400}
className={styles.messageDrawer}
extra={
<Space>
<Button type="text" size="small" onClick={handleReadAll}>
</Button>
</Space>
}
>
<div className={styles.messageContent}>
{loading ? (
<div style={{ textAlign: "center", padding: "20px" }}>
...
</div>
) : messageList.length === 0 ? (
<Empty description="暂无消息" />
) : (
messageList.map(item => (
<div
key={item.id}
className={`${styles.messageItem} ${
item.isRead === 0 ? styles.unread : ""
}`}
onClick={() => handleReadMessage(item.id)}
>
<div className={styles.messageAvatar}>
<Avatar
size={40}
src={item.friendData?.avatar}
style={{ backgroundColor: "#87d068" }}
>
{item.friendData?.nickname?.charAt(0) || "U"}
</Avatar>
</div>
<div className={styles.messageInfo}>
<div className={styles.messageTitle}>
<span className={styles.messageType}>{item.title}</span>
{item.isRead === 0 && (
<div className={styles.messageStatus}></div>
)}
</div>
<div className={styles.messageText}>{item.message}</div>
{item.isRead === 0 && (
<div className={styles.messageTime}>
{formatTime(item.createTime)}
<Button type="link" size="small">
</Button>
</div>
)}
</div>
</div>
))
)}
</div>
</Drawer>
</>
);
};