From 71efb34795123ed6c0634e93f247361029c0a93b Mon Sep 17 00:00:00 2001 From: stary <834207172@qq.com> Date: Sat, 16 May 2026 21:26:19 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90m=E3=80=91=E4=B8=A4=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/public/onebyone/connect/connect.html | 113 +----- client/public/onebyone/connectview.js | 359 ++++++++++++++++++++ client/public/onebyone/index.html | 111 +++++- client/public/onebyone/main.js | 134 ++++++-- client/public/onebyone/store.js | 27 +- 5 files changed, 597 insertions(+), 147 deletions(-) create mode 100644 client/public/onebyone/connectview.js diff --git a/client/public/onebyone/connect/connect.html b/client/public/onebyone/connect/connect.html index 7ef9a18..ad059b6 100644 --- a/client/public/onebyone/connect/connect.html +++ b/client/public/onebyone/connect/connect.html @@ -3,112 +3,13 @@ - VideoCall - 连接界面 - - - + VideoCall - 重定向 + - - -
- - - - -
- -
-
-
- -
-

VideoCall

-

一对一视频通话

- -
-
- - -
-

- 连接ID是用于建立点对点通话的唯一标识,由发起方生成并分享给接收方。 -

-
- -
- - -
- - - - - - -
-
- - -
- - 通知内容 -
- - - + +

正在跳转到视频通话界面...

diff --git a/client/public/onebyone/connectview.js b/client/public/onebyone/connectview.js new file mode 100644 index 0000000..03753a7 --- /dev/null +++ b/client/public/onebyone/connectview.js @@ -0,0 +1,359 @@ +/** + * connect视图逻辑 + * 处理初始连接界面的UI、用户设置、WebSocket连接状态显示 + */ + +import { showNotification } from './utils.js'; +import store from './store.js'; + +const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB + +// WebSocket连接状态更新回调 +let onWsStatusChange = null; + +/** + * 设置WebSocket状态变化回调 + * @param {function} callback - 回调函数(connected: boolean) + */ +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'; + wsStatusText.textContent = 'WebSocket已连接'; + } else { + wsStatusDot.className = 'w-2 h-2 bg-gray-500 rounded-full'; + wsStatusText.textContent = '未连接'; + } + } + + if (onWsStatusChange) { + onWsStatusChange(connected); + } +} + +/** + * 初始化WebSocket连接(页面加载时调用) + */ +export async function initWebSocket() { + try { + await store.connectSignaling(); + updateWsStatus(true); + console.log('WebSocket initialized from connectview'); + } catch (error) { + console.error('Failed to initialize WebSocket:', error); + updateWsStatus(false); + showNotification('WebSocket连接失败,请刷新页面重试', 'error'); + } +} + +/** + * 获取所有连接ID + */ +async function getAllConnectionIds() { + showNotification('正在获取连接ID列表...'); + try { + const response = await fetch('/signaling/connection-ids'); + if (!response.ok) { + throw new Error('Failed to fetch connection IDs'); + } + const data = await response.json(); + displayConnectionIds(data.connectionIds); + } catch (error) { + console.error('Error fetching connection IDs:', error); + showNotification('获取连接ID失败', 'error'); + } +} + +/** + * 显示连接ID列表 + * @param {string[]} connectionIds - 连接ID数组 + */ +function displayConnectionIds(connectionIds) { + const idsContainer = document.getElementById('idsContainer'); + const connectionIdsList = document.getElementById('connectionIdsList'); + + if (idsContainer) { + idsContainer.innerHTML = ''; + + if (connectionIds.length === 0) { + idsContainer.innerHTML = '

暂无可用的连接ID

'; + } else { + connectionIds.forEach(id => { + const idElement = document.createElement('div'); + idElement.className = 'flex items-center justify-between p-2 bg-white/5 rounded-lg hover:bg-white/10 cursor-pointer transition-colors'; + idElement.innerHTML = ` + ${id} + + `; + idsContainer.appendChild(idElement); + }); + } + + if (connectionIdsList) { + connectionIdsList.classList.remove('hidden'); + } + + showNotification(`找到 ${connectionIds.length} 个连接ID`); + } +} + +/** + * 选择连接ID + * @param {string} id - 连接ID + */ +function selectConnectionId(id) { + const connectionIdInput = document.getElementById('connectionIdInput'); + if (connectionIdInput) { + connectionIdInput.value = id; + showNotification(`已选择连接ID: ${id}`); + } +} + +/** + * 生成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(); + } +} + +/** + * 保存用户设置 + */ +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)); + + if (userName) userName.textContent = settings.name; + if (userAvatar) userAvatar.src = settings.avatar; + + showNotification('设置已保存', 'success'); +} + +/** + * 处理头像上传 + * @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; + }); +} + +/** + * 复制用户ID到剪贴板 + */ +export function copyUserId() { + const userIdInput = document.getElementById('userIdInput'); + if (userIdInput) { + userIdInput.select(); + document.execCommand('copy'); + showNotification('用户ID已复制到剪贴板', 'success'); + } +} + +/** + * 切换设置菜单 + */ +export function toggleSettingsMenu() { + const settingsMenu = document.getElementById('settingsMenu'); + if (settingsMenu) { + settingsMenu.classList.toggle('hidden'); + } +} + +/** + * 绑定connect视图事件 + * @param {function} onJoinCall - 加入通话回调(connectionId: string) + * @param {function} onCreateCall - 创建通话回调() + */ +export function bindConnectViewEvents(onJoinCall, onCreateCall) { + // 加入通话按钮 + const connectBtn = document.getElementById('connectBtn'); + if (connectBtn) { + connectBtn.addEventListener('click', () => { + const connectionIdInput = document.getElementById('connectionIdInput'); + const connectionId = connectionIdInput ? connectionIdInput.value.trim() : ''; + if (connectionId) { + onJoinCall(connectionId); + } else { + showNotification('请输入连接ID', 'error'); + } + }); + } + + // 创建通话按钮 + const createCallBtn = document.getElementById('createCallBtn'); + if (createCallBtn) { + createCallBtn.addEventListener('click', onCreateCall); + } + + // 浏览全部ID按钮 + const browseIdsBtn = document.getElementById('browseIdsBtn'); + if (browseIdsBtn) { + browseIdsBtn.addEventListener('click', getAllConnectionIds); + } + + // 输入框回车事件 + 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'); + } + } + }); + } + + // 用户设置按钮 + const userSettingsBtn = document.getElementById('userSettingsBtn'); + if (userSettingsBtn) { + userSettingsBtn.addEventListener('click', toggleSettingsMenu); + } +} + +// 点击外部关闭设置菜单 +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使用) +window.selectConnectionId = selectConnectionId; +window.saveSettings = saveSettings; +window.handleAvatarUpload = handleAvatarUpload; +window.copyUserId = copyUserId; +window.toggleSettingsMenu = toggleSettingsMenu; \ No newline at end of file diff --git a/client/public/onebyone/index.html b/client/public/onebyone/index.html index a0fc777..d919d9a 100644 --- a/client/public/onebyone/index.html +++ b/client/public/onebyone/index.html @@ -13,10 +13,114 @@ +
+ +
+ + + + +
+ + +
+
+
+ +
+

VideoCall

+

一对一视频通话

+ +
+
+ + +
+

+ 连接ID是用于建立点对点通话的唯一标识,由发起方生成并分享给接收方。 +

+
+ +
+ + +
+ + + + + + + + +
+ + 未连接 +
+
+
+
+ + +