diff --git a/client/public/connectview.js b/client/public/connectview.js index fa36d8d..a016b79 100644 --- a/client/public/connectview.js +++ b/client/public/connectview.js @@ -1,8 +1,3 @@ -/** - * connect视图逻辑 - * 处理初始连接界面的UI、用户设置、WebSocket连接状态显示 - */ - import { showNotification } from './utils.js'; import store from './store.js'; import { @@ -11,29 +6,24 @@ import { renderConnectionIds, renderOnlineUsers } from './connect-directory.js'; +import { createProfileSettingsController } from './profile-settings.js'; -const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB - -// WebSocket连接状态更新回调 let onWsStatusChange = null; let cachedOnlineUsers = []; -/** - * 设置WebSocket状态变化回调 - * @param {function} callback - 回调函数(connected: boolean) - */ +const profileSettingsController = createProfileSettingsController({ + store, + notify: showNotification +}); + export function setWsStatusCallback(callback) { onWsStatusChange = callback; } -/** - * 更新WebSocket状态显示 - * @param {boolean} connected - 是否已连接 - */ export function updateWsStatus(connected) { const wsStatusDot = document.getElementById('wsStatusDot'); const wsStatusText = document.getElementById('wsStatusText'); - + if (wsStatusDot && wsStatusText) { if (connected) { wsStatusDot.className = 'w-2 h-2 bg-green-500 rounded-full animate-pulse'; @@ -43,15 +33,12 @@ export function updateWsStatus(connected) { wsStatusText.textContent = '未连接'; } } - + if (onWsStatusChange) { onWsStatusChange(connected); } } -/** - * 初始化WebSocket连接(页面加载时调用) - */ export async function initWebSocket() { try { await store.connectSignaling(); @@ -66,14 +53,11 @@ export async function initWebSocket() { } } -/** - * 获取全部在线WebSocket用户 - * @param {boolean} silent - 是否静默刷新 - */ async function refreshOnlineUsers(silent = true) { try { cachedOnlineUsers = await fetchOnlineUsers(); updateOnlineUsersList(cachedOnlineUsers); + if (!silent) { showNotification(`当前共有 ${cachedOnlineUsers.length} 个WebSocket用户在线`); } @@ -85,11 +69,9 @@ async function refreshOnlineUsers(silent = true) { } } -/** - * 获取所有连接ID - */ async function getAllConnectionIds() { showNotification('正在获取连接ID和在线用户...'); + try { const { connectionIds, users } = await fetchConnectionDirectory(); cachedOnlineUsers = users; @@ -101,10 +83,6 @@ async function getAllConnectionIds() { } } -/** - * 显示连接ID列表 - * @param {string[]} connectionIds - 连接ID数组 - */ function updateConnectionIdList(connectionIds) { const idsContainer = document.getElementById('idsContainer'); const connectionIdsList = document.getElementById('connectionIdsList'); @@ -131,10 +109,6 @@ function getCurrentUserId() { } } -/** - * 显示全部在线WebSocket用户 - * @param {Array} users - 在线用户列表 - */ function updateOnlineUsersList(users) { const onlineUsersList = document.getElementById('onlineUsersList'); const usersContainer = document.getElementById('usersContainer'); @@ -149,253 +123,86 @@ function updateOnlineUsersList(users) { }); } -/** - * 选择连接ID - * @param {string} id - 连接ID - */ -function selectConnectionId(id) { +function selectConnectionId(connectionId) { const connectionIdInput = document.getElementById('connectionIdInput'); if (connectionIdInput) { - connectionIdInput.value = id; - showNotification(`已选择连接ID: ${id}`); + connectionIdInput.value = connectionId; + 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() { - const defaultAvatar = '/images/p1.png'; - 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(); - } + profileSettingsController.loadUserSettings(); } -/** - * 保存用户设置 - */ export function saveSettings() { - const defaultAvatar = '/images/p1.png'; - 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'); + profileSettingsController.saveSettings(); } -/** - * 处理头像上传 - * @param {Event} event - 文件选择事件 - */ export function handleAvatarUpload(event) { - const file = event.target.files[0]; - 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; - }); + profileSettingsController.handleAvatarUpload(event); } -/** - * 复制用户ID到剪贴板 - */ export function copyUserId() { - const userIdInput = document.getElementById('userIdInput'); - if (userIdInput) { - userIdInput.select(); - document.execCommand('copy'); - showNotification('用户ID已复制到剪贴板', 'success'); - } + profileSettingsController.copyUserId(); } -/** - * 切换设置菜单 - */ export function toggleSettingsMenu() { - const settingsMenu = document.getElementById('settingsMenu'); - if (settingsMenu) { - settingsMenu.classList.toggle('hidden'); - } + profileSettingsController.toggleSettingsMenu(); } -/** - * 绑定connect视图事件 - * @param {function} onJoinCall - 加入通话回调(connectionId: string) - * @param {function} onCreateCall - 创建通话回调() - */ export function bindConnectViewEvents(onJoinCall, onCreateCall) { - // 加入通话按钮 const connectBtn = document.getElementById('connectBtn'); - if (connectBtn) { + if (connectBtn && !connectBtn.dataset.bound) { connectBtn.addEventListener('click', () => { const connectionIdInput = document.getElementById('connectionIdInput'); const connectionId = connectionIdInput ? connectionIdInput.value.trim() : ''; + if (connectionId) { onJoinCall(connectionId); } else { showNotification('请输入连接ID', 'error'); } }); + connectBtn.dataset.bound = 'true'; } - // 创建通话按钮 const createCallBtn = document.getElementById('createCallBtn'); - if (createCallBtn) { + if (createCallBtn && !createCallBtn.dataset.bound) { createCallBtn.addEventListener('click', onCreateCall); + createCallBtn.dataset.bound = 'true'; } - // 浏览全部ID按钮 const browseIdsBtn = document.getElementById('browseIdsBtn'); - if (browseIdsBtn) { + if (browseIdsBtn && !browseIdsBtn.dataset.bound) { browseIdsBtn.addEventListener('click', getAllConnectionIds); + browseIdsBtn.dataset.bound = 'true'; } - // 输入框回车事件 const connectionIdInput = document.getElementById('connectionIdInput'); - if (connectionIdInput) { - connectionIdInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - const connectionId = connectionIdInput.value.trim(); - if (connectionId) { - onJoinCall(connectionId); - } else { - showNotification('请输入连接ID', 'error'); - } + if (connectionIdInput && !connectionIdInput.dataset.bound) { + connectionIdInput.addEventListener('keypress', (event) => { + if (event.key !== 'Enter') { + return; + } + + const connectionId = connectionIdInput.value.trim(); + if (connectionId) { + onJoinCall(connectionId); + } else { + showNotification('请输入连接ID', 'error'); } }); + connectionIdInput.dataset.bound = 'true'; } - // 用户设置按钮 const userSettingsBtn = document.getElementById('userSettingsBtn'); - if (userSettingsBtn) { + if (userSettingsBtn && !userSettingsBtn.dataset.bound) { userSettingsBtn.addEventListener('click', toggleSettingsMenu); + userSettingsBtn.dataset.bound = 'true'; } } -// 点击外部关闭设置菜单 -document.addEventListener('click', function(event) { - 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使用) +profileSettingsController.bindDocumentEvents(); +profileSettingsController.bindWindowHandlers(); window.selectConnectionId = selectConnectionId; -window.saveSettings = saveSettings; -window.handleAvatarUpload = handleAvatarUpload; -window.copyUserId = copyUserId; -window.toggleSettingsMenu = toggleSettingsMenu; diff --git a/client/public/profile-settings.js b/client/public/profile-settings.js new file mode 100644 index 0000000..6af0c36 --- /dev/null +++ b/client/public/profile-settings.js @@ -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 + }; +}