切换房间功能
This commit is contained in:
1 parent
d86a265862
commit
d731eb4d9f
3 files changed
+211
-79
No files matched your search
+1
-1
@@ -7,7 +7,7 @@ const ws = require('ws');
|
||||
|
||||
const config = {
|
||||
rsaPrivate: crypto.createPrivateKey({
|
||||
key: Buffer.from('...generate it with key-generate.js...', 'base64'),
|
||||
key: Buffer.from('MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDMqxjzmpko9umpyWFoRjP0JcdSpPBd7IJifI5VB+sNjaFqjmQCqmVTU0qn3ChyDzV7UQIVMybvlAUmEMv9n8vqDvprWxoUZ3R+Wlnarnke10ht4eaFVl3YL4IN+x++hS0uIVK8q2VvFoTSE6fely6tUU7pF7xAIHs9TtF/iN2/KXB4WNC9/vyg8b2sjW7Ok/Blh92Hbcdtgin9T882KUiMT102KvtrjN8a+gWxfOlLd4vozdmkcEbtOpRkh7EgkMf4KaelROzXePjKct8K/YGphy+s6W8aVLR8Px7EmOaiDt0JaByJY+1wjYb5LrQQ3qi4qzSWpSb2QNiW2cjBZIZpAgMBAAECggEACHpe7HpfNU/zEm4t24aq8MZe2RjNfyqEpSVBNRsAnN1czh/rWyPpUhgNa+ImB8GDBuJuamflyT9Rrke8V3t21DF+JJPPwwAdFn3SIomfKUykKq8UoDnZZdLj+YXJRRhZrINDXN7hswYVBum7BrCvBY1sbiHmUaa2zC2XZofKHbi78iAldZW/N7HxCmWv8NlxU76X6EBza6uQPwiz3aWr3lbZAE9BQVbU1T9fyW9s11auEunciP6eTrv7IYb1318SAJ5oPj2bDqMS2qr4BpdwMHw0ny6OfNJJfPlbV66J6FfM69sgxNKIgPlBW4tcYsDOXZf2+9uOLclKZJeEg9u8oQKBgQD4KDberLO+WdHs1ctgBEvQ1cdbTxcvXmlwuPo4gMOW7y6xPaC6hQil+ngnLTOTbIbWkRfYDJ0MmkUdCLmaoh6d3VecWOFny3OjpdZmlu/2DWdwLDBbBm4QrjNIEWVL682+Yx3CeiEooakD01NEVfAXWVGqvjkQRXcC1vkTcOh1UQKBgQDTIwZs9yFAW56UVlj54DGdvYq+Wcfm//yPP0tLwShZs7EnZO/a9CJ3Hx7T5wWUIYOKi5r+hLD9/HV3l/Wn8YU/nF+HvIZ6D83Wi+jbytXn15cA4v1uAQPd/JkFxoVIclbIoi8KT3NNZoyYAcpf8qUY4/mCsgPWQG7pxpTbkImZmQKBgAUhd7j04yZ9XuzBotnSXOrtcJi/suAE2LM+79V5PBID0fhSKN+SXpJn8lIFCjrzFHqdN1v9LsYS7njiNEwxK+bhq60M3WQy/aoVhLBMUJvKvSfBkiJfqKjpoR/NVEXUk0VrcTMTKVpYtVDh7qfMCxHAhAK+uhlPBl3lqfKv8+5xAoGAJBreM24DQqUoNgeaCYjcEzRhcHGb7JWj078oGhk61DQVPD5XgeKzrhklRWlBG/arLtr2s0Vvms88KypSONjlR15vO5/ygxcGxQSB2mwSLiZAzUcDvXbw+VoDtOggzKW4z3BkiOfTOM4rhqbvu36b2ujJNx9H5rB4BwQNWoqZ7SkCgYA0W1tXV92Uh+h6DEBciPChfiCjJctNpnvDUx/QZGYm5sIp6I95dF0Qu2ufQNle2zzcPz/VRnei3zkF5ZQyBcBNvd65wqGoS0bub84Qi6BXJUmIXt72HvRsRH+ztzD8gJKWBES6kS9HucdGWCmq8WOPrWBbLRmKqrLZphSqu7CgCg==', 'base64'),
|
||||
format: 'der',
|
||||
type: 'pkcs8'
|
||||
}),
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
const config = {
|
||||
rsaPublic: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsB3/iPQiU6nvQhTMfTctJb4uCazd4Vbw0pSqPa/R2wRo5i3m8qqCQa1roTJ/n3aR1wmrR4KY7ntnMVlTWza75PB2OWAY0aKWZbAj5dsrfoT6GrCo72IXQpVGCn2RerddOQs/1rART3Y0nsL3K1SnVf0yQYh3QbWqd8if0Gpa8bBaL0Of1WnXoJyUpS3p9agbXIimdP6UayNtKh/VmkztvXr+36/f4s4zZN2hhMNeRJePpQ1MtAf+oSqlyaeYiFr5w6vphkGwNRn13oqmiXzcH4t4jSxFmEPLpZozz7JyEncOU0yBTj6iyCsrIegJkabLTqJkOXn25c2QbsddPfWvcwIDAQAB',
|
||||
wsAddress: 'ws://46.232.60.69:8088',
|
||||
rsaPublic: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzKsY85qZKPbpqclhaEYz9CXHUqTwXeyCYnyOVQfrDY2hao5kAqplU1NKp9wocg81e1ECFTMm75QFJhDL/Z/L6g76a1saFGd0flpZ2q55HtdIbeHmhVZd2C+CDfsfvoUtLiFSvKtlbxaE0hOn3pcurVFO6Re8QCB7PU7Rf4jdvylweFjQvf78oPG9rI1uzpPwZYfdh23HbYIp/U/PNilIjE9dNir7a4zfGvoFsXzpS3eL6M3ZpHBG7TqUZIexIJDH+CmnpUTs13j4ynLfCv2BqYcvrOlvGlS0fD8exJjmog7dCWgciWPtcI2G+S60EN6ouKs0lqUm9kDYltnIwWSGaQIDAQAB',
|
||||
wsAddress: 'ws://127.0.0.1:8088',
|
||||
debug: true
|
||||
};
|
||||
|
||||
|
||||
+208
-76
@@ -1,5 +1,39 @@
|
||||
import config from './config.js';
|
||||
|
||||
// 多房间状态管理
|
||||
let roomsData = [];
|
||||
let activeRoomIndex = -1;
|
||||
function getNewRoomData() {
|
||||
return { room: '', userList: [], userMap: {}, myId: null, myName: '', chat: null, messages: [] };
|
||||
}
|
||||
// 切换房间并恢复上下文,更新 UI
|
||||
function switchRoom(index) {
|
||||
if (index < 0 || index >= roomsData.length) return;
|
||||
activeRoomIndex = index;
|
||||
const rd = roomsData[index];
|
||||
currentRoom = rd.room;
|
||||
userList = rd.userList;
|
||||
userMap = rd.userMap;
|
||||
myId = rd.myId;
|
||||
myName = rd.myName;
|
||||
chat = rd.chat;
|
||||
renderRooms(index);
|
||||
renderMainHeader();
|
||||
renderUserList();
|
||||
renderChatArea();
|
||||
}
|
||||
|
||||
// 渲染当前房间消息区
|
||||
function renderChatArea() {
|
||||
const chatArea = document.getElementById('chat-area');
|
||||
if (!chatArea) return;
|
||||
chatArea.innerHTML = '';
|
||||
roomsData[activeRoomIndex].messages.forEach(m => {
|
||||
if (m.type === 'me') addMsg(m.text, true);
|
||||
else addOtherMsg(m.text, m.name, m.avatar, true);
|
||||
});
|
||||
}
|
||||
|
||||
// 生成简易SVG头像
|
||||
function getSvgAvatar(text, size = 42) {
|
||||
const color = "#30a8f7";
|
||||
@@ -35,9 +69,10 @@ function escapeHTML(str) {
|
||||
function renderUserList() {
|
||||
const userListEl = document.getElementById('member-list');
|
||||
userListEl.innerHTML = '';
|
||||
// 不再提前 return,让 main-header 始终刷新
|
||||
let me = userList.find(u => u.clientId === myId);
|
||||
let others = userList.filter(u => u.clientId !== myId);
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
if (!rd) return;
|
||||
let me = rd.userList.find(u => u.clientId === rd.myId);
|
||||
let others = rd.userList.filter(u => u.clientId !== rd.myId);
|
||||
if (me) userListEl.appendChild(createUserItem(me, true));
|
||||
others.forEach(u => userListEl.appendChild(createUserItem(u, false)));
|
||||
renderMainHeader(); // 在线用户变化时刷新
|
||||
@@ -57,27 +92,33 @@ function createUserItem(user, isMe) {
|
||||
|
||||
// 处理服务器推送的在线用户列表
|
||||
function handleClientList(list, selfId) {
|
||||
userList = list;
|
||||
userMap = {};
|
||||
userList.forEach(u => { userMap[u.clientId] = u; });
|
||||
myId = selfId;
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
if (!rd) return;
|
||||
rd.userList = list;
|
||||
rd.userMap = {};
|
||||
list.forEach(u => { rd.userMap[u.clientId] = u; });
|
||||
rd.myId = selfId;
|
||||
renderUserList();
|
||||
}
|
||||
// 新用户上线或资料变更
|
||||
function handleClientSecured(user) {
|
||||
let idx = userList.findIndex(u => u.clientId === user.clientId);
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
if (!rd) return;
|
||||
let idx = rd.userList.findIndex(u => u.clientId === user.clientId);
|
||||
if (idx === -1) {
|
||||
userList.push(user);
|
||||
rd.userList.push(user);
|
||||
} else {
|
||||
userList[idx] = user;
|
||||
rd.userList[idx] = user;
|
||||
}
|
||||
userMap[user.clientId] = user;
|
||||
rd.userMap[user.clientId] = user;
|
||||
renderUserList();
|
||||
}
|
||||
// 用户下线
|
||||
function handleClientLeft(clientId) {
|
||||
userList = userList.filter(u => u.clientId !== clientId);
|
||||
delete userMap[clientId];
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
if (!rd) return;
|
||||
rd.userList = rd.userList.filter(u => u.clientId !== clientId);
|
||||
delete rd.userMap[clientId];
|
||||
renderUserList();
|
||||
}
|
||||
|
||||
@@ -95,7 +136,9 @@ function setStatus(text) {
|
||||
statusBar.innerText = text;
|
||||
}
|
||||
|
||||
function addMsg(text) {
|
||||
function addMsg(text, isHistory = false) {
|
||||
// 只有新消息才记录
|
||||
if (!isHistory && activeRoomIndex >= 0) roomsData[activeRoomIndex].messages.push({ type: 'me', text });
|
||||
const chatArea = document.getElementById('chat-area');
|
||||
if (!chatArea) return;
|
||||
const div = document.createElement('div');
|
||||
@@ -107,7 +150,9 @@ function addMsg(text) {
|
||||
chatArea.scrollTop = chatArea.scrollHeight;
|
||||
}
|
||||
|
||||
function addOtherMsg(msg, name = '匿名', avatar = '') {
|
||||
function addOtherMsg(msg, name = '匿名', avatar = '', isHistory = false) {
|
||||
// 只有新消息才记录
|
||||
if (!isHistory && activeRoomIndex >= 0) roomsData[activeRoomIndex].messages.push({ type: 'other', text: msg, name, avatar });
|
||||
const chatArea = document.getElementById('chat-area');
|
||||
if (!chatArea) return;
|
||||
const bubbleWrap = document.createElement('div');
|
||||
@@ -126,40 +171,158 @@ function addOtherMsg(msg, name = '匿名', avatar = '') {
|
||||
chatArea.scrollTop = chatArea.scrollHeight;
|
||||
}
|
||||
|
||||
// 占位房间和成员数量,实际项目请用你的数据替换
|
||||
const ROOM_COUNT = 4;
|
||||
const MEMBER_COUNT = 8;
|
||||
|
||||
// 渲染房间列表(UI占位)
|
||||
// 渲染房间列表(动态多房间)
|
||||
function renderRooms(activeId = 0) {
|
||||
const roomList = document.getElementById("room-list");
|
||||
roomList.innerHTML = "";
|
||||
for (let i = 0; i < ROOM_COUNT; i++) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "room" + (i === activeId ? " active" : "");
|
||||
const roomList = document.getElementById('room-list');
|
||||
roomList.innerHTML = '';
|
||||
roomsData.forEach((rd, i) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'room' + (i === activeId ? ' active' : '');
|
||||
div.innerHTML = `
|
||||
<div class="info">
|
||||
<div class="title" style="font-size: 1.18em; font-weight: bold;">#Room ${i+1}</div>
|
||||
<div class="lastmsg"></div>
|
||||
<div class="title">#${escapeHTML(rd.room)}</div>
|
||||
</div>
|
||||
<div class="time"></div>
|
||||
`;
|
||||
div.onclick = () => {
|
||||
renderRooms(i);
|
||||
renderMainHeader(i);
|
||||
clearChat();
|
||||
};
|
||||
div.onclick = () => switchRoom(i);
|
||||
roomList.appendChild(div);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 登录表单提交处理
|
||||
function loginFormHandler(modal) {
|
||||
return function(e) {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const room = document.getElementById('room').value.trim();
|
||||
const password = document.getElementById('password').value.trim();
|
||||
if (!username || !room) {
|
||||
alert('请输入用户名和房间名');
|
||||
return;
|
||||
}
|
||||
// 生成房间数据并切换
|
||||
const newRd = getNewRoomData();
|
||||
newRd.room = room;
|
||||
newRd.myName = username;
|
||||
roomsData.push(newRd);
|
||||
const idx = roomsData.length - 1;
|
||||
switchRoom(idx);
|
||||
// 更新侧边栏
|
||||
const sidebarUsername = document.getElementById('sidebar-username');
|
||||
if (sidebarUsername) sidebarUsername.textContent = username;
|
||||
const avatarEl = document.getElementById('sidebar-user-avatar');
|
||||
if (avatarEl) avatarEl.innerHTML = getSvgAvatar(username, 44);
|
||||
// 隐藏登录界面
|
||||
if (modal) modal.remove();
|
||||
else document.getElementById('login-container').style.display = 'none';
|
||||
document.getElementById('chat-container').style.display = '';
|
||||
setStatus('正在连接...');
|
||||
// 初始化 ChatCrypt
|
||||
const callbacks = {
|
||||
onServerClosed: () => setStatus('服务器连接关闭'),
|
||||
onServerSecured: () => setStatus('与服务器安全连接已建立'),
|
||||
onClientSecured: (user) => handleClientSecured(user),
|
||||
onClientList: (list, selfId) => handleClientList(list, selfId),
|
||||
onClientLeft: (clientId) => handleClientLeft(clientId),
|
||||
onClientMessage: (msg) => {
|
||||
if (msg.username === newRd.myName) return;
|
||||
roomsData[idx].messages.push({ type: 'other', text: msg.data, name: msg.username, avatar: msg.username });
|
||||
if (activeRoomIndex === idx) renderChatArea();
|
||||
}
|
||||
};
|
||||
chat = new window.ChatCrypt(config, callbacks);
|
||||
chat.setCredentials(username, room, password);
|
||||
chat.connect();
|
||||
roomsData[idx].chat = chat;
|
||||
};
|
||||
}
|
||||
|
||||
// 打开新房间登录模态
|
||||
function openLoginModal() {
|
||||
const loginContainer = document.getElementById('login-container');
|
||||
// 创建一个新的modal,内容copy(不是复用)初始登录页面
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'login-modal';
|
||||
// 复制登录表单内容
|
||||
modal.innerHTML = `
|
||||
<div class="login-modal-bg"></div>
|
||||
<div class="login-modal-card">
|
||||
<button class="login-modal-close" style="position:absolute;right:10px;top:10px;font-size:22px;background:none;border:none;cursor:pointer;">×</button>
|
||||
<h1>Sign in to Chatroom</h1>
|
||||
<p class="subtips">请输入用户名、房间名,房间密码可选</p>
|
||||
<form id="login-form-modal">
|
||||
<div class="input-group">
|
||||
<label for="username-modal">Username</label>
|
||||
<input id="username-modal" type="text" autocomplete="username" required="">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="room-modal">Room Name</label>
|
||||
<input id="room-modal" type="text" required="">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="password-modal">Room Password <span class="optional">(optional)</span></label>
|
||||
<input id="password-modal" type="password" autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="login-btn">NEXT</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
// 关闭按钮
|
||||
modal.querySelector('.login-modal-close').onclick = () => modal.remove();
|
||||
// 表单提交逻辑,复用loginFormHandler但传入modal
|
||||
const form = modal.querySelector('#login-form-modal');
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
// 获取输入
|
||||
const username = document.getElementById('username-modal').value.trim();
|
||||
const room = document.getElementById('room-modal').value.trim();
|
||||
const password = document.getElementById('password-modal').value.trim();
|
||||
if (!username || !room) {
|
||||
alert('请输入用户名和房间名');
|
||||
return;
|
||||
}
|
||||
// 生成房间数据并切换
|
||||
const newRd = getNewRoomData();
|
||||
newRd.room = room;
|
||||
newRd.myName = username;
|
||||
roomsData.push(newRd);
|
||||
const idx = roomsData.length - 1;
|
||||
switchRoom(idx);
|
||||
// 更新侧边栏
|
||||
const sidebarUsername = document.getElementById('sidebar-username');
|
||||
if (sidebarUsername) sidebarUsername.textContent = username;
|
||||
const avatarEl = document.getElementById('sidebar-user-avatar');
|
||||
if (avatarEl) avatarEl.innerHTML = getSvgAvatar(username, 44);
|
||||
// 关闭modal
|
||||
modal.remove();
|
||||
document.getElementById('chat-container').style.display = '';
|
||||
setStatus('正在连接...');
|
||||
// 初始化 ChatCrypt
|
||||
const callbacks = {
|
||||
onServerClosed: () => setStatus('服务器连接关闭'),
|
||||
onServerSecured: () => setStatus('与服务器安全连接已建立'),
|
||||
onClientSecured: (user) => handleClientSecured(user),
|
||||
onClientList: (list, selfId) => handleClientList(list, selfId),
|
||||
onClientLeft: (clientId) => handleClientLeft(clientId),
|
||||
onClientMessage: (msg) => {
|
||||
if (msg.username === newRd.myName) return;
|
||||
roomsData[idx].messages.push({ type: 'other', text: msg.data, name: msg.username, avatar: msg.username });
|
||||
if (activeRoomIndex === idx) renderChatArea();
|
||||
}
|
||||
};
|
||||
chat = new window.ChatCrypt(config, callbacks);
|
||||
chat.setCredentials(username, room, password);
|
||||
chat.connect();
|
||||
roomsData[idx].chat = chat;
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染主面板房间头部(UI占位)
|
||||
function renderMainHeader() {
|
||||
// 取当前房间名和在线人数
|
||||
let roomName = currentRoom || 'Room';
|
||||
// 如果用户列表不包含自己,则显示人数+1
|
||||
let onlineCount = userList && userList.length ? userList.length : 0;
|
||||
if (!userList.some(u => u.clientId === myId)) {
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
let roomName = rd ? rd.room : 'Room';
|
||||
let onlineCount = rd && rd.userList ? rd.userList.length : 0;
|
||||
if (rd && !rd.userList.some(u => u.clientId === rd.myId)) {
|
||||
onlineCount += 1;
|
||||
}
|
||||
document.getElementById("main-header").innerHTML = `
|
||||
@@ -328,42 +491,11 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const loginForm = document.getElementById('login-form');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const room = document.getElementById('room').value.trim();
|
||||
const password = document.getElementById('password').value.trim();
|
||||
if (!username || !room) {
|
||||
alert('请输入用户名和房间名');
|
||||
return;
|
||||
}
|
||||
myName = username;
|
||||
currentRoom = room;
|
||||
loginContainer.style.display = 'none';
|
||||
chatContainer.style.display = '';
|
||||
const sidebarUsername = document.getElementById('sidebar-username');
|
||||
if (sidebarUsername) sidebarUsername.textContent = username;
|
||||
const avatar = document.getElementById('sidebar-user-avatar');
|
||||
if (avatar) avatar.innerHTML = getSvgAvatar(username, 44);
|
||||
setStatus('正在连接...');
|
||||
renderMainHeader(); // 登录后立即渲染一次
|
||||
// 初始化 ChatCrypt
|
||||
const callbacks = {
|
||||
onServerClosed: () => setStatus('服务器连接关闭'),
|
||||
onServerSecured: () => setStatus('与服务器安全连接已建立'),
|
||||
onClientSecured: (user) => handleClientSecured(user),
|
||||
onClientList: (list, selfId) => handleClientList(list, selfId),
|
||||
onClientLeft: (clientId) => handleClientLeft(clientId),
|
||||
onClientMessage: (msg) => {
|
||||
if (msg.username === myName) return;
|
||||
addOtherMsg(msg.data, msg.username, msg.username);
|
||||
}
|
||||
};
|
||||
chat = new window.ChatCrypt(config, callbacks);
|
||||
chat.setCredentials(username, room, password);
|
||||
chat.connect();
|
||||
});
|
||||
loginForm.addEventListener('submit', loginFormHandler(null));
|
||||
}
|
||||
// 绑定“进入新房间”按钮
|
||||
const joinBtn = document.querySelector('.join-room');
|
||||
if (joinBtn) joinBtn.onclick = openLoginModal;
|
||||
|
||||
setupInputPlaceholder();
|
||||
setupMoreBtnMenu();
|
||||
@@ -388,8 +520,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// 初始化
|
||||
renderRooms();
|
||||
renderRooms(activeRoomIndex);
|
||||
renderMainHeader();
|
||||
renderUserList();
|
||||
setupTabs();
|
||||
clearChat();
|
||||
renderChatArea();
|
||||
Reference in new issue
Block a user