优化导航组件:新增消息列表功能,定时获取未读消息数量,支持标记消息为已读和全部已读,提升用户体验和代码可读性。

This commit is contained in:
超级老白兔
2025-09-29 17:12:17 +08:00
parent 713956aa4f
commit 85149e5af0
2 changed files with 186 additions and 73 deletions

View File

@@ -0,0 +1,16 @@
import request from "@/api/request";
// 消息列表
export const noticeList = (params: { page: number; limit: number }) => {
return request(`/v1/kefu/notice/list`, params, "GET");
};
// 消息列表
export const readMessage = (params: { id: number }) => {
return request(`/v1/kefu/notice/readMessage`, params, "PUT");
};
// 消息列表
export const readAll = () => {
return request(`/v1/kefu/notice/readAll`, undefined, "PUT");
};

View File

@@ -1,16 +1,24 @@
import React, { useState } from "react";
import { Layout, Drawer, Avatar, Space, Button, Badge, Dropdown } from "antd";
import React, { useState, useEffect } from "react";
import {
Layout,
Drawer,
Avatar,
Space,
Button,
Badge,
Dropdown,
Empty,
} from "antd";
import {
BarChartOutlined,
UserOutlined,
BellOutlined,
LogoutOutlined,
UserSwitchOutlined,
ThunderboltOutlined,
SettingOutlined,
WechatOutlined,
} 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";
@@ -22,13 +30,39 @@ 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 [messageCount] = useState(3); // 模拟消息数量
const [messageList, setMessageList] = useState<MessageItem[]>([]);
const [messageCount, setMessageCount] = useState(0);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const location = useLocation();
const { user, logout } = useUserStore();
// 初始化时获取消息列表
useEffect(() => {
fetchMessageList();
setInterval(IntervalMessageCount, 30 * 1000);
}, []);
// 处理菜单图标点击:在两个路由之间切换
const handleMenuClick = () => {
const current = location.pathname;
@@ -43,9 +77,41 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
return location.pathname.startsWith("/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();
};
// 处理消息抽屉关闭
@@ -59,6 +125,61 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
navigate("/login"); // 跳转到登录页面
};
// 处理消息已读
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 = [
{
@@ -120,14 +241,7 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
<BellOutlined style={{ fontSize: 20 }} />
</Badge>
</div>
{/* <Button
onClick={() => {
navigate("/pc/commonConfig");
}}
icon={<SettingOutlined />}
>
全局配置
</Button> */}
<Dropdown
menu={{ items: userMenuItems }}
placement="bottomRight"
@@ -160,74 +274,57 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
className={styles.messageDrawer}
extra={
<Space>
<Button type="text" size="small">
<Button type="text" size="small" onClick={handleReadAll}>
</Button>
</Space>
}
>
<div className={styles.messageContent}>
<div className={styles.messageItem}>
<div className={styles.messageAvatar}>
<Avatar size={40} style={{ backgroundColor: "#87d068" }}>
</Avatar>
{loading ? (
<div style={{ textAlign: "center", padding: "20px" }}>
...
</div>
<div className={styles.messageInfo}>
<div className={styles.messageTitle}>
<span className={styles.messageType}></span>
<div className={styles.messageStatus}></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 className={styles.messageText}>
19991699
</div>
<div className={styles.messageTime}>
03-05
<Button type="link" size="small">
</Button>
</div>
</div>
</div>
<div className={styles.messageItem}>
<div className={styles.messageAvatar}>
<Avatar size={40} style={{ backgroundColor: "#f56a00" }}>
E
</Avatar>
</div>
<div className={styles.messageInfo}>
<div className={styles.messageTitle}>
<span className={styles.messageType}></span>
<div className={styles.messageStatus}></div>
</div>
<div className={styles.messageText}>
Eric在@了您
</div>
<div className={styles.messageTime}>03-05</div>
</div>
</div>
<div className={styles.messageItem}>
<div className={styles.messageAvatar}>
<Avatar size={40} style={{ backgroundColor: "#1890ff" }}>
</Avatar>
</div>
<div className={styles.messageInfo}>
<div className={styles.messageTitle}>
<span className={styles.messageType}></span>
</div>
<div className={styles.messageText}></div>
<div className={styles.messageTime}>03-04</div>
<div className={styles.messageActions}>
<Button type="primary" size="small">
</Button>
<Button size="small"></Button>
</div>
</div>
</div>
))
)}
</div>
</Drawer>
</>