评论连接优化,新增私信页面,等私信后端写完
Change-Id: I63c05945c47be9bcba6113ddd299058f302cb927
diff --git a/react-ui/src/pages/Message/index.tsx b/react-ui/src/pages/Message/index.tsx
new file mode 100644
index 0000000..b07630a
--- /dev/null
+++ b/react-ui/src/pages/Message/index.tsx
@@ -0,0 +1,361 @@
+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;
\ No newline at end of file