blob: b07630a0418cd43da8f172517dd36d9c05442b70 [file] [log] [blame]
BirdNETM632c0612025-05-27 17:41:40 +08001import React, { useState, useEffect } from 'react';
2import { Card, List, Avatar, Input, Button, Row, Col, message } from 'antd';
3import { SysUserMessage, ChatContact } from './data.d';
4import { getChatContactList, getChatHistory, sendMessage, getUserInfo } from './service';
5import './index.less';
6
7const MessagePage: React.FC = () => {
8 const [chatContacts, setChatContacts] = useState<ChatContact[]>([]);
9 const [selectedUserId, setSelectedUserId] = useState<number | null>(null);
10 const [inputMessage, setInputMessage] = useState('');
11 const [chatMessages, setChatMessages] = useState<SysUserMessage[]>([]);
12 const [loading, setLoading] = useState(false);
13 const [sending, setSending] = useState(false);
14
15 // 获取聊天对象列表
16 const fetchChatContacts = async () => {
17 try {
18 setLoading(true);
19 const response = await getChatContactList();
20
21 // 按最后消息时间排序
22 const sortedContacts = response.sort((a: ChatContact, b: ChatContact) =>
23 new Date(b.lastMessageTime).getTime() - new Date(a.lastMessageTime).getTime()
24 );
25 setChatContacts(sortedContacts);
26 } catch (error) {
27 message.error('获取聊天列表失败');
28 } finally {
29 setLoading(false);
30 }
31 };
32
33 // 获取与特定用户的聊天记录
34 const fetchChatHistory = async (userId: number) => {
35 try {
36 setLoading(true);
37 const response = await getChatHistory({
38 userId,
39 currentUserId: 1, // 假设当前用户ID为1,实际应该从用户状态获取
40 pageSize: 100 // 获取最近100条消息
41 });
42
43 // 按时间排序
44 const sortedMessages = response.sort((a: SysUserMessage, b: SysUserMessage) =>
45 new Date(a.createTime).getTime() - new Date(b.createTime).getTime()
46 );
47 setChatMessages(sortedMessages);
48 } catch (error) {
49 message.error('获取聊天记录失败');
50 } finally {
51 setLoading(false);
52 }
53 };
54
55 // 发送消息
56 const handleSendMessage = async () => {
57 if (!inputMessage.trim() || !selectedUserId || sending) {
58 return;
59 }
60
61 try {
62 setSending(true);
63
64 // 先在界面上显示消息(乐观更新)
65 const tempMessage: SysUserMessage = {
66 messageId: Date.now(), // 临时ID
67 senderId: 1, // 当前用户ID
68 receiverId: selectedUserId,
69 content: inputMessage,
70 createTime: new Date(),
71 delFlag: '0'
72 };
73 setChatMessages(prev => [...prev, tempMessage]);
74
75 // 清空输入框
76 const messageContent = inputMessage;
77 setInputMessage('');
78
79 // 调用API发送消息
80 await sendMessage({
81 receiverId: selectedUserId,
82 content: messageContent
83 });
84
85 // 更新聊天对象列表中的最后消息
86 setChatContacts(prevContacts => {
87 const updatedContacts = prevContacts.map(contact =>
88 contact.userId === selectedUserId
89 ? {
90 ...contact,
91 lastMessage: messageContent,
92 lastMessageTime: new Date()
93 }
94 : contact
95 );
96 // 重新排序,将当前聊天对象移到最前面
97 return updatedContacts.sort((a, b) =>
98 new Date(b.lastMessageTime).getTime() - new Date(a.lastMessageTime).getTime()
99 );
100 });
101
102 message.success('消息发送成功');
103
104 } catch (error) {
105 message.error('发送消息失败');
106 // 发送失败时,移除临时消息
107 setChatMessages(prev => prev.filter(msg => msg.messageId !== Date.now()));
108 } finally {
109 setSending(false);
110 }
111 };
112
113 // 选择聊天对象
114 const handleSelectUser = (userId: number) => {
115 setSelectedUserId(userId);
116 fetchChatHistory(userId);
117 };
118
119 // 格式化时间显示
120 const formatTime = (time: Date | string) => {
121 const date = new Date(time);
122 const now = new Date();
123 const diff = now.getTime() - date.getTime();
124 const minutes = Math.floor(diff / (1000 * 60));
125 const hours = Math.floor(diff / (1000 * 60 * 60));
126 const days = Math.floor(diff / (1000 * 60 * 60 * 24));
127
128 if (minutes < 60) {
129 return `${minutes}分钟前`;
130 } else if (hours < 24) {
131 return `${hours}小时前`;
132 } else {
133 return `${days}天前`;
134 }
135 };
136
137 // 获取用户名
138 const getUserName = (userId: number) => {
139 const contact = chatContacts.find(c => c.userId === userId);
140 return contact?.userName || `用户${userId}`;
141 };
142
143 // 处理回车键发送
144 const handleKeyPress = (e: React.KeyboardEvent) => {
145 if (e.key === 'Enter' && !e.shiftKey) {
146 e.preventDefault();
147 handleSendMessage();
148 }
149 };
150
151 useEffect(() => {
152 fetchChatContacts();
153 }, []);
154
155 return (
156 <div className="message-page" style={{ height: '100vh', padding: '16px', display: 'flex', flexDirection: 'column' }}>
157 <Row gutter={16} style={{ flex: 1, overflow: 'hidden' }}>
158 {/* 左侧聊天对象列表 */}
159 <Col span={8}>
160 <Card
161 title="聊天列表"
162 bordered={false}
163 style={{ height: '100%' }}
164 loading={loading && !selectedUserId}
165 >
166 <List
167 style={{ height: 'calc(100vh - 140px)', overflowY: 'auto' }}
168 dataSource={chatContacts}
169 renderItem={(contact) => (
170 <List.Item
171 onClick={() => handleSelectUser(contact.userId)}
172 style={{
173 cursor: 'pointer',
174 backgroundColor: selectedUserId === contact.userId ? '#f0f8ff' : 'transparent',
175 padding: '12px',
176 borderRadius: '8px',
177 margin: '4px 0'
178 }}
179 className={selectedUserId === contact.userId ? 'selected' : ''}
180 >
181 <List.Item.Meta
182 avatar={
183 <Avatar size="large">
184 {contact.userName.charAt(0)}
185 </Avatar>
186 }
187 title={
188 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
189 <span style={{
190 fontWeight: 'normal',
191 fontSize: '14px'
192 }}>
193 {contact.userName}
194 </span>
195 <span style={{ fontSize: '12px', color: '#999' }}>
196 {formatTime(contact.lastMessageTime)}
197 </span>
198 </div>
199 }
200 description={
201 <div style={{
202 color: '#666',
203 fontWeight: 'normal',
204 overflow: 'hidden',
205 textOverflow: 'ellipsis',
206 whiteSpace: 'nowrap',
207 fontSize: '13px'
208 }}>
209 {contact.lastMessage}
210 </div>
211 }
212 />
213 </List.Item>
214 )}
215 />
216 </Card>
217 </Col>
218
219 {/* 右侧聊天界面 */}
220 <Col span={16}>
221 <div style={{
222 height: '100%',
223 display: 'flex',
224 flexDirection: 'column',
225 position: 'relative'
226 }}>
227 {/* 聊天标题 */}
228 <Card
229 title={selectedUserId ? `与 ${getUserName(selectedUserId)} 的对话` : '选择一个联系人开始聊天'}
230 bordered={false}
231 style={{
232 marginBottom: 0,
233 borderBottom: '1px solid #f0f0f0'
234 }}
235 bodyStyle={{ padding: 0 }}
236 />
237
238 {/* 聊天消息区域 */}
239 <div style={{
240 flex: 1,
241 overflow: 'hidden',
242 display: 'flex',
243 flexDirection: 'column',
244 backgroundColor: '#fff',
245 border: '1px solid #f0f0f0',
246 borderTop: 'none'
247 }}>
248 {selectedUserId ? (
249 <>
250 <List
251 style={{
252 flex: 1,
253 overflowY: 'auto',
254 padding: '16px',
255 paddingBottom: '80px' // 为输入框预留空间
256 }}
257 dataSource={chatMessages}
258 loading={loading && selectedUserId !== null}
259 renderItem={(item) => (
260 <List.Item
261 className={`chat-message ${item.senderId === 1 ? 'sent' : 'received'}`}
262 style={{
263 border: 'none',
264 padding: '8px 0',
265 display: 'flex',
266 justifyContent: item.senderId === 1 ? 'flex-end' : 'flex-start'
267 }}
268 >
269 <div style={{
270 maxWidth: '70%',
271 display: 'flex',
272 flexDirection: item.senderId === 1 ? 'row-reverse' : 'row',
273 alignItems: 'flex-start',
274 gap: '8px'
275 }}>
276 <Avatar size="small">
277 {item.senderId === 1 ? 'Me' : getUserName(item.senderId).charAt(0)}
278 </Avatar>
279 <div>
280 <div style={{
281 fontSize: '12px',
282 color: '#999',
283 marginBottom: '4px',
284 textAlign: item.senderId === 1 ? 'right' : 'left'
285 }}>
286 {item.senderId === 1 ? '我' : getUserName(item.senderId)} · {new Date(item.createTime).toLocaleTimeString()}
287 </div>
288 <div style={{
289 backgroundColor: item.senderId === 1 ? '#1890ff' : '#f0f0f0',
290 color: item.senderId === 1 ? '#fff' : '#000',
291 padding: '8px 12px',
292 borderRadius: '12px',
293 wordBreak: 'break-word',
294 lineHeight: '1.4'
295 }}>
296 {item.content}
297 </div>
298 </div>
299 </div>
300 </List.Item>
301 )}
302 />
303
304 {/* 输入框区域 - 固定在底部 */}
305 <div style={{
306 position: 'absolute',
307 bottom: 0,
308 left: 0,
309 right: 0,
310 backgroundColor: '#fff',
311 padding: '16px',
312 borderTop: '1px solid #f0f0f0',
313 display: 'flex',
314 gap: '8px',
315 zIndex: 10,
316 boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.1)'
317 }}>
318 <Input.TextArea
319 value={inputMessage}
320 onChange={(e) => setInputMessage(e.target.value)}
321 onKeyDown={handleKeyPress}
322 placeholder="输入消息...(按 Enter 发送,Shift+Enter 换行)"
323 style={{
324 flex: 1,
325 resize: 'none',
326 minHeight: '40px',
327 maxHeight: '120px'
328 }}
329 autoSize={{ minRows: 1, maxRows: 4 }}
330 />
331 <Button
332 type="primary"
333 onClick={handleSendMessage}
334 loading={sending}
335 style={{ height: '40px' }}
336 >
337 发送
338 </Button>
339 </div>
340 </>
341 ) : (
342 <div style={{
343 flex: 1,
344 display: 'flex',
345 alignItems: 'center',
346 justifyContent: 'center',
347 color: '#999',
348 fontSize: '16px'
349 }}>
350 请从左侧选择一个联系人开始聊天
351 </div>
352 )}
353 </div>
354 </div>
355 </Col>
356 </Row>
357 </div>
358 );
359};
360
361export default MessagePage;