| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import React, { useEffect } from 'react';
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
- import dayjs from 'dayjs';
- import 'dayjs/locale/zh-cn';
- import { BellIcon } from '@heroicons/react/24/outline';
- import { MessageStatus } from '../share/types.ts';
- import { io, Socket } from 'socket.io-client';
- // 添加通知页面组件
- import { MessageAPI } from './api/index.ts';
- import { useAuth } from "./hooks.tsx";
- export const NotificationsPage = () => {
- const { token , user} = useAuth();
- const queryClient = useQueryClient();
- const [socket, setSocket] = React.useState<Socket | null>(null);
- const [isSubscribed, setIsSubscribed] = React.useState(false);
- // 获取消息列表
- const { data: messages, isLoading } = useQuery({
- queryKey: ['messages'],
- queryFn: () => MessageAPI.getMessages(),
- });
- // 初始化Socket.IO连接
- useEffect(() => {
- if (!token || !user) return;
- const newSocket = io('/', {
- path: '/socket.io',
- transports: ['websocket'],
- withCredentials: true,
- query: {
- socket_token: token
- }
- });
- setSocket(newSocket);
- // 订阅消息频道
- newSocket.on('connect', () => {
- newSocket.emit('message:subscribe', `user_${user.id}`);
- setIsSubscribed(true);
- });
- // 处理实时消息
- newSocket.on('message:broadcasted', (newMessage) => {
- queryClient.setQueryData(['messages'], (oldData: any) => {
- if (!oldData) return oldData;
- return {
- ...oldData,
- data: [newMessage, ...oldData.data]
- };
- });
- // 更新未读计数
- queryClient.setQueryData(['unreadCount'], (oldData: any) => {
- if (!oldData) return oldData;
- return {
- ...oldData,
- count: oldData.count + 1
- };
- });
- });
- // 错误处理
- newSocket.on('error', (error) => {
- console.error('Socket error:', error);
- });
- return () => {
- if (newSocket) {
- newSocket.emit('message:unsubscribe', `user_${user.id}`);
- newSocket.disconnect();
- }
- };
- }, [queryClient, token]);
- // 获取未读消息数量
- const { data: unreadCount } = useQuery({
- queryKey: ['unreadCount'],
- queryFn: () => MessageAPI.getUnreadCount(),
- });
- // 标记消息为已读
- const markAsReadMutation = useMutation({
- mutationFn: (id: number) => MessageAPI.markAsRead(id),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['messages'] });
- queryClient.invalidateQueries({ queryKey: ['unreadCount'] });
- },
- });
- // 删除消息
- const deleteMutation = useMutation({
- mutationFn: (id: number) => MessageAPI.deleteMessage(id),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['messages'] });
- },
- });
- const handleMarkAsRead = (id: number) => {
- markAsReadMutation.mutate(id);
- };
- const handleDelete = (id: number) => {
- if (confirm('确定要删除这条消息吗?')) {
- deleteMutation.mutate(id);
- }
- };
- if (isLoading) {
- return (
- <div className="p-4">
- <h1 className="text-2xl font-bold mb-4">通知</h1>
- <div className="flex justify-center items-center h-40">
- <div className="w-8 h-8 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
- </div>
- </div>
- );
- }
- return (
- <div className="p-4">
- <div className="flex items-center justify-between mb-4">
- <h1 className="text-2xl font-bold">通知</h1>
- {unreadCount && unreadCount.count > 0 ? (
- <div className="flex items-center">
- <BellIcon className="h-5 w-5 text-red-500 mr-1" />
- <span className="text-sm text-red-500">{unreadCount.count}条未读</span>
- </div>
- ) : null}
- </div>
- <div className="bg-white rounded-lg shadow divide-y">
- {messages?.data.map((message) => (
- <div key={message.id} className="p-4">
- <div className="flex justify-between items-start">
- <h3 className="font-medium">{message.title}</h3>
- <div className="flex space-x-2">
- {message.user_status === MessageStatus.UNREAD && (
- <button
- type="button"
- onClick={() => handleMarkAsRead(message.id)}
- className="text-xs text-blue-600 hover:text-blue-800"
- >
- 标记已读
- </button>
- )}
- <button
- type="button"
- onClick={() => handleDelete(message.id)}
- className="text-xs text-red-600 hover:text-red-800"
- >
- 删除
- </button>
- </div>
- </div>
- <p className="text-gray-500 text-sm mt-1">{message.content}</p>
- <p className="text-xs text-gray-400 mt-2">
- {dayjs(message.created_at).format('YYYY-MM-DD HH:mm')}
- </p>
- </div>
- ))}
- </div>
- </div>
- );
- };
|