blob: b07630a0418cd43da8f172517dd36d9c05442b70 [file] [log] [blame]
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;