blob: 2efbfc8ed3b5459f9a0db08d590f6be34fa2203b [file] [log] [blame]
import React, { useEffect, useState, useRef } from 'react';
import { Input, Button, Avatar, Typography, List, Popover, message as antdMessage } from 'antd';
import { SendOutlined, SmileOutlined } from '@ant-design/icons';
import { getChatsBetweenUsers, createChat } from '../api/chat';
import axios from 'axios';
import './chat.css';
const { Text } = Typography;
const { TextArea } = Input;
// 完整表情数组
const emojis = [
"😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "😇",
"🙂", "🙃", "😉", "😌", "😍", "🥰", "😘", "😗", "😙", "😚",
"😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🤩",
"🥳", "😏", "😒", "😞", "😔", "😟", "😕", "🙁", "☹️", "😣",
"😖", "😫", "😩", "🥺", "😢", "😭", "😤", "😠", "😡", "🤬",
"🤯", "😳", "🥵", "🥶", "😱", "😨", "😰", "😥", "😓", "🤗",
"🤔", "🤭", "🤫", "🤥", "😶", "😐", "😑", "😬", "🙄", "😯",
"😦", "😧", "😮", "😲", "🥱", "😴", "🤤", "😪", "😵", "🤐",
"🥴", "🤢", "🤮", "🤧", "😷", "🤒", "🤕", "🤑", "🤠", "😈",
"👿", "👹", "👺", "🤡", "💩", "👻", "💀", "☠️", "👽", "👾",
"🤖", "🎃", "😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿",
"😾", "👋", "🤚", "🖐", "✋", "🖖", "👌", "🤏", "✌️", "🤞",
"🤟", "🤘", "🤙", "👈", "👉", "👆", "🖕", "👇", "☝️", "👍",
"👎", "✊", "👊", "🤛", "🤜", "👏", "🙌", "👐", "🤲", "🤝",
"🙏", "✍️", "💅", "🤳", "💪", "🦾", "🦵", "🦿", "🦶", "👂",
"🦻", "👃", "🧠", "🦷", "🦴", "👀", "👁", "👅", "👄", "💋",
"🩸", "💘", "💝", "💖", "💗", "💓", "💞", "💕", "💟", "❣️",
"💔", "❤️", "🧡", "💛", "💚", "💙", "💜", "🤎", "🖤", "🤍",
"💯", "💢", "💥", "💫", "💦", "💨", "🕳", "💣", "💬", "👁️‍🗨️",
"🗨", "🗯", "💭", "💤", "👋", "🤚", "🖐", "✋", "🖖", "👌"
];
const ChatBox = ({ senderId, receiverId }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [showEmojis, setShowEmojis] = useState(false);
const messagesEndRef = useRef(null);
const inputRef = useRef(null);
const [loading, setLoading] = useState(false);
const [cursorPosition, setCursorPosition] = useState(0);
const [users, setUsers] = useState({}); // {id: {username, avatar}}
const formatTime = timeStr => new Date(timeStr).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const fetchUserInfo = async (id) => {
if (!id || users[id]) return;
try {
const res = await axios.get(`http://localhost:8080/user/getDecoration?userid=${id}`);
const info = res.data.data;
setUsers(u => ({ ...u, [id]: { username: info.username, avatar: info.image } }));
} catch (e) {
console.error('获取用户信息失败', e);
setUsers(u => ({ ...u, [id]: { username: `用户${id}`, avatar: null } }));
}
};
const fetchMessages = async () => {
if (!senderId || !receiverId) return;
try {
await fetchUserInfo(senderId);
await fetchUserInfo(receiverId);
const data = await getChatsBetweenUsers(senderId, receiverId);
setMessages(data.sort((a, b) => new Date(a.talkTime) - new Date(b.talkTime)));
} catch {
antdMessage.error('加载聊天记录失败');
}
};
useEffect(fetchMessages, [senderId, receiverId]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSend = async () => {
const trimmed = input.trim();
if (!trimmed) return;
setLoading(true);
const newChat = { senderId, receiverId, content: trimmed, talkTime: new Date().toISOString() };
try {
await createChat(newChat);
setInput('');
fetchMessages();
} catch {
antdMessage.error('发送失败,请重试');
}
setLoading(false);
};
const handleKeyDown = e => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
const handleEmojiInsert = emoji => {
const start = input.slice(0, cursorPosition);
const end = input.slice(cursorPosition);
const newText = start + emoji + end;
setInput(newText);
setShowEmojis(false);
setTimeout(() => {
const pos = start.length + emoji.length;
inputRef.current.focus();
inputRef.current.setSelectionRange(pos, pos);
setCursorPosition(pos);
}, 0);
};
const handleInputSelection = () => inputRef.current && setCursorPosition(inputRef.current.selectionStart);
const renderEmojiPicker = () => (
<div className="emoji-picker">
{emojis.map((e, i) => (
<span key={i} className="emoji-item" onClick={() => handleEmojiInsert(e)}>
{e}
</span>
))}
</div>
);
return (
<div className="chatbox-container">
<div className="chatbox-header">
<Text strong style={{ fontSize: 20 }}>
🟢 Chat with {users[receiverId]?.username || receiverId}
</Text>
</div>
<div className="chatbox-content">
{messages.length === 0 ? (
<div className="no-messages">暂无聊天记录</div>
) : (
<List
dataSource={messages}
renderItem={msg => {
const isSender = msg.senderId === senderId;
const otherId = isSender ? senderId : receiverId;
const user = users[otherId] || {};
const avatarContent = user.avatar ? (
<Avatar src={user.avatar} className="avatar" />
) : (
<Avatar className="avatar">{String(otherId).slice(-2)}</Avatar>
);
return (
<List.Item key={msg.informationid} className={`chat-message ${isSender ? 'sender' : 'receiver'}`}>
{!isSender && avatarContent}
<div className="message-bubble-wrapper">
<div className="message-bubble">
<Text>{msg.content}</Text>
<div className="message-time">{formatTime(msg.talkTime)}</div>
</div>
</div>
{isSender && avatarContent}
</List.Item>
);
}}
/>
)}
<div ref={messagesEndRef} />
</div>
<div className="chatbox-footer">
<Popover
content={renderEmojiPicker()}
title="选择表情"
trigger="click"
open={showEmojis}
onOpenChange={setShowEmojis}
placement="topLeft"
overlayClassName="emoji-popover"
arrow={false}
>
<Button icon={<SmileOutlined />} type="text" className="emoji-btn" />
</Popover>
<TextArea
ref={inputRef}
value={input}
onChange={e => { setInput(e.target.value); setCursorPosition(e.target.selectionStart); }}
onClick={handleInputSelection}
onSelect={handleInputSelection}
onKeyUp={handleInputSelection}
onKeyDown={handleKeyDown}
placeholder="输入消息,Enter发送,Shift+Enter换行"
autoSize={{ minRows: 2, maxRows: 6 }}
disabled={loading}
maxLength={500}
showCount
className="chat-input"
/>
<Button type="primary" icon={<SendOutlined />} onClick={handleSend} loading={loading} disabled={!input.trim()} className="send-btn">
发送
</Button>
</div>
</div>
);
};
export default ChatBox;