blob: 2efbfc8ed3b5459f9a0db08d590f6be34fa2203b [file] [log] [blame]
ym9233bf06b12025-06-09 18:18:51 +08001import React, { useEffect, useState, useRef } from 'react';
2import { Input, Button, Avatar, Typography, List, Popover, message as antdMessage } from 'antd';
3import { SendOutlined, SmileOutlined } from '@ant-design/icons';
4import { getChatsBetweenUsers, createChat } from '../api/chat';
5import axios from 'axios';
6import './chat.css';
7
8const { Text } = Typography;
9const { TextArea } = Input;
10
11// 完整表情数组
12const emojis = [
13 "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "😇",
14 "🙂", "🙃", "😉", "😌", "😍", "🥰", "😘", "😗", "😙", "😚",
15 "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🤩",
16 "🥳", "😏", "😒", "😞", "😔", "😟", "😕", "🙁", "☹️", "😣",
17 "😖", "😫", "😩", "🥺", "😢", "😭", "😤", "😠", "😡", "🤬",
18 "🤯", "😳", "🥵", "🥶", "😱", "😨", "😰", "😥", "😓", "🤗",
19 "🤔", "🤭", "🤫", "🤥", "😶", "😐", "😑", "😬", "🙄", "😯",
20 "😦", "😧", "😮", "😲", "🥱", "😴", "🤤", "😪", "😵", "🤐",
21 "🥴", "🤢", "🤮", "🤧", "😷", "🤒", "🤕", "🤑", "🤠", "😈",
22 "👿", "👹", "👺", "🤡", "💩", "👻", "💀", "☠️", "👽", "👾",
23 "🤖", "🎃", "😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿",
24 "😾", "👋", "🤚", "🖐", "✋", "🖖", "👌", "🤏", "✌️", "🤞",
25 "🤟", "🤘", "🤙", "👈", "👉", "👆", "🖕", "👇", "☝️", "👍",
26 "👎", "✊", "👊", "🤛", "🤜", "👏", "🙌", "👐", "🤲", "🤝",
27 "🙏", "✍️", "💅", "🤳", "💪", "🦾", "🦵", "🦿", "🦶", "👂",
28 "🦻", "👃", "🧠", "🦷", "🦴", "👀", "👁", "👅", "👄", "💋",
29 "🩸", "💘", "💝", "💖", "💗", "💓", "💞", "💕", "💟", "❣️",
30 "💔", "❤️", "🧡", "💛", "💚", "💙", "💜", "🤎", "🖤", "🤍",
31 "💯", "💢", "💥", "💫", "💦", "💨", "🕳", "💣", "💬", "👁️‍🗨️",
32 "🗨", "🗯", "💭", "💤", "👋", "🤚", "🖐", "✋", "🖖", "👌"
33];
34
35const ChatBox = ({ senderId, receiverId }) => {
36 const [messages, setMessages] = useState([]);
37 const [input, setInput] = useState('');
38 const [showEmojis, setShowEmojis] = useState(false);
39 const messagesEndRef = useRef(null);
40 const inputRef = useRef(null);
41 const [loading, setLoading] = useState(false);
42 const [cursorPosition, setCursorPosition] = useState(0);
43 const [users, setUsers] = useState({}); // {id: {username, avatar}}
44
45 const formatTime = timeStr => new Date(timeStr).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
46
47 const fetchUserInfo = async (id) => {
48 if (!id || users[id]) return;
49 try {
50 const res = await axios.get(`http://localhost:8080/user/getDecoration?userid=${id}`);
51 const info = res.data.data;
52 setUsers(u => ({ ...u, [id]: { username: info.username, avatar: info.image } }));
53 } catch (e) {
54 console.error('获取用户信息失败', e);
55 setUsers(u => ({ ...u, [id]: { username: `用户${id}`, avatar: null } }));
56 }
57 };
58
59 const fetchMessages = async () => {
60 if (!senderId || !receiverId) return;
61 try {
62 await fetchUserInfo(senderId);
63 await fetchUserInfo(receiverId);
64 const data = await getChatsBetweenUsers(senderId, receiverId);
65 setMessages(data.sort((a, b) => new Date(a.talkTime) - new Date(b.talkTime)));
66 } catch {
67 antdMessage.error('加载聊天记录失败');
68 }
69 };
70
71 useEffect(fetchMessages, [senderId, receiverId]);
72
73 useEffect(() => {
74 messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
75 }, [messages]);
76
77 const handleSend = async () => {
78 const trimmed = input.trim();
79 if (!trimmed) return;
80 setLoading(true);
81 const newChat = { senderId, receiverId, content: trimmed, talkTime: new Date().toISOString() };
82 try {
83 await createChat(newChat);
84 setInput('');
85 fetchMessages();
86 } catch {
87 antdMessage.error('发送失败,请重试');
88 }
89 setLoading(false);
90 };
91
92 const handleKeyDown = e => {
93 if (e.key === 'Enter' && !e.shiftKey) {
94 e.preventDefault();
95 handleSend();
96 }
97 };
98
99 const handleEmojiInsert = emoji => {
100 const start = input.slice(0, cursorPosition);
101 const end = input.slice(cursorPosition);
102 const newText = start + emoji + end;
103 setInput(newText);
104 setShowEmojis(false);
105 setTimeout(() => {
106 const pos = start.length + emoji.length;
107 inputRef.current.focus();
108 inputRef.current.setSelectionRange(pos, pos);
109 setCursorPosition(pos);
110 }, 0);
111 };
112
113 const handleInputSelection = () => inputRef.current && setCursorPosition(inputRef.current.selectionStart);
114
115 const renderEmojiPicker = () => (
116 <div className="emoji-picker">
117 {emojis.map((e, i) => (
118 <span key={i} className="emoji-item" onClick={() => handleEmojiInsert(e)}>
119 {e}
120 </span>
121 ))}
122 </div>
123 );
124
125 return (
126 <div className="chatbox-container">
127 <div className="chatbox-header">
128 <Text strong style={{ fontSize: 20 }}>
129 🟢 Chat with {users[receiverId]?.username || receiverId}
130 </Text>
131 </div>
132
133 <div className="chatbox-content">
134 {messages.length === 0 ? (
135 <div className="no-messages">暂无聊天记录</div>
136 ) : (
137 <List
138 dataSource={messages}
139 renderItem={msg => {
140 const isSender = msg.senderId === senderId;
141 const otherId = isSender ? senderId : receiverId;
142 const user = users[otherId] || {};
143 const avatarContent = user.avatar ? (
144 <Avatar src={user.avatar} className="avatar" />
145 ) : (
146 <Avatar className="avatar">{String(otherId).slice(-2)}</Avatar>
147 );
148 return (
149 <List.Item key={msg.informationid} className={`chat-message ${isSender ? 'sender' : 'receiver'}`}>
150 {!isSender && avatarContent}
151 <div className="message-bubble-wrapper">
152 <div className="message-bubble">
153 <Text>{msg.content}</Text>
154 <div className="message-time">{formatTime(msg.talkTime)}</div>
155 </div>
156 </div>
157 {isSender && avatarContent}
158 </List.Item>
159 );
160 }}
161 />
162 )}
163 <div ref={messagesEndRef} />
164 </div>
165
166 <div className="chatbox-footer">
167 <Popover
168 content={renderEmojiPicker()}
169 title="选择表情"
170 trigger="click"
171 open={showEmojis}
172 onOpenChange={setShowEmojis}
173 placement="topLeft"
174 overlayClassName="emoji-popover"
175 arrow={false}
176 >
177 <Button icon={<SmileOutlined />} type="text" className="emoji-btn" />
178 </Popover>
179 <TextArea
180 ref={inputRef}
181 value={input}
182 onChange={e => { setInput(e.target.value); setCursorPosition(e.target.selectionStart); }}
183 onClick={handleInputSelection}
184 onSelect={handleInputSelection}
185 onKeyUp={handleInputSelection}
186 onKeyDown={handleKeyDown}
187 placeholder="输入消息,Enter发送,Shift+Enter换行"
188 autoSize={{ minRows: 2, maxRows: 6 }}
189 disabled={loading}
190 maxLength={500}
191 showCount
192 className="chat-input"
193 />
194 <Button type="primary" icon={<SendOutlined />} onClick={handleSend} loading={loading} disabled={!input.trim()} className="send-btn">
195 发送
196 </Button>
197 </div>
198 </div>
199 );
200};
201
202export default ChatBox;