From 03474051159eda066cf51fa3edeafeb818b4bb77 Mon Sep 17 00:00:00 2001 From: Gloridust Date: Tue, 28 Jul 2026 22:33:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E8=87=AA=E5=8A=A8=E5=9B=9E?= =?UTF-8?q?=E8=BD=A6=E5=BC=80=E5=85=B3=20+=20=E5=8A=9F=E8=83=BD=E9=94=AE?= =?UTF-8?q?=E6=8E=92=20+=20=E4=BF=AE=E9=BD=BF=E8=BD=AE=E5=9B=BE=E6=A0=87/?= =?UTF-8?q?=E5=AD=90=E7=94=A8=E6=88=B7=E6=96=87=E6=A1=88=EF=BC=88issue=20#?= =?UTF-8?q?125=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) 转发输入条「自动回车」开关(默认开,保持 issue #81 行为) 关掉后只把文字填进应用输入框、不代发,按钮文案随之变「填入」。 原先无条件补回车,导致想先编辑再发时文本一进框就被发出去, 用户只能靠外部剪贴板工具绕开。开关记在 localStorage。 2) 功能键折叠排(Fn):Esc / Tab / 退格 / Del / 回车 / 方向键 这些键在 VNC 里常被浏览器吞掉,手机端更是没有。经 xdotool 送出, 键名须匹配服务端白名单 /^[A-Za-z_]{1,20}$/,故只放单键不放组合键。 标签用「退格」而非 ⌫(U+232B)、用「Fn」而非 ⌨——实测这两个符号 在容器字体下渲染成豆腐块方框。 3) 齿轮图标:旧路径是手改过的 feather 变体,H9/V9 这类绝对指令混在 相对弧线里,有两颗齿明显走形(右下那颗尤其)。换成 lucide 官方 settings 路径(MIT),12 齿对称。已渲染新旧对比确认。 4) 子用户文案:「重启实例」按钮本就按 isAdmin 隐藏,但两处提示文字没跟着 变,子用户会照着找一个根本不存在的按钮。改为提示联系管理员。 样式复用既有牛奶布艺 token(--crease / --crease-press / --trough / --surface / --muted),未新造阴影值。 --- panel/web/src/AppShell.tsx | 4 +- panel/web/src/pages/Desktop.tsx | 142 ++++++++++++++++++++++++++------ panel/web/src/styles.css | 51 ++++++++++++ 3 files changed, 169 insertions(+), 28 deletions(-) diff --git a/panel/web/src/AppShell.tsx b/panel/web/src/AppShell.tsx index 3c79995..3e3b9a5 100644 --- a/panel/web/src/AppShell.tsx +++ b/panel/web/src/AppShell.tsx @@ -63,7 +63,9 @@ const Icon = { ), gear: ( - + {/* 齿轮:原路径是手改过的 feather 变体,H9/V9 这类绝对指令混在相对弧线里, + 导致有两颗齿明显走形(issue #125)。换成 lucide 官方 settings 路径(MIT),12 齿对称。 */} + ), logout: ( diff --git a/panel/web/src/pages/Desktop.tsx b/panel/web/src/pages/Desktop.tsx index 1b18125..c52db51 100644 --- a/panel/web/src/pages/Desktop.tsx +++ b/panel/web/src/pages/Desktop.tsx @@ -133,6 +133,21 @@ function allowAutoRecover(iid: string): boolean { return true; } +// 转发输入条上的功能键(issue #125)。键名走 xdotool,须匹配服务端白名单 /^[A-Za-z_]{1,20}$/, +// 故只放单键、不放组合键(ctrl+a 这类含 "+" 会被拒)。 +const FUNC_KEYS: { key: string; label: string; title: string }[] = [ + { key: 'Escape', label: 'Esc', title: 'Escape(关弹窗/退出全屏输入)' }, + { key: 'Tab', label: 'Tab', title: 'Tab(切换焦点)' }, + // 用中文字面而非 ⌫(U+232B):容器/部分系统缺字形会渲染成豆腐块,实测就是方框 + { key: 'BackSpace', label: '退格', title: '退格(删除前一个字符)' }, + { key: 'Delete', label: 'Del', title: 'Delete(删除后一个字符)' }, + { key: 'Return', label: '↵', title: '回车(发送/换行)' }, + { key: 'Left', label: '←', title: '左方向键' }, + { key: 'Up', label: '↑', title: '上方向键' }, + { key: 'Down', label: '↓', title: '下方向键' }, + { key: 'Right', label: '→', title: '右方向键' }, +]; + const MenuIcon = ( @@ -189,6 +204,26 @@ export default function InstanceView({ onOpenMenu }: { onOpenMenu: () => void }) /* ignore */ } }; + const [showKeys, setShowKeys] = useState(false); + // 转发输入条「发送后自动回车」开关(默认开,保持 issue #81 起的既有行为)。 + // 关掉的场景(issue #125):想先把长文/多行填进微信输入框里再自己斟酌、编辑后手动发; + // 自动回车会让文本一进框就被发出去,也让 Shift+回车换行之外的编辑很别扭。 + const [autoEnter, setAutoEnter] = useState(() => { + try { + return window.localStorage.getItem('woc_ime_auto_enter') !== '0'; + } catch { + return true; + } + }); + const toggleAutoEnter = () => { + const v = !autoEnter; + setAutoEnter(v); + try { + window.localStorage.setItem('woc_ime_auto_enter', v ? '1' : '0'); + } catch { + /* ignore */ + } + }; // 麦克风开关(默认关):仅在「声音」开启时有意义;默认不抢占麦克风,避免把 AirPods 切到低质通话模式。 const [micOn, setMicOn] = useState(() => { try { @@ -775,16 +810,26 @@ export default function InstanceView({ onOpenMenu }: { onOpenMenu: () => void }) // 中文输入条发送:把本框文本经 xclip+xdotool 直接粘进微信当前聚焦的输入框(绕开 VNC IME)。 // 在面板的真实 textarea 里用原生输入法打字,100% 可靠,不依赖 VNC 的 enable_ime / 合成事件。 + const pressKey = async (key: string) => { + if (!id) return; + try { + await api.keyInInstance(id, key); + } catch (e: any) { + toast(e?.message || '按键失败:请确认实例已「升级实例」(镜像含 xdotool)', 'error'); + } + }; + const sendImeText = async () => { const t = imeText; if (!t.trim() || !id) return; setImeSending(true); try { await api.typeInInstance(id, t); - // 打完直接补一个回车把消息发出去(issue #81),焦点【始终留在本输入条】。 + // 打完补一个回车把消息发出去(issue #81);可由「自动回车」开关关掉(issue #125), + // 关掉时只把文字填进应用输入框,发不发由用户自己按。焦点【始终留在本输入条】。 // 切勿在转发模式把焦点切回虚拟机——那等于开了"无感输入",用户接着打的拼音会以原始 keysym 直灌微信 // 输入框(出现 "nniih'h你好啊" 这种串码)。下一条仍在本条用本机输入法安全地打。 - await api.keyInInstance(id, 'Return'); + if (autoEnter) await api.keyInInstance(id, 'Return'); setImeText(''); } catch (e: any) { toast(e?.message || '发送失败:请确认实例已「升级实例」(镜像含 xclip/xdotool)', 'error'); @@ -1031,7 +1076,13 @@ export default function InstanceView({ onOpenMenu }: { onOpenMenu: () => void }) {!frameLoaded && loadStuck && (
桌面无响应
-
连接超时。可能是实例临时卡住,先「重新连接」;若仍无效请「重启实例」。
+ {/* 子用户无「重启实例」权限(按钮下方已按 isAdmin 隐藏),文案也要跟着变, + 否则会让人照着找一个根本不存在的按钮(issue #125)。 */} +
+ {isAdmin + ? '连接超时。可能是实例临时卡住,先「重新连接」;若仍无效请「重启实例」。' + : '连接超时。可能是实例临时卡住,请先「重新连接」;若反复无效,请联系管理员重启该实例。'} +
)}
-
- 若反复无响应,点「重启实例」即可恢复(数据保留)。 -
+ {isAdmin && ( +
+ 若反复无响应,点「重启实例」即可恢复(数据保留)。 +
+ )}
)} @@ -1216,28 +1269,63 @@ export default function InstanceView({ onOpenMenu }: { onOpenMenu: () => void }) {inputMode === 'forward' && ( -
-