Files
im-demo-server/resources/views/welcome.blade.php
2025-06-25 14:09:34 +08:00

922 lines
27 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>Nailao云 - 暗色主题聊天室</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;
}
#login-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--bg-light);
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
width: 100%;
max-width: 420px;
z-index: 10;
}
#login-container.hidden {
display: none;
}
.login-header {
text-align: center;
margin-bottom: 2rem;
}
.login-header h1 {
font-size: 1.8rem;
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #9d88ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.login-header p {
color: var(--text-secondary);
font-size: 0.9rem;
}
.login-form .input-group {
margin-bottom: 1.5rem;
}
.login-form label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: var(--text-secondary);
}
.login-form input {
width: 100%;
padding: 12px 15px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
color: var(--text-primary);
font-size: 1rem;
transition: all 0.3s;
}
.login-form input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(106, 85, 250, 0.2);
}
#login-btn {
width: 100%;
padding: 12px;
border-radius: 8px;
background: var(--accent);
color: white;
font-weight: bold;
font-size: 1rem;
border: none;
cursor: pointer;
transition: all 0.3s;
margin-top: 1rem;
}
#login-btn:hover {
background: var(--accent-hover);
transform: translateY(-2px);
}
#login-btn:active {
transform: translateY(1px);
}
.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 {
display: none;
height: 100vh;
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%;
background: linear-gradient(45deg, var(--accent), #7e6efc);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
#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 {
max-width: 70%;
padding: 12px 16px;
margin-bottom: 1rem;
border-radius: 12px;
position: relative;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.message.received {
background: var(--bg-light);
align-self: flex-start;
border-bottom-left-radius: 4px;
}
.message.sent {
background: var(--accent);
color: white;
align-self: flex-end;
border-bottom-right-radius: 4px;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
font-size: 0.8rem;
}
.username {
font-weight: bold;
}
.timestamp {
color: var(--text-secondary);
font-size: 0.7rem;
margin-left: 8px;
}
.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);
}
.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);
}
</style>
</head>
<body>
<!-- 登录页面 -->
<div id="login-container">
<div class="login-header">
<h1>Nailao云聊天室</h1>
<p>安全通讯 · 暗色主题 · 极致体验</p>
</div>
<form class="login-form" id="login-form">
<div class="input-group">
<label for="user-id">用户ID</label>
<input type="text" id="user-id" required autocomplete="off" placeholder="请输入您的唯一ID">
</div>
<div class="input-group">
<label for="user-name">用户名称</label>
<input type="text" id="user-name" required autocomplete="off" placeholder="输入您的显示名称">
</div>
<button type="submit" id="login-btn">登录聊天室</button>
</form>
</div>
<!-- 连接状态提示 -->
<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-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: ''
};
// WebSocket连接
let socket = null;
const serverUrl = "ws://t-ws.nailaoyun.cn/ws/";
// const serverUrl = "ws://127.0.0.1:18282";
// DOM元素引用
const loginContainer = document.getElementById('login-container');
const chatContainer = document.getElementById('chat-container');
const loginForm = document.getElementById('login-form');
const userIdInput = document.getElementById('user-id');
const userNameInput = document.getElementById('user-name');
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 userAvatar = document.getElementById('user-avatar');
const currentUsername = document.getElementById('current-username');
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');
// 登录时清除本地缓存
function clearStoredCredentials() {
localStorage.removeItem('chat_userId');
localStorage.removeItem('chat_userName');
}
// 初始化页面:尝试加载缓存的用户信息
function initializePage() {
const savedUserId = localStorage.getItem('chat_userId');
const savedUserName = localStorage.getItem('chat_userName');
if (savedUserId && savedUserName) {
userIdInput.value = savedUserId;
userNameInput.value = savedUserName;
}
}
// 处理用户登录
function handleLogin(e) {
e.preventDefault();
const userId = userIdInput.value.trim();
const userName = userNameInput.value.trim();
if (!userId || !userName) {
alert('请输入用户ID和名称');
return;
}
// 清除旧缓存
clearStoredCredentials();
// 保存新凭证
localStorage.setItem('chat_userId', userId);
localStorage.setItem('chat_userName', userName);
currentUser.id = userId;
currentUser.name = userName;
// 更新用户UI
userAvatar.textContent = userName.charAt(0).toUpperCase();
currentUsername.textContent = `${userName} 的聊天`;
// 隐藏登录页,显示聊天页
loginContainer.classList.add('hidden');
chatContainer.style.display = 'flex';
// 连接WebSocket
connectWebSocket();
}
// 连接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
}));
};
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':
// 如果有client_id字段不为空则缓存
if (message.client_id) {
localStorage.setItem('chat_client_id', message.client_id);
// axios请求/b
axios.post('/b', {
client_id: message.client_id,
user_id: currentUser.id
}).then(response => {
console.log('请求成功:', response.data);
}).catch(error => {
console.error('请求出错:', error);
});
}
addSystemMessage(message.content);
break;
case 'chat':
addMessage({
userId: message.userId,
name: message.userName,
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,
content: text,
timestamp: Date.now()
};
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
// 添加消息到UI自己的消息
addMessage({
userId: currentUser.id,
name: currentUser.name,
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 messageElement = document.createElement('div');
messageElement.className = `message ${msg.type}`;
// 格式化时间
const hours = msg.timestamp.getHours().toString().padStart(2, '0');
const minutes = msg.timestamp.getMinutes().toString().padStart(2, '0');
const timeString = `${hours}:${minutes}`;
messageElement.innerHTML = `
<div class="message-header">
<span class="username">${msg.name}</span>
<span class="timestamp">${timeString}</span>
</div>
<div class="message-content">${escapeHtml(msg.text)}</div>
`;
messagesContainer.appendChild(messageElement);
scrollToBottom();
}
// 更新用户列表
function updateUserList(users) {
userListContainer.innerHTML = '';
onlineCount.textContent = `(${users.length})`;
console.log(users, 'ssssssssssssss')
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">${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 userItem = document.createElement('div');
userItem.className = 'user-item';
userItem.innerHTML = `
<div class="user-avatar">${user.name.charAt(0).toUpperCase()}</div>
<div class="user-name">${user.name}</div>
<div class="status-indicator connected"></div>
`;
userListContainer.appendChild(userItem);
});
// 插入到user-list-container
}
// 初始化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();
}
clearStoredCredentials();
location.reload();
}
// 事件监听
document.addEventListener('DOMContentLoaded', () => {
initializePage();
initEmojiPicker();
// 表单提交登录
loginForm.addEventListener('submit', handleLogin);
// 发送消息
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);
});
</script>
</body>
</html>