Spaces:
Configuration error
Configuration error
| import React, { useState, useEffect, useRef } from 'react'; | |
| import { Send, LogOut, Users, MessageCircle } from 'lucide-react'; | |
| import { User, Message, OnlineUser } from '../types'; | |
| import { messageAPI } from '../utils/api'; | |
| import { socketService } from '../utils/socket'; | |
| interface ChatProps { | |
| user: User; | |
| onLogout: () => void; | |
| } | |
| const Chat: React.FC<ChatProps> = ({ user, onLogout }) => { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [newMessage, setNewMessage] = useState(''); | |
| const [onlineUsers, setOnlineUsers] = useState<OnlineUser[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const messagesEndRef = useRef<HTMLDivElement>(null); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| }; | |
| useEffect(() => { | |
| scrollToBottom(); | |
| }, [messages]); | |
| useEffect(() => { | |
| const initializeChat = async () => { | |
| try { | |
| // 获取历史消息 | |
| const historyMessages = await messageAPI.getMessages(); | |
| setMessages(historyMessages); | |
| // 连接Socket | |
| const token = localStorage.getItem('token'); | |
| if (token) { | |
| const socket = socketService.connect(token); | |
| // 监听新消息 | |
| socketService.onNewMessage((message: Message) => { | |
| setMessages(prev => [...prev, message]); | |
| }); | |
| // 监听用户上线 | |
| socketService.onUserJoined((userData) => { | |
| console.log(`${userData.username} 加入了聊天室`); | |
| }); | |
| // 监听用户下线 | |
| socketService.onUserLeft((userData) => { | |
| console.log(`${userData.username} 离开了聊天室`); | |
| }); | |
| // 监听在线用户列表 | |
| socketService.onOnlineUsers((users: OnlineUser[]) => { | |
| setOnlineUsers(users); | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('初始化聊天失败:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| initializeChat(); | |
| // 清理函数 | |
| return () => { | |
| socketService.offAllListeners(); | |
| socketService.disconnect(); | |
| }; | |
| }, []); | |
| const handleSendMessage = (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (newMessage.trim()) { | |
| socketService.sendMessage(newMessage.trim()); | |
| setNewMessage(''); | |
| } | |
| }; | |
| const handleLogout = () => { | |
| socketService.disconnect(); | |
| onLogout(); | |
| }; | |
| const formatTime = (timestamp: Date) => { | |
| return new Date(timestamp).toLocaleTimeString('zh-CN', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| }); | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center bg-gray-50"> | |
| <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="flex h-screen bg-gray-50"> | |
| {/* 侧边栏 */} | |
| <div className="w-64 bg-white border-r border-gray-200 flex flex-col"> | |
| {/* 用户信息 */} | |
| <div className="p-4 border-b border-gray-200"> | |
| <div className="flex items-center space-x-3"> | |
| <div className="w-10 h-10 bg-primary-600 rounded-full flex items-center justify-center"> | |
| <span className="text-white font-medium"> | |
| {user.username.charAt(0).toUpperCase()} | |
| </span> | |
| </div> | |
| <div className="flex-1"> | |
| <h3 className="font-medium text-gray-900">{user.username}</h3> | |
| <p className="text-sm text-gray-500">{user.email}</p> | |
| </div> | |
| <button | |
| onClick={handleLogout} | |
| className="p-2 text-gray-400 hover:text-gray-600 rounded-lg hover:bg-gray-100" | |
| title="退出登录" | |
| > | |
| <LogOut className="h-5 w-5" /> | |
| </button> | |
| </div> | |
| </div> | |
| {/* 在线用户 */} | |
| <div className="flex-1 p-4"> | |
| <div className="flex items-center space-x-2 mb-4"> | |
| <Users className="h-5 w-5 text-gray-500" /> | |
| <h4 className="font-medium text-gray-900"> | |
| 在线用户 ({onlineUsers.length}) | |
| </h4> | |
| </div> | |
| <div className="space-y-2"> | |
| {onlineUsers.map((onlineUser) => ( | |
| <div key={onlineUser.userId} className="flex items-center space-x-3"> | |
| <div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center"> | |
| <span className="text-white text-sm font-medium"> | |
| {onlineUser.username.charAt(0).toUpperCase()} | |
| </span> | |
| </div> | |
| <span className="text-sm text-gray-700">{onlineUser.username}</span> | |
| {onlineUser.userId === user.id && ( | |
| <span className="text-xs text-gray-500">(你)</span> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| {/* 主聊天区域 */} | |
| <div className="flex-1 flex flex-col"> | |
| {/* 聊天头部 */} | |
| <div className="bg-white border-b border-gray-200 p-4"> | |
| <div className="flex items-center space-x-2"> | |
| <MessageCircle className="h-6 w-6 text-primary-600" /> | |
| <h2 className="text-lg font-semibold text-gray-900">聊天室</h2> | |
| </div> | |
| </div> | |
| {/* 消息列表 */} | |
| <div className="flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar"> | |
| {messages.map((message) => ( | |
| <div | |
| key={message.id} | |
| className={`flex ${ | |
| message.sender.id === user.id ? 'justify-end' : 'justify-start' | |
| }`} | |
| > | |
| <div | |
| className={`message-bubble ${ | |
| message.sender.id === user.id ? 'message-own' : 'message-other' | |
| }`} | |
| > | |
| {message.sender.id !== user.id && ( | |
| <div className="text-xs font-medium mb-1 text-gray-600"> | |
| {message.sender.username} | |
| </div> | |
| )} | |
| <div className="text-sm">{message.content}</div> | |
| <div | |
| className={`text-xs mt-1 ${ | |
| message.sender.id === user.id | |
| ? 'text-primary-200' | |
| : 'text-gray-500' | |
| }`} | |
| > | |
| {formatTime(message.timestamp)} | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* 消息输入 */} | |
| <div className="bg-white border-t border-gray-200 p-4"> | |
| <form onSubmit={handleSendMessage} className="flex space-x-4"> | |
| <input | |
| type="text" | |
| value={newMessage} | |
| onChange={(e) => setNewMessage(e.target.value)} | |
| placeholder="输入消息..." | |
| className="flex-1 input-field" | |
| /> | |
| <button | |
| type="submit" | |
| disabled={!newMessage.trim()} | |
| className="btn-primary disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2" | |
| > | |
| <Send className="h-4 w-4" /> | |
| <span>发送</span> | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default Chat; | |