Files
im-demo-server/resources/views/chat.blade.php
2025-06-26 10:11:43 +08:00

943 lines
29 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>奶酪云聊天室</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.5/axios.min.js"></script>
<style>
:root {
--bg-dark: #121212;
--bg-darker: #0a0a0a;
--bg-light: #1e1e1e;
--accent: #6a55fa;
--accent-hover: #7d6dfa;
--text-primary: #f0f0f0;
--text-secondary: #a0a0a0;
--success: #4ade80;
--warning: #fbbf24;
--error: #f87171;
--border: #2d2d2d;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: var(--bg-dark);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
}
.connection-status {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.05);
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 0.9rem;
backdrop-filter: blur(10px);
z-index: 5;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--text-secondary);
}
.status-indicator.connected {
background: var(--success);
}
#chat-container {
height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-dark);
}
.chat-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
background: rgba(30, 30, 30, 0.6);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 2;
}
.chat-title {
display: flex;
align-items: center;
gap: 0.8rem;
}
.chat-title h2 {
font-size: 1.2rem;
font-weight: 600;
}
.user-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
}
#logout-btn {
background: rgba(255, 255, 255, 0.08);
border: none;
color: var(--text-secondary);
padding: 8px 16px;
border-radius: 6px;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s;
}
#logout-btn:hover {
background: rgba(255, 255, 255, 0.12);
color: var(--text-primary);
}
.chat-content {
display: flex;
flex: 1;
height: calc(100vh - 140px);
}
.user-list {
width: 260px;
border-right: 1px solid var(--border);
background: var(--bg-darker);
overflow-y: auto;
padding: 1rem 0;
}
.section-title {
padding: 0 1.5rem 1rem;
color: var(--text-secondary);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.user-item {
display: flex;
align-items: center;
gap: 0.8rem;
padding: 0.8rem 1.5rem;
cursor: pointer;
transition: all 0.2s;
}
.user-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.user-item.active {
background: rgba(106, 85, 250, 0.15);
border-left: 3px solid var(--accent);
}
.user-item .status-indicator {
margin-left: auto;
}
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
display: flex;
flex-direction: column;
}
.message-container {
display: flex;
margin-bottom: 1.5rem;
animation: fadeIn 0.3s ease-out;
}
.message-container.sent {
align-self: flex-end;
flex-direction: row-reverse;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.message-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
margin: 0 12px;
}
.message-body {
max-width: 70%;
display: flex;
flex-direction: column;
}
.message-name {
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 4px;
color: var(--text-secondary);
}
.message-bubble {
padding: 12px 16px;
border-radius: 12px;
position: relative;
}
.message-container.received .message-bubble {
background: var(--bg-light);
border-bottom-left-radius: 4px;
align-self: flex-start;
}
.message-container.sent .message-bubble {
background: var(--accent);
color: white;
border-bottom-right-radius: 4px;
align-self: flex-end;
}
.message-info {
display: flex;
justify-content: flex-end;
margin-top: 5px;
}
.timestamp {
font-size: 0.7rem;
color: var(--text-secondary);
}
.message-container.sent .timestamp {
color: rgba(255, 255, 255, 0.7);
}
.message-content {
line-height: 1.5;
word-wrap: break-word;
font-size: 1rem;
}
.message-input-area {
padding: 1.5rem;
border-top: 1px solid var(--border);
background: rgba(30, 30, 30, 0.7);
position: relative;
}
.input-toolbar {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.toolbar-btn {
width: 36px;
height: 36px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.07);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
}
.toolbar-btn:hover {
background: rgba(255, 255, 255, 0.15);
color: var(--text-primary);
}
.input-container {
position: relative;
background: var(--bg-light);
border-radius: 12px;
display: flex;
padding: 4px;
}
#message-input {
flex: 1;
border: none;
background: transparent;
padding: 12px 16px;
color: var(--text-primary);
min-height: 50px;
max-height: 150px;
overflow-y: auto;
resize: none;
outline: none;
font-size: 1rem;
}
.input-actions {
display: flex;
align-items: center;
padding: 0 8px;
}
.emoji-btn {
width: 36px;
height: 36px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text-secondary);
transition: all 0.3s;
}
.emoji-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
}
#send-btn {
padding: 8px 20px;
border-radius: 8px;
background: var(--accent);
color: white;
border: none;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
margin-left: 8px;
}
#send-btn:hover {
background: var(--accent-hover);
}
.emoji-picker {
position: absolute;
bottom: 70px;
right: 50px;
width: 280px;
height: 260px;
background: var(--bg-light);
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
padding: 1rem;
overflow-y: auto;
display: none;
flex-wrap: wrap;
gap: 6px;
z-index: 10;
}
.emoji-picker.show {
display: flex;
}
.emoji-item {
font-size: 1.4rem;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.emoji-item:hover {
background: rgba(255, 255, 255, 0.1);
transform: scale(1.2);
}
.system-message {
align-self: center;
background: rgba(255, 255, 255, 0.1);
padding: 8px 16px;
border-radius: 8px;
font-size: 0.85rem;
color: var(--text-secondary);
margin: 10px 0;
text-align: center;
}
.no-messages {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-secondary);
text-align: center;
}
.no-messages i {
font-size: 4rem;
margin-bottom: 1.5rem;
color: var(--accent);
opacity: 0.3;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--bg-light);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
/* 语音录制按钮样式 */
#voice-btn {
transition: all 0.3s;
color: var(--accent);
}
#voice-btn.recording {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); box-shadow: 0 0 10px var(--accent); }
100% { transform: scale(1); }
}
</style>
</head>
<body>
<!-- 连接状态提示 -->
<div class="connection-status">
<div class="status-indicator" id="status-indicator"></div>
<span id="status-text">未连接</span>
</div>
<!-- 聊天主界面 -->
<div id="chat-container">
<div class="chat-header">
<div class="chat-title">
<div class="user-avatar" id="user-avatar">U</div>
<h2 id="current-username">奶酪云聊天室</h2>
</div>
<button id="logout-btn">退出登录</button>
</div>
<div class="chat-content">
<div class="user-list">
<div class="section-title">在线用户 <span id="online-count">(0)</span></div>
<div id="user-list-container">
<!-- 用户列表将通过JS动态生成 -->
</div>
</div>
<div class="chat-area">
<div class="messages-container" id="messages-container">
<div class="no-messages">
<i class="fas fa-comment-alt"></i>
<p>开始聊天吧!您发送的消息将在这里显示</p>
</div>
</div>
<div class="message-input-area">
<!-- 工具栏 -->
<div class="input-toolbar">
<div class="toolbar-btn" id="image-btn">
<i class="fas fa-image"></i>
</div>
<div class="toolbar-btn" id="voice-btn">
<i class="fas fa-microphone"></i>
</div>
<div class="toolbar-btn" id="attach-btn">
<i class="fas fa-paperclip"></i>
</div>
</div>
<div class="input-container">
<textarea id="message-input" placeholder="输入消息..." autocomplete="off"></textarea>
<div class="input-actions">
<div class="emoji-btn" id="emoji-btn">
<i class="far fa-smile"></i>
</div>
<button id="send-btn">发送</button>
</div>
</div>
<div class="emoji-picker" id="emoji-picker">
<!-- Emoji将通过JS动态加载 -->
</div>
</div>
</div>
</div>
</div>
<script>
// 当前用户信息
let currentUser = {
id: '',
name: ''
};
// 获取用户信息
const storedUser = sessionStorage.getItem('currentUser');
if (!storedUser) {
// 如果没有登录信息,重定向到登录页面
window.location.href = '/login';
} else {
const userData = JSON.parse(storedUser);
currentUser.id = userData.username;
currentUser.name = userData.name;
// 为用户生成头像颜色
const avatarColors = [
'#6a55fa', '#f87171', '#4ade80', '#fbbf24',
'#60a5fa', '#c084fc', '#fb923c', '#34d399'
];
const colorIndex = Math.floor(Math.random() * avatarColors.length);
currentUser.avatarColor = avatarColors[colorIndex];
// 更新用户UI
document.getElementById('user-avatar').textContent = currentUser.name.charAt(0).toUpperCase();
document.getElementById('user-avatar').style.background = currentUser.avatarColor;
document.getElementById('current-username').textContent = `${currentUser.name} 的聊天`;
}
// WebSocket连接
let socket = null;
const serverUrl = "ws://t-ws.nailaoyun.cn/ws/";
// const serverUrl = "ws://127.0.0.1:8282";
// DOM元素引用
const statusIndicator = document.getElementById('status-indicator');
const statusText = document.getElementById('status-text');
const messagesContainer = document.getElementById('messages-container');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const logoutBtn = document.getElementById('logout-btn');
const userListContainer = document.getElementById('user-list-container');
const onlineCount = document.getElementById('online-count');
const emojiBtn = document.getElementById('emoji-btn');
const emojiPicker = document.getElementById('emoji-picker');
const imageBtn = document.getElementById('image-btn');
const voiceBtn = document.getElementById('voice-btn');
const attachBtn = document.getElementById('attach-btn');
// 连接WebSocket
function connectWebSocket() {
updateStatus('连接中...', 'connecting');
socket = new WebSocket(serverUrl);
socket.onopen = () => {
updateStatus('已连接', 'connected');
// 发送登录信息
socket.send(JSON.stringify({
type: 'login',
userId: currentUser.id,
userName: currentUser.name,
avatarColor: currentUser.avatarColor
}));
};
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
handleIncomingMessage(message);
} catch (e) {
console.error('消息解析错误:', e);
}
};
socket.onerror = (error) => {
console.error('WebSocket错误:', error);
updateStatus('连接出错', 'error');
};
socket.onclose = () => {
updateStatus('连接已关闭', 'disconnected');
};
}
// 处理接收到的消息
function handleIncomingMessage(message) {
switch(message.type) {
case 'system':
addSystemMessage(message.content);
break;
case 'login_success':
let token = sessionStorage.getItem('token');
axios.post('/api/b', {
client_id: message.client_id,
user_id: token
}).then(response => {
console.log('请求成功:', response.data);
}).catch(error => {
console.error('请求出错:', error);
});
addSystemMessage(message.content);
break;
case 'chat':
addMessage({
userId: message.userId,
name: message.userName,
avatarColor: message.avatarColor,
text: message.content,
timestamp: new Date(message.timestamp),
type: message.userId === currentUser.id ? 'sent' : 'received'
});
break;
case 'user_list':
updateUserList(message.users);
break;
default:
console.log('未知消息类型:', message);
}
}
// 发送聊天消息
function sendChatMessage() {
const text = messageInput.value.trim();
if (!text) return;
const message = {
type: 'chat',
userId: currentUser.id,
userName: currentUser.name,
avatarColor: currentUser.avatarColor,
content: text,
timestamp: Date.now()
};
let token = sessionStorage.getItem('token');
if (socket && socket.readyState === WebSocket.OPEN) {
axios.post('/api/im/send-message', {
message_type: 0,
data: message
}, {
headers: {
'token': `${token}`, // 假设使用 Bearer token
'Content-Type': 'application/json' // 通常也需要指定内容类型
}
})
.then(response => {
console.log('请求成功:', response.data);
let res = response.data;
if (res.code === 0) {
// 添加消息到UI自己的消息
addMessage({
userId: currentUser.id,
name: currentUser.name,
avatarColor: currentUser.avatarColor,
text: text,
timestamp: new Date(),
type: 'sent'
});
// 清空输入框
messageInput.value = '';
// 重新聚焦输入框
messageInput.focus();
}
})
.catch(error => {
console.error('请求出错:', error);
});
// socket.send(JSON.stringify(message));
//
// // 添加消息到UI自己的消息
// addMessage({
// userId: currentUser.id,
// name: currentUser.name,
// avatarColor: currentUser.avatarColor,
// text: text,
// timestamp: new Date(),
// type: 'sent'
// });
//
// // 清空输入框
// messageInput.value = '';
//
// // 重新聚焦输入框
// messageInput.focus();
}
}
// 更新连接状态
function updateStatus(text, status) {
statusText.textContent = text;
statusIndicator.className = 'status-indicator';
switch(status) {
case 'connecting':
statusIndicator.style.backgroundColor = 'var(--warning)';
break;
case 'connected':
statusIndicator.classList.add('connected');
break;
case 'error':
case 'disconnected':
statusIndicator.style.backgroundColor = 'var(--error)';
break;
}
}
// 添加系统消息
function addSystemMessage(text) {
const noMessages = messagesContainer.querySelector('.no-messages');
if (noMessages) noMessages.remove();
const systemMessage = document.createElement('div');
systemMessage.className = 'system-message';
systemMessage.textContent = text;
messagesContainer.appendChild(systemMessage);
scrollToBottom();
}
// 添加聊天消息到UI优化后的结构
function addMessage(msg) {
const noMessages = messagesContainer.querySelector('.no-messages');
if (noMessages) noMessages.remove();
const messageContainer = document.createElement('div');
messageContainer.className = `message-container ${msg.type}`;
// 格式化时间
const hours = msg.timestamp.getHours().toString().padStart(2, '0');
const minutes = msg.timestamp.getMinutes().toString().padStart(2, '0');
const timeString = `${hours}:${minutes}`;
messageContainer.innerHTML = `
<div class="message-avatar" style="background: ${msg.avatarColor}">${msg.name.charAt(0).toUpperCase()}</div>
<div class="message-body">
<div class="message-name">${msg.name}</div>
<div class="message-bubble">
<div class="message-content">${escapeHtml(msg.text)}</div>
</div>
<div class="message-info">
<span class="timestamp">${timeString}</span>
</div>
</div>
`;
messagesContainer.appendChild(messageContainer);
scrollToBottom();
}
// 更新用户列表
function updateUserList(users) {
userListContainer.innerHTML = '';
onlineCount.textContent = `(${users.length})`;
if (users.length === 0) {
userListContainer.innerHTML = '<div class="no-users">暂无在线用户</div>';
return;
}
// 当前用户显示在顶部
const currentUserItem = document.createElement('div');
currentUserItem.className = 'user-item active';
currentUserItem.innerHTML = `
<div class="user-avatar" style="background: ${currentUser.avatarColor}">${currentUser.name.charAt(0).toUpperCase()}</div>
<div class="user-name">${currentUser.name} (我)</div>
<div class="status-indicator connected"></div>
`;
userListContainer.appendChild(currentUserItem);
// 添加其他用户
users.filter(user => user.id != currentUser.id).forEach(user => {
const avatarColor = user.avatarColor || '#6a55fa';
const userItem = document.createElement('div');
userItem.className = 'user-item';
userItem.innerHTML = `
<div class="user-avatar" style="background: ${avatarColor}">${user.name.charAt(0).toUpperCase()}</div>
<div class="user-name">${user.name}</div>
<div class="status-indicator connected"></div>
`;
userListContainer.appendChild(userItem);
});
}
// 初始化Emoji选择器
function initEmojiPicker() {
// 常见的emoji
const emojis = [
'😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣',
'😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰',
'😘', '😗', '😋', '😛', '😜', '🤪', '😝', '🤑',
'🤗', '🤭', '🤫', '🤔', '🤐', '🤨', '😐', '😑',
'😶', '😏', '😒', '🙄', '😬', '🤥', '😲', '😮',
'😦', '😧', '😳', '😵', '😖', '😫', '😩', '🥺',
'😢', '😭', '😤', '😠', '😡', '🤬', '🤯', '😳',
'🥵', '🥶', '😨', '😰', '😥', '😓', '🤗', '🤔',
'🫣', '🤭', '🤫', '🫢', '🫠', '😶‍🌫️', '😐', '😑',
'🙈', '🙉', '🙊', '❤️', '💛', '💚', '💙', '💜',
'💔', '💋', '💯', '💢', '💥', '💫', '💦', '💨',
'👍', '👎', '👌', '✌️', '🤞', '🤟', '🤘', '🤙',
'👋', '🤚', '🖐️', '✋', '🖖', '👏', '🙌', '👐',
'🤲', '🤝', '🙏', '💪', '🦾', '🦿', '🦵', '🦶'
];
emojiPicker.innerHTML = '';
emojis.forEach(emoji => {
const emojiItem = document.createElement('div');
emojiItem.className = 'emoji-item';
emojiItem.textContent = emoji;
emojiItem.addEventListener('click', () => {
messageInput.value += emoji;
emojiPicker.classList.remove('show');
});
emojiPicker.appendChild(emojiItem);
});
}
// 辅助函数HTML转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 滚动到底部
function scrollToBottom() {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 退出登录
function handleLogout() {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.close();
}
sessionStorage.removeItem('currentUser');
window.location.href = '/login';
}
// 语音录制功能
function handleVoiceRecording() {
if (!voiceBtn.classList.contains('recording')) {
// 开始录制
voiceBtn.classList.add('recording');
voiceBtn.innerHTML = '<i class="fas fa-square"></i>';
document.getElementById('voice-btn').style.backgroundColor = 'rgba(248, 113, 113, 0.2)';
// 这里应该添加实际录制逻辑
setTimeout(() => {
// 模拟完成录制
handleVoiceRecordingEnd();
}, 3000);
}
}
function handleVoiceRecordingEnd() {
voiceBtn.classList.remove('recording');
voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>';
document.getElementById('voice-btn').style.backgroundColor = '';
}
// 事件监听
document.addEventListener('DOMContentLoaded', () => {
// 初始化
initEmojiPicker();
// 连接WebSocket
connectWebSocket();
// 发送消息
sendBtn.addEventListener('click', sendChatMessage);
// Enter发送消息Shift+Enter换行
messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendChatMessage();
}
});
// Emoji选择器
emojiBtn.addEventListener('click', (e) => {
emojiPicker.classList.toggle('show');
e.stopPropagation();
});
// 点击页面其他地方关闭emoji选择器
document.addEventListener('click', (e) => {
if (emojiPicker.classList.contains('show') && !emojiPicker.contains(e.target) && !emojiBtn.contains(e.target)) {
emojiPicker.classList.remove('show');
}
});
// 退出登录
logoutBtn.addEventListener('click', handleLogout);
// 图片按钮功能
imageBtn.addEventListener('click', () => {
const message = "图片上传功能(实际应用中需要实现上传功能)";
addSystemMessage(message);
});
// 语音按钮功能
voiceBtn.addEventListener('click', handleVoiceRecording);
// 附件按钮功能
attachBtn.addEventListener('click', () => {
const message = "附件上传功能(实际应用中需要实现上传功能)";
addSystemMessage(message);
});
});
</script>
</body>
</html>