| import React, { useState, useEffect } from 'react'; |
| import { Card, List, Avatar, Input, Button, Row, Col, message } from 'antd'; |
| import { SysUserMessage, ChatContact } from './data.d'; |
| import { getChatContactList, getChatHistory, sendMessage, getUserInfo } from './service'; |
| import './index.less'; |
| |
| const MessagePage: React.FC = () => { |
| const [chatContacts, setChatContacts] = useState<ChatContact[]>([]); |
| const [selectedUserId, setSelectedUserId] = useState<number | null>(null); |
| const [inputMessage, setInputMessage] = useState(''); |
| const [chatMessages, setChatMessages] = useState<SysUserMessage[]>([]); |
| const [loading, setLoading] = useState(false); |
| const [sending, setSending] = useState(false); |
| |
| // 获取聊天对象列表 |
| const fetchChatContacts = async () => { |
| try { |
| setLoading(true); |
| const response = await getChatContactList(); |
| |
| // 按最后消息时间排序 |
| const sortedContacts = response.sort((a: ChatContact, b: ChatContact) => |
| new Date(b.lastMessageTime).getTime() - new Date(a.lastMessageTime).getTime() |
| ); |
| setChatContacts(sortedContacts); |
| } catch (error) { |
| message.error('获取聊天列表失败'); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| // 获取与特定用户的聊天记录 |
| const fetchChatHistory = async (userId: number) => { |
| try { |
| setLoading(true); |
| const response = await getChatHistory({ |
| userId, |
| currentUserId: 1, // 假设当前用户ID为1,实际应该从用户状态获取 |
| pageSize: 100 // 获取最近100条消息 |
| }); |
| |
| // 按时间排序 |
| const sortedMessages = response.sort((a: SysUserMessage, b: SysUserMessage) => |
| new Date(a.createTime).getTime() - new Date(b.createTime).getTime() |
| ); |
| setChatMessages(sortedMessages); |
| } catch (error) { |
| message.error('获取聊天记录失败'); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| // 发送消息 |
| const handleSendMessage = async () => { |
| if (!inputMessage.trim() || !selectedUserId || sending) { |
| return; |
| } |
| |
| try { |
| setSending(true); |
| |
| // 先在界面上显示消息(乐观更新) |
| const tempMessage: SysUserMessage = { |
| messageId: Date.now(), // 临时ID |
| senderId: 1, // 当前用户ID |
| receiverId: selectedUserId, |
| content: inputMessage, |
| createTime: new Date(), |
| delFlag: '0' |
| }; |
| setChatMessages(prev => [...prev, tempMessage]); |
| |
| // 清空输入框 |
| const messageContent = inputMessage; |
| setInputMessage(''); |
| |
| // 调用API发送消息 |
| await sendMessage({ |
| receiverId: selectedUserId, |
| content: messageContent |
| }); |
| |
| // 更新聊天对象列表中的最后消息 |
| setChatContacts(prevContacts => { |
| const updatedContacts = prevContacts.map(contact => |
| contact.userId === selectedUserId |
| ? { |
| ...contact, |
| lastMessage: messageContent, |
| lastMessageTime: new Date() |
| } |
| : contact |
| ); |
| // 重新排序,将当前聊天对象移到最前面 |
| return updatedContacts.sort((a, b) => |
| new Date(b.lastMessageTime).getTime() - new Date(a.lastMessageTime).getTime() |
| ); |
| }); |
| |
| message.success('消息发送成功'); |
| |
| } catch (error) { |
| message.error('发送消息失败'); |
| // 发送失败时,移除临时消息 |
| setChatMessages(prev => prev.filter(msg => msg.messageId !== Date.now())); |
| } finally { |
| setSending(false); |
| } |
| }; |
| |
| // 选择聊天对象 |
| const handleSelectUser = (userId: number) => { |
| setSelectedUserId(userId); |
| fetchChatHistory(userId); |
| }; |
| |
| // 格式化时间显示 |
| const formatTime = (time: Date | string) => { |
| const date = new Date(time); |
| const now = new Date(); |
| const diff = now.getTime() - date.getTime(); |
| const minutes = Math.floor(diff / (1000 * 60)); |
| const hours = Math.floor(diff / (1000 * 60 * 60)); |
| const days = Math.floor(diff / (1000 * 60 * 60 * 24)); |
| |
| if (minutes < 60) { |
| return `${minutes}分钟前`; |
| } else if (hours < 24) { |
| return `${hours}小时前`; |
| } else { |
| return `${days}天前`; |
| } |
| }; |
| |
| // 获取用户名 |
| const getUserName = (userId: number) => { |
| const contact = chatContacts.find(c => c.userId === userId); |
| return contact?.userName || `用户${userId}`; |
| }; |
| |
| // 处理回车键发送 |
| const handleKeyPress = (e: React.KeyboardEvent) => { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| handleSendMessage(); |
| } |
| }; |
| |
| useEffect(() => { |
| fetchChatContacts(); |
| }, []); |
| |
| return ( |
| <div className="message-page" style={{ height: '100vh', padding: '16px', display: 'flex', flexDirection: 'column' }}> |
| <Row gutter={16} style={{ flex: 1, overflow: 'hidden' }}> |
| {/* 左侧聊天对象列表 */} |
| <Col span={8}> |
| <Card |
| title="聊天列表" |
| bordered={false} |
| style={{ height: '100%' }} |
| loading={loading && !selectedUserId} |
| > |
| <List |
| style={{ height: 'calc(100vh - 140px)', overflowY: 'auto' }} |
| dataSource={chatContacts} |
| renderItem={(contact) => ( |
| <List.Item |
| onClick={() => handleSelectUser(contact.userId)} |
| style={{ |
| cursor: 'pointer', |
| backgroundColor: selectedUserId === contact.userId ? '#f0f8ff' : 'transparent', |
| padding: '12px', |
| borderRadius: '8px', |
| margin: '4px 0' |
| }} |
| className={selectedUserId === contact.userId ? 'selected' : ''} |
| > |
| <List.Item.Meta |
| avatar={ |
| <Avatar size="large"> |
| {contact.userName.charAt(0)} |
| </Avatar> |
| } |
| title={ |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> |
| <span style={{ |
| fontWeight: 'normal', |
| fontSize: '14px' |
| }}> |
| {contact.userName} |
| </span> |
| <span style={{ fontSize: '12px', color: '#999' }}> |
| {formatTime(contact.lastMessageTime)} |
| </span> |
| </div> |
| } |
| description={ |
| <div style={{ |
| color: '#666', |
| fontWeight: 'normal', |
| overflow: 'hidden', |
| textOverflow: 'ellipsis', |
| whiteSpace: 'nowrap', |
| fontSize: '13px' |
| }}> |
| {contact.lastMessage} |
| </div> |
| } |
| /> |
| </List.Item> |
| )} |
| /> |
| </Card> |
| </Col> |
| |
| {/* 右侧聊天界面 */} |
| <Col span={16}> |
| <div style={{ |
| height: '100%', |
| display: 'flex', |
| flexDirection: 'column', |
| position: 'relative' |
| }}> |
| {/* 聊天标题 */} |
| <Card |
| title={selectedUserId ? `与 ${getUserName(selectedUserId)} 的对话` : '选择一个联系人开始聊天'} |
| bordered={false} |
| style={{ |
| marginBottom: 0, |
| borderBottom: '1px solid #f0f0f0' |
| }} |
| bodyStyle={{ padding: 0 }} |
| /> |
| |
| {/* 聊天消息区域 */} |
| <div style={{ |
| flex: 1, |
| overflow: 'hidden', |
| display: 'flex', |
| flexDirection: 'column', |
| backgroundColor: '#fff', |
| border: '1px solid #f0f0f0', |
| borderTop: 'none' |
| }}> |
| {selectedUserId ? ( |
| <> |
| <List |
| style={{ |
| flex: 1, |
| overflowY: 'auto', |
| padding: '16px', |
| paddingBottom: '80px' // 为输入框预留空间 |
| }} |
| dataSource={chatMessages} |
| loading={loading && selectedUserId !== null} |
| renderItem={(item) => ( |
| <List.Item |
| className={`chat-message ${item.senderId === 1 ? 'sent' : 'received'}`} |
| style={{ |
| border: 'none', |
| padding: '8px 0', |
| display: 'flex', |
| justifyContent: item.senderId === 1 ? 'flex-end' : 'flex-start' |
| }} |
| > |
| <div style={{ |
| maxWidth: '70%', |
| display: 'flex', |
| flexDirection: item.senderId === 1 ? 'row-reverse' : 'row', |
| alignItems: 'flex-start', |
| gap: '8px' |
| }}> |
| <Avatar size="small"> |
| {item.senderId === 1 ? 'Me' : getUserName(item.senderId).charAt(0)} |
| </Avatar> |
| <div> |
| <div style={{ |
| fontSize: '12px', |
| color: '#999', |
| marginBottom: '4px', |
| textAlign: item.senderId === 1 ? 'right' : 'left' |
| }}> |
| {item.senderId === 1 ? '我' : getUserName(item.senderId)} · {new Date(item.createTime).toLocaleTimeString()} |
| </div> |
| <div style={{ |
| backgroundColor: item.senderId === 1 ? '#1890ff' : '#f0f0f0', |
| color: item.senderId === 1 ? '#fff' : '#000', |
| padding: '8px 12px', |
| borderRadius: '12px', |
| wordBreak: 'break-word', |
| lineHeight: '1.4' |
| }}> |
| {item.content} |
| </div> |
| </div> |
| </div> |
| </List.Item> |
| )} |
| /> |
| |
| {/* 输入框区域 - 固定在底部 */} |
| <div style={{ |
| position: 'absolute', |
| bottom: 0, |
| left: 0, |
| right: 0, |
| backgroundColor: '#fff', |
| padding: '16px', |
| borderTop: '1px solid #f0f0f0', |
| display: 'flex', |
| gap: '8px', |
| zIndex: 10, |
| boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.1)' |
| }}> |
| <Input.TextArea |
| value={inputMessage} |
| onChange={(e) => setInputMessage(e.target.value)} |
| onKeyDown={handleKeyPress} |
| placeholder="输入消息...(按 Enter 发送,Shift+Enter 换行)" |
| style={{ |
| flex: 1, |
| resize: 'none', |
| minHeight: '40px', |
| maxHeight: '120px' |
| }} |
| autoSize={{ minRows: 1, maxRows: 4 }} |
| /> |
| <Button |
| type="primary" |
| onClick={handleSendMessage} |
| loading={sending} |
| style={{ height: '40px' }} |
| > |
| 发送 |
| </Button> |
| </div> |
| </> |
| ) : ( |
| <div style={{ |
| flex: 1, |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| color: '#999', |
| fontSize: '16px' |
| }}> |
| 请从左侧选择一个联系人开始聊天 |
| </div> |
| )} |
| </div> |
| </div> |
| </Col> |
| </Row> |
| </div> |
| ); |
| }; |
| |
| export default MessagePage; |