This commit is contained in:
2026-05-24 13:03:22 +08:00
parent a37fba5519
commit 20760a2668
2 changed files with 258 additions and 234 deletions

View File

@@ -1,8 +1,3 @@
/**
* connect视图逻辑
* 处理初始连接界面的UI、用户设置、WebSocket连接状态显示
*/
import { showNotification } from './utils.js'; import { showNotification } from './utils.js';
import store from './store.js'; import store from './store.js';
import { import {
@@ -11,25 +6,20 @@ import {
renderConnectionIds, renderConnectionIds,
renderOnlineUsers renderOnlineUsers
} from './connect-directory.js'; } from './connect-directory.js';
import { createProfileSettingsController } from './profile-settings.js';
const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB
// WebSocket连接状态更新回调
let onWsStatusChange = null; let onWsStatusChange = null;
let cachedOnlineUsers = []; let cachedOnlineUsers = [];
/** const profileSettingsController = createProfileSettingsController({
* 设置WebSocket状态变化回调 store,
* @param {function} callback - 回调函数(connected: boolean) notify: showNotification
*/ });
export function setWsStatusCallback(callback) { export function setWsStatusCallback(callback) {
onWsStatusChange = callback; onWsStatusChange = callback;
} }
/**
* 更新WebSocket状态显示
* @param {boolean} connected - 是否已连接
*/
export function updateWsStatus(connected) { export function updateWsStatus(connected) {
const wsStatusDot = document.getElementById('wsStatusDot'); const wsStatusDot = document.getElementById('wsStatusDot');
const wsStatusText = document.getElementById('wsStatusText'); const wsStatusText = document.getElementById('wsStatusText');
@@ -49,9 +39,6 @@ export function updateWsStatus(connected) {
} }
} }
/**
* 初始化WebSocket连接页面加载时调用
*/
export async function initWebSocket() { export async function initWebSocket() {
try { try {
await store.connectSignaling(); await store.connectSignaling();
@@ -66,14 +53,11 @@ export async function initWebSocket() {
} }
} }
/**
* 获取全部在线WebSocket用户
* @param {boolean} silent - 是否静默刷新
*/
async function refreshOnlineUsers(silent = true) { async function refreshOnlineUsers(silent = true) {
try { try {
cachedOnlineUsers = await fetchOnlineUsers(); cachedOnlineUsers = await fetchOnlineUsers();
updateOnlineUsersList(cachedOnlineUsers); updateOnlineUsersList(cachedOnlineUsers);
if (!silent) { if (!silent) {
showNotification(`当前共有 ${cachedOnlineUsers.length} 个WebSocket用户在线`); showNotification(`当前共有 ${cachedOnlineUsers.length} 个WebSocket用户在线`);
} }
@@ -85,11 +69,9 @@ async function refreshOnlineUsers(silent = true) {
} }
} }
/**
* 获取所有连接ID
*/
async function getAllConnectionIds() { async function getAllConnectionIds() {
showNotification('正在获取连接ID和在线用户...'); showNotification('正在获取连接ID和在线用户...');
try { try {
const { connectionIds, users } = await fetchConnectionDirectory(); const { connectionIds, users } = await fetchConnectionDirectory();
cachedOnlineUsers = users; cachedOnlineUsers = users;
@@ -101,10 +83,6 @@ async function getAllConnectionIds() {
} }
} }
/**
* 显示连接ID列表
* @param {string[]} connectionIds - 连接ID数组
*/
function updateConnectionIdList(connectionIds) { function updateConnectionIdList(connectionIds) {
const idsContainer = document.getElementById('idsContainer'); const idsContainer = document.getElementById('idsContainer');
const connectionIdsList = document.getElementById('connectionIdsList'); const connectionIdsList = document.getElementById('connectionIdsList');
@@ -131,10 +109,6 @@ function getCurrentUserId() {
} }
} }
/**
* 显示全部在线WebSocket用户
* @param {Array} users - 在线用户列表
*/
function updateOnlineUsersList(users) { function updateOnlineUsersList(users) {
const onlineUsersList = document.getElementById('onlineUsersList'); const onlineUsersList = document.getElementById('onlineUsersList');
const usersContainer = document.getElementById('usersContainer'); const usersContainer = document.getElementById('usersContainer');
@@ -149,253 +123,86 @@ function updateOnlineUsersList(users) {
}); });
} }
/** function selectConnectionId(connectionId) {
* 选择连接ID
* @param {string} id - 连接ID
*/
function selectConnectionId(id) {
const connectionIdInput = document.getElementById('connectionIdInput'); const connectionIdInput = document.getElementById('connectionIdInput');
if (connectionIdInput) { if (connectionIdInput) {
connectionIdInput.value = id; connectionIdInput.value = connectionId;
showNotification(`已选择连接ID: ${id}`); showNotification(`已选择连接ID: ${connectionId}`);
} }
} }
/**
* 生成8位用户ID
* @returns {string} 用户ID
*/
function generateUserId() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = 'user_';
for (let i = 0; i < 8; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* 加载用户设置
*/
export function loadUserSettings() { export function loadUserSettings() {
const defaultAvatar = '/images/p1.png'; profileSettingsController.loadUserSettings();
const userSettings = localStorage.getItem('userSettings');
if (userSettings) {
try {
const settings = JSON.parse(userSettings);
if (settings.userId) {
const userIdInput = document.getElementById('userIdInput');
if (userIdInput) userIdInput.value = settings.userId;
}
if (settings.name) {
const nicknameInput = document.getElementById('nicknameInput');
const userName = document.getElementById('userName');
if (nicknameInput) nicknameInput.value = settings.name;
if (userName) userName.textContent = settings.name;
}
const avatar = settings.avatar || defaultAvatar;
const userAvatar = document.getElementById('userAvatar');
const avatarPreview = document.getElementById('avatarPreview');
if (userAvatar) userAvatar.src = avatar;
if (avatarPreview) avatarPreview.src = avatar;
} catch (error) {
console.error('Error loading user settings:', error);
const userAvatar = document.getElementById('userAvatar');
const avatarPreview = document.getElementById('avatarPreview');
if (userAvatar) userAvatar.src = defaultAvatar;
if (avatarPreview) avatarPreview.src = defaultAvatar;
}
} else {
const newUserId = generateUserId();
const userIdInput = document.getElementById('userIdInput');
if (userIdInput) userIdInput.value = newUserId;
const userAvatar = document.getElementById('userAvatar');
const avatarPreview = document.getElementById('avatarPreview');
if (userAvatar) userAvatar.src = defaultAvatar;
if (avatarPreview) avatarPreview.src = defaultAvatar;
saveSettings();
}
} }
/**
* 保存用户设置
*/
export function saveSettings() { export function saveSettings() {
const defaultAvatar = '/images/p1.png'; profileSettingsController.saveSettings();
const nicknameInput = document.getElementById('nicknameInput');
const userIdInput = document.getElementById('userIdInput');
const avatarPreview = document.getElementById('avatarPreview');
const userName = document.getElementById('userName');
const userAvatar = document.getElementById('userAvatar');
const settings = {
userId: userIdInput ? userIdInput.value : generateUserId(),
name: nicknameInput ? (nicknameInput.value || '我') : '我',
avatar: avatarPreview ? (avatarPreview.src || defaultAvatar) : defaultAvatar
};
localStorage.setItem('userSettings', JSON.stringify(settings));
store.syncSocketUserInfo(settings);
if (userName) userName.textContent = settings.name;
if (userAvatar) userAvatar.src = settings.avatar;
showNotification('设置已保存', 'success');
} }
/**
* 处理头像上传
* @param {Event} event - 文件选择事件
*/
export function handleAvatarUpload(event) { export function handleAvatarUpload(event) {
const file = event.target.files[0]; profileSettingsController.handleAvatarUpload(event);
if (!file) return;
if (!file.type.startsWith('image/')) {
showNotification('请选择图片文件', 'error');
return;
}
if (file.size > MAX_AVATAR_SIZE) {
showNotification('图片大小不能超过2MB', 'error');
return;
}
const formData = new FormData();
formData.append('avatar', file);
const userIdInput = document.getElementById('userIdInput');
if (userIdInput) {
formData.append('userId', userIdInput.value);
}
showNotification('正在上传头像...');
fetch('/api/upload/avatar', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) throw new Error('上传失败');
return response.json();
})
.then(data => {
if (data.success && data.avatarUrl) {
const avatarUrl = data.avatarUrl;
const avatarPreview = document.getElementById('avatarPreview');
const userAvatar = document.getElementById('userAvatar');
if (avatarPreview) avatarPreview.src = avatarUrl;
if (userAvatar) userAvatar.src = avatarUrl;
saveSettings();
showNotification('头像上传成功', 'success');
} else {
throw new Error('上传失败:' + (data.message || '未知错误'));
}
})
.catch(error => {
console.error('Error uploading avatar:', error);
showNotification('头像上传失败,请重试', 'error');
const defaultAvatar = '/images/p1.png';
const avatarPreview = document.getElementById('avatarPreview');
if (avatarPreview) avatarPreview.src = defaultAvatar;
});
} }
/**
* 复制用户ID到剪贴板
*/
export function copyUserId() { export function copyUserId() {
const userIdInput = document.getElementById('userIdInput'); profileSettingsController.copyUserId();
if (userIdInput) {
userIdInput.select();
document.execCommand('copy');
showNotification('用户ID已复制到剪贴板', 'success');
}
} }
/**
* 切换设置菜单
*/
export function toggleSettingsMenu() { export function toggleSettingsMenu() {
const settingsMenu = document.getElementById('settingsMenu'); profileSettingsController.toggleSettingsMenu();
if (settingsMenu) {
settingsMenu.classList.toggle('hidden');
}
} }
/**
* 绑定connect视图事件
* @param {function} onJoinCall - 加入通话回调(connectionId: string)
* @param {function} onCreateCall - 创建通话回调()
*/
export function bindConnectViewEvents(onJoinCall, onCreateCall) { export function bindConnectViewEvents(onJoinCall, onCreateCall) {
// 加入通话按钮
const connectBtn = document.getElementById('connectBtn'); const connectBtn = document.getElementById('connectBtn');
if (connectBtn) { if (connectBtn && !connectBtn.dataset.bound) {
connectBtn.addEventListener('click', () => { connectBtn.addEventListener('click', () => {
const connectionIdInput = document.getElementById('connectionIdInput'); const connectionIdInput = document.getElementById('connectionIdInput');
const connectionId = connectionIdInput ? connectionIdInput.value.trim() : ''; const connectionId = connectionIdInput ? connectionIdInput.value.trim() : '';
if (connectionId) { if (connectionId) {
onJoinCall(connectionId); onJoinCall(connectionId);
} else { } else {
showNotification('请输入连接ID', 'error'); showNotification('请输入连接ID', 'error');
} }
}); });
connectBtn.dataset.bound = 'true';
} }
// 创建通话按钮
const createCallBtn = document.getElementById('createCallBtn'); const createCallBtn = document.getElementById('createCallBtn');
if (createCallBtn) { if (createCallBtn && !createCallBtn.dataset.bound) {
createCallBtn.addEventListener('click', onCreateCall); createCallBtn.addEventListener('click', onCreateCall);
createCallBtn.dataset.bound = 'true';
} }
// 浏览全部ID按钮
const browseIdsBtn = document.getElementById('browseIdsBtn'); const browseIdsBtn = document.getElementById('browseIdsBtn');
if (browseIdsBtn) { if (browseIdsBtn && !browseIdsBtn.dataset.bound) {
browseIdsBtn.addEventListener('click', getAllConnectionIds); browseIdsBtn.addEventListener('click', getAllConnectionIds);
browseIdsBtn.dataset.bound = 'true';
} }
// 输入框回车事件
const connectionIdInput = document.getElementById('connectionIdInput'); const connectionIdInput = document.getElementById('connectionIdInput');
if (connectionIdInput) { if (connectionIdInput && !connectionIdInput.dataset.bound) {
connectionIdInput.addEventListener('keypress', (e) => { connectionIdInput.addEventListener('keypress', (event) => {
if (e.key === 'Enter') { if (event.key !== 'Enter') {
return;
}
const connectionId = connectionIdInput.value.trim(); const connectionId = connectionIdInput.value.trim();
if (connectionId) { if (connectionId) {
onJoinCall(connectionId); onJoinCall(connectionId);
} else { } else {
showNotification('请输入连接ID', 'error'); showNotification('请输入连接ID', 'error');
} }
}
}); });
connectionIdInput.dataset.bound = 'true';
} }
// 用户设置按钮
const userSettingsBtn = document.getElementById('userSettingsBtn'); const userSettingsBtn = document.getElementById('userSettingsBtn');
if (userSettingsBtn) { if (userSettingsBtn && !userSettingsBtn.dataset.bound) {
userSettingsBtn.addEventListener('click', toggleSettingsMenu); userSettingsBtn.addEventListener('click', toggleSettingsMenu);
userSettingsBtn.dataset.bound = 'true';
} }
} }
// 点击外部关闭设置菜单 profileSettingsController.bindDocumentEvents();
document.addEventListener('click', function(event) { profileSettingsController.bindWindowHandlers();
const settingsMenu = document.getElementById('settingsMenu');
const userSettingsBtn = document.getElementById('userSettingsBtn');
if (settingsMenu && userSettingsBtn &&
!settingsMenu.contains(event.target) &&
!userSettingsBtn.contains(event.target)) {
settingsMenu.classList.add('hidden');
}
});
// 导出全局函数供HTML onclick使用
window.selectConnectionId = selectConnectionId; window.selectConnectionId = selectConnectionId;
window.saveSettings = saveSettings;
window.handleAvatarUpload = handleAvatarUpload;
window.copyUserId = copyUserId;
window.toggleSettingsMenu = toggleSettingsMenu;

View File

@@ -0,0 +1,217 @@
const DEFAULT_AVATAR = '/images/p1.png';
const MAX_AVATAR_SIZE = 2 * 1024 * 1024;
const USER_ID_PREFIX = 'user_';
const USER_ID_LENGTH = 8;
const USER_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function getElement(id) {
return document.getElementById(id);
}
function setAvatarPreview(avatarUrl) {
const userAvatar = getElement('userAvatar');
const avatarPreview = getElement('avatarPreview');
if (userAvatar) {
userAvatar.src = avatarUrl;
}
if (avatarPreview) {
avatarPreview.src = avatarUrl;
}
}
function updateUserName(name) {
const userName = getElement('userName');
if (userName) {
userName.textContent = name;
}
}
function generateUserId() {
let result = USER_ID_PREFIX;
for (let i = 0; i < USER_ID_LENGTH; i++) {
result += USER_ID_CHARS.charAt(Math.floor(Math.random() * USER_ID_CHARS.length));
}
return result;
}
function readStoredSettings() {
const rawSettings = localStorage.getItem('userSettings');
if (!rawSettings) {
return null;
}
return JSON.parse(rawSettings);
}
function getCurrentSettingsPayload() {
const nicknameInput = getElement('nicknameInput');
const userIdInput = getElement('userIdInput');
const avatarPreview = getElement('avatarPreview');
return {
userId: userIdInput ? userIdInput.value : generateUserId(),
name: nicknameInput ? (nicknameInput.value || '\u6211') : '\u6211',
avatar: avatarPreview ? (avatarPreview.src || DEFAULT_AVATAR) : DEFAULT_AVATAR
};
}
async function uploadAvatar(formData) {
const response = await fetch('/api/upload/avatar', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('\u4e0a\u4f20\u5931\u8d25');
}
return response.json();
}
export function createProfileSettingsController({ store, notify }) {
let documentEventsBound = false;
function loadUserSettings() {
try {
const settings = readStoredSettings();
if (!settings) {
const nextUserId = generateUserId();
const userIdInput = getElement('userIdInput');
if (userIdInput) {
userIdInput.value = nextUserId;
}
setAvatarPreview(DEFAULT_AVATAR);
saveSettings();
return;
}
const userIdInput = getElement('userIdInput');
const nicknameInput = getElement('nicknameInput');
if (settings.userId && userIdInput) {
userIdInput.value = settings.userId;
}
if (settings.name && nicknameInput) {
nicknameInput.value = settings.name;
}
updateUserName(settings.name || '\u6211');
setAvatarPreview(settings.avatar || DEFAULT_AVATAR);
} catch (error) {
console.error('Error loading user settings:', error);
setAvatarPreview(DEFAULT_AVATAR);
}
}
function saveSettings() {
const settings = getCurrentSettingsPayload();
localStorage.setItem('userSettings', JSON.stringify(settings));
store.syncSocketUserInfo(settings);
updateUserName(settings.name);
setAvatarPreview(settings.avatar);
notify('\u8bbe\u7f6e\u5df2\u4fdd\u5b58', 'success');
}
async function handleAvatarUpload(event) {
const file = event.target.files[0];
if (!file) {
return;
}
if (!file.type.startsWith('image/')) {
notify('\u8bf7\u9009\u62e9\u56fe\u7247\u6587\u4ef6', 'error');
return;
}
if (file.size > MAX_AVATAR_SIZE) {
notify('\u56fe\u7247\u5927\u5c0f\u4e0d\u80fd\u8d85\u8fc72MB', 'error');
return;
}
const formData = new FormData();
formData.append('avatar', file);
const userIdInput = getElement('userIdInput');
if (userIdInput) {
formData.append('userId', userIdInput.value);
}
notify('\u6b63\u5728\u4e0a\u4f20\u5934\u50cf...');
try {
const data = await uploadAvatar(formData);
if (!data.success || !data.avatarUrl) {
throw new Error(data.message || '\u672a\u77e5\u9519\u8bef');
}
setAvatarPreview(data.avatarUrl);
saveSettings();
notify('\u5934\u50cf\u4e0a\u4f20\u6210\u529f', 'success');
} catch (error) {
console.error('Error uploading avatar:', error);
setAvatarPreview(DEFAULT_AVATAR);
notify('\u5934\u50cf\u4e0a\u4f20\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5', 'error');
}
}
function copyUserId() {
const userIdInput = getElement('userIdInput');
if (!userIdInput) {
return;
}
userIdInput.select();
document.execCommand('copy');
notify('\u7528\u6237ID\u5df2\u590d\u5236\u5230\u526a\u8d34\u677f', 'success');
}
function toggleSettingsMenu() {
const settingsMenu = getElement('settingsMenu');
if (settingsMenu) {
settingsMenu.classList.toggle('hidden');
}
}
function bindDocumentEvents() {
if (documentEventsBound) {
return;
}
document.addEventListener('click', (event) => {
const settingsMenu = getElement('settingsMenu');
const userSettingsButton = getElement('userSettingsBtn');
if (
settingsMenu &&
userSettingsButton &&
!settingsMenu.contains(event.target) &&
!userSettingsButton.contains(event.target)
) {
settingsMenu.classList.add('hidden');
}
});
documentEventsBound = true;
}
function bindWindowHandlers() {
window.saveSettings = saveSettings;
window.handleAvatarUpload = handleAvatarUpload;
window.copyUserId = copyUserId;
window.toggleSettingsMenu = toggleSettingsMenu;
}
return {
bindDocumentEvents,
bindWindowHandlers,
copyUserId,
handleAvatarUpload,
loadUserSettings,
saveSettings,
toggleSettingsMenu
};
}