From 84a52b9dcf45ef3ee9c50b1fe4a4817660800a59 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Thu, 14 May 2026 22:18:37 +0200 Subject: [PATCH] Improve: Web UI - iOS safe-area, icon toolbar buttons, logout confirm --- server/2015Remote/UIBranding.h | 5 +- server/2015Remote/WebPage.h | 214 ++++++++++++++++++++++++++------- 2 files changed, 173 insertions(+), 46 deletions(-) diff --git a/server/2015Remote/UIBranding.h b/server/2015Remote/UIBranding.h index ac001aa..e8b3a16 100644 --- a/server/2015Remote/UIBranding.h +++ b/server/2015Remote/UIBranding.h @@ -9,6 +9,8 @@ // // 编码要求:此文件必须保存为 UTF-8 with BOM(MSVC 要求) // 注意:此文件中的配置会编译到程序中,运行时无法修改。 +// 修改原则:最小化原则。如果是UI文案修改,通常没有问题 +// 如果修改项涉及数据存储、程序配置、代码逻辑,可能导致程序异常。 // // ============================================================ // @@ -107,6 +109,7 @@ // 注册表键名 [仅ASCII,无空格] // 存储位置:HKCU\Software\{此键名} +// 不能修改,修改会隐藏授权 #define BRAND_REGISTRY_KEY "YAMA" // 网络通信前缀 [仅ASCII,无空格] @@ -262,7 +265,7 @@ // // 如果配置值以 "http" 开头,则作为 URL 打开浏览器。 // 否则直接显示为文本消息。 -// +// 链接从上级同步而来,修改无效;建议设置这些菜单为隐藏 // 反馈链接(帮助菜单 → 反馈) #define BRAND_URL_FEEDBACK "https://t.me/SimpleRemoter" diff --git a/server/2015Remote/WebPage.h b/server/2015Remote/WebPage.h index 1e6bb2c..72be981 100644 --- a/server/2015Remote/WebPage.h +++ b/server/2015Remote/WebPage.h @@ -32,7 +32,17 @@ inline std::string GetWebPageHTML() { min-height: 100vh; overflow-x: hidden; } - .page { display: none !important; padding: 20px; min-height: 100vh; } + .page { + display: none !important; + min-height: 100vh; + /* iOS notch / Dynamic Island: viewport-fit=cover 让 viewport 顶到物理边缘, + 这里用 env(safe-area-inset-*) 把内容推回到安全区内,避免被前摄/底部 Home 条遮挡。 + 旧设备 / 非全屏环境下 env() 解析为 0,等价于纯 20px 内边距。 */ + padding: calc(20px + env(safe-area-inset-top)) + calc(20px + env(safe-area-inset-right)) + calc(20px + env(safe-area-inset-bottom)) + calc(20px + env(safe-area-inset-left)); + } .page.active { display: block !important; } #login-page.active { display: flex !important; @@ -252,44 +262,32 @@ inline std::string GetWebPageHTML() { .view-btn:first-child { border-right: 1px solid rgba(255,255,255,0.1); } .view-btn:hover { color: #fff; } .view-btn.active { background: rgba(233, 69, 96, 0.3); color: #e94560; } - .refresh-btn { - padding: 10px 20px; + /* Unified icon button (Refresh / Users / Logout). 40x40 方块更紧凑,留位置给未来按钮。 + 颜色身份通过修饰类(.refresh / .users / .logout)保留,hover 高光与原来一致。 */ + .icon-btn { + width: 40px; + height: 40px; border: none; border-radius: 8px; - background: linear-gradient(135deg, #0f3460 0%, #1a4a7a 100%); color: #fff; - font-size: 14px; cursor: pointer; - transition: all 0.3s; + transition: all 0.2s; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + margin-left: 8px; } - .refresh-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(15, 52, 96, 0.4); } - .logout-btn { - padding: 10px 20px; - border: none; - border-radius: 8px; - background: linear-gradient(135deg, #c0392b 0%, #e74c3c 100%); - color: #fff; - font-size: 14px; - cursor: pointer; - transition: all 0.3s; - margin-left: 10px; - } - .logout-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); } - .users-btn { - padding: 10px 20px; - border: none; - border-radius: 8px; - background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%); - color: #fff; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.3s; - margin-left: 10px; - display: none; - } - .users-btn.visible { display: inline-block; } - .users-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(142, 68, 173, 0.4); } + .icon-btn svg { display: block; width: 20px; height: 20px; } + .icon-btn:hover { transform: translateY(-1px); } + .icon-btn:active { transform: translateY(0); } + .icon-btn.refresh { background: linear-gradient(135deg, #0f3460 0%, #1a4a7a 100%); } + .icon-btn.refresh:hover { box-shadow: 0 4px 12px rgba(15, 52, 96, 0.4); } + .icon-btn.users { background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%); display: none; } + .icon-btn.users.visible { display: inline-flex; } + .icon-btn.users:hover { box-shadow: 0 4px 12px rgba(142, 68, 173, 0.4); } + .icon-btn.logout { background: linear-gradient(135deg, #c0392b 0%, #e74c3c 100%); } + .icon-btn.logout:hover { box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); } /* User Management Modal */ .modal-overlay { display: none; @@ -387,6 +385,54 @@ inline std::string GetWebPageHTML() { .user-msg { padding: 10px; border-radius: 6px; margin-bottom: 12px; font-size: 13px; } .user-msg.success { background: rgba(39, 174, 96, 0.2); color: #2ecc71; } .user-msg.error { background: rgba(231, 76, 60, 0.2); color: #e74c3c; } + + /* Generic confirmation modal (compact yes/no dialog, e.g. logout) —— 复用 .modal-overlay + 做遮罩,自带一套紧凑布局 + 危险/取消双按钮风格。 */ + .confirm-modal-content { + background: rgba(22, 33, 62, 0.98); + border-radius: 16px; + padding: 28px; + width: 90%; + max-width: 380px; + box-shadow: 0 8px 32px rgba(0,0,0,0.5); + border: 1px solid rgba(231, 76, 60, 0.25); + text-align: center; + } + .confirm-modal-icon { + width: 56px; + height: 56px; + margin: 0 auto 16px; + border-radius: 50%; + background: rgba(231, 76, 60, 0.15); + color: #e74c3c; + display: flex; + align-items: center; + justify-content: center; + } + .confirm-modal-icon svg { width: 28px; height: 28px; } + .confirm-modal-content h3 { color: #fff; margin: 0 0 8px; font-size: 18px; } + .confirm-modal-content p { color: #aaa; margin: 0 0 24px; font-size: 14px; line-height: 1.5; } + .confirm-modal-actions { display: flex; gap: 12px; justify-content: center; } + .confirm-modal-actions button { + flex: 1; + padding: 12px 20px; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + } + .confirm-modal-actions .cancel-btn { + background: rgba(255,255,255,0.08); + color: #ccc; + } + .confirm-modal-actions .cancel-btn:hover { background: rgba(255,255,255,0.15); } + .confirm-modal-actions .danger-btn { + background: linear-gradient(135deg, #c0392b 0%, #e74c3c 100%); + color: #fff; + } + .confirm-modal-actions .danger-btn:hover { box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); transform: translateY(-1px); } )HTML"; // Part 3: Device card styles @@ -558,7 +604,11 @@ inline std::string GetWebPageHTML() { #screen-canvas { max-width: 100%; max-height: 100%; } .screen-toolbar { background: linear-gradient(180deg, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.85) 100%); - padding: 12px 20px; + /* 远程桌面顶部工具栏也避开 notch / Dynamic Island —— 顶 padding + 安全区 */ + padding: calc(12px + env(safe-area-inset-top)) + calc(20px + env(safe-area-inset-right)) + 12px + calc(20px + env(safe-area-inset-left)); display: flex; justify-content: space-between; align-items: center; @@ -633,13 +683,14 @@ inline std::string GetWebPageHTML() { height: 100dvh !important; max-height: none !important; } - /* Portrait fullscreen: align canvas to top */ + /* Portrait fullscreen: align canvas to top. 56px 是给浮动工具栏留出的空间; + 竖屏 iPhone 全屏时再加上 safe-area-inset-top 把内容推到 notch / 灵动岛之下。 */ @media (orientation: portrait) { #screen-page:fullscreen .canvas-container, #screen-page:-webkit-full-screen .canvas-container, #screen-page.pseudo-fullscreen .canvas-container { align-items: flex-start !important; - padding-top: 56px !important; + padding-top: calc(56px + env(safe-area-inset-top)) !important; } } #screen-page.pseudo-fullscreen #screen-canvas { @@ -666,7 +717,8 @@ inline std::string GetWebPageHTML() { /* Floating toolbar menu - minimal icon style */ .floating-toolbar { position: fixed; - top: 8px; + /* 8px 基础留白 + safe-area-inset-top 避开 iPhone 刘海/灵动岛 */ + top: calc(8px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%); z-index: 1001; @@ -701,7 +753,8 @@ inline std::string GetWebPageHTML() { .toolbar-btn:disabled:active { transform: none; } .toolbar-toggle { position: fixed; - top: 8px; + /* 同 .floating-toolbar:8px 基础 + 安全区 inset 避开刘海/灵动岛 */ + top: calc(8px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%); z-index: 1000; @@ -779,6 +832,10 @@ inline std::string GetWebPageHTML() { /* Portrait: horizontal at top center */ @media (orientation: portrait) { .quick-controls { + /* 非全屏:screen-toolbar 已经吃了一份 safe-area,这里再叠加会让快捷按钮 + 离 title 远到 ~2x 图标高度的空白,且 canvas-container 因此被推得过低, + 底部 input-shortcuts 在键盘弹出时会被遮挡。 + 全屏:另在 fullscreen 选择器里补回 safe-area-inset-top 避开前置摄像头。 */ top: 4px; left: 50%; transform: translateX(-50%); @@ -791,12 +848,23 @@ inline std::string GetWebPageHTML() { height: 40px; font-size: 18px; } - /* Portrait: align canvas to top, leave space for controls */ + /* Portrait: align canvas to top, leave space for controls. + 同上:非全屏 screen-toolbar 已避开刘海,这里只留固定 56px 给快捷按钮, + 全屏路径在下方有独立 !important 覆写补回 safe-area。 */ .canvas-container { align-items: flex-start; padding-top: 56px; } } + /* 全屏(无 screen-toolbar 兜底)才需要 quick-controls 自己加 safe-area + 避开 iPhone 刘海/灵动岛/前置摄像头。 */ + @media (orientation: portrait) { + #screen-page:fullscreen .quick-controls, + #screen-page:-webkit-full-screen .quick-controls, + #screen-page.pseudo-fullscreen .quick-controls { + top: calc(4px + env(safe-area-inset-top)); + } + } /* Landscape: vertical at right center */ @media (orientation: landscape) { .quick-controls { @@ -852,7 +920,12 @@ inline std::string GetWebPageHTML() { .input-shortcuts .shortcut-btn:active { transform: scale(0.95); background: rgba(128,128,128,0.6); } /* Mobile responsive */ @media (max-width: 768px) { - .page { padding: 10px; } + .page { + padding: calc(10px + env(safe-area-inset-top)) + calc(10px + env(safe-area-inset-right)) + calc(10px + env(safe-area-inset-bottom)) + calc(10px + env(safe-area-inset-left)); + } .header { flex-direction: column; gap: 12px; padding: 12px; } .header h1 { font-size: 20px; } .search-box { width: 100%; } @@ -863,7 +936,12 @@ inline std::string GetWebPageHTML() { .device-card .ip { font-size: 12px; } .device-card .meta-row { flex-wrap: wrap; gap: 8px; } .device-card .active-window { font-size: 11px; } - .screen-toolbar { padding: 8px 12px; } + .screen-toolbar { + padding: calc(8px + env(safe-area-inset-top)) + calc(12px + env(safe-area-inset-right)) + 8px + calc(12px + env(safe-area-inset-left)); + } .back-btn { padding: 6px 12px; font-size: 13px; } .toolbar-info .device-name { font-size: 13px; } .toolbar-info .conn-info { font-size: 11px; } @@ -925,9 +1003,28 @@ inline std::string GetWebPageHTML() { - - - + + +
+ + + )HTML"; // Part 7: JavaScript - State and WebSocket @@ -1648,7 +1764,15 @@ inline std::string GetWebPageHTML() { ws.send(JSON.stringify({ cmd: 'login', username, response, nonce: challengeNonce })); } + // Logout 二次确认:onclick="logout()" 仅打开确认弹窗;用户点 "Logout" 才真正退出。 function logout() { + document.getElementById('logout-confirm-modal').classList.add('active'); + } + function cancelLogout() { + document.getElementById('logout-confirm-modal').classList.remove('active'); + } + function confirmLogout() { + document.getElementById('logout-confirm-modal').classList.remove('active'); // Close and reconnect WebSocket to get new challenge if (ws) { ws.onclose = null; // Prevent auto-reconnect delay