| import React, { useState, useEffect } from 'react'; |
| import { Card, List, Avatar, Input, Button, Row, Col, message, Modal, Form } from 'antd'; |
| import { UserAddOutlined, SearchOutlined } from '@ant-design/icons'; |
| import { SysUserMessage, ChatContact } from './data.d'; |
| import { getChatContactList, getChatHistory, sendMessage, getUserInfo, addFriend } from './service'; |
| import './index.less'; |
| import { useModel } from 'umi'; |
| |
| 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 { initialState } = useModel('@@initialState'); |
| const ccuserId = initialState?.currentUser?.userId || ''; |
| // 添加好友相关状态 |
| const [addFriendVisible, setAddFriendVisible] = useState(false); |
| const [addFriendLoading, setAddFriendLoading] = useState(false); |
| const [form] = Form.useForm(); |
| |
| // 获取聊天对象列表 |
| 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: Number(ccuserId), // 假设当前用户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 handleAddFriend = async (values: { username: string }) => { |
| try { |
| setAddFriendLoading(true); |
| await addFriend({ userId: Number(ccuserId), authorUsername: values.username }); |
| message.success('好友添加成功'); |
| |
| // 重新获取聊天对象列表 |
| await fetchChatContacts(); |
| |
| // 关闭弹窗并重置表单 |
| setAddFriendVisible(false); |
| form.resetFields(); |
| } catch (error) { |
| message.error('添加好友失败,请检查用户名是否正确'); |
| } finally { |
| setAddFriendLoading(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?.nickName || `用户${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={ |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> |
| <span>聊天列表</span> |
| <Button |
| type="primary" |
| icon={<UserAddOutlined />} |
| size="small" |
| onClick={() => setAddFriendVisible(true)} |
| > |
| 添加好友 |
| </Button> |
| </div> |
| } |
| 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.nickName.charAt(0).toUpperCase()} |
| </Avatar> |
| } |
| title={ |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> |
| <span style={{ |
| fontWeight: 'normal', |
| fontSize: '14px' |
| }}> |
| {contact.nickName} |
| </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> |
| |
| {/* 添加好友弹窗 */} |
| <Modal |
| title="添加好友" |
| open={addFriendVisible} |
| onCancel={() => { |
| setAddFriendVisible(false); |
| form.resetFields(); |
| }} |
| footer={null} |
| width={400} |
| > |
| <Form |
| form={form} |
| layout="vertical" |
| onFinish={handleAddFriend} |
| style={{ marginTop: '20px' }} |
| > |
| <Form.Item |
| label="用户名" |
| name="username" |
| rules={[ |
| { required: true, message: '请输入用户名' }, |
| { min: 2, message: '用户名至少2个字符' }, |
| { max: 20, message: '用户名最多20个字符' } |
| ]} |
| > |
| <Input |
| placeholder="请输入要添加的用户名" |
| prefix={<SearchOutlined />} |
| size="large" |
| /> |
| </Form.Item> |
| <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}> |
| <Button |
| onClick={() => { |
| setAddFriendVisible(false); |
| form.resetFields(); |
| }} |
| style={{ marginRight: '8px' }} |
| > |
| 取消 |
| </Button> |
| <Button |
| type="primary" |
| htmlType="submit" |
| loading={addFriendLoading} |
| > |
| 添加 |
| </Button> |
| </Form.Item> |
| </Form> |
| </Modal> |
| </div> |
| ); |
| }; |
| |
| export default MessagePage; |