From 0c9a6b6a271227961feabf1cc442785ee9aa4bfe Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 10 Sep 2026 00:09:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(console):=20=E5=BC=80=E6=9C=BA=E8=87=AA?= =?UTF-8?q?=E5=90=AF=E6=94=B9=E4=B8=BA=E3=80=8C=E6=B3=A8=E5=86=8C=E4=BD=86?= =?UTF-8?q?=E4=B8=8D=E5=90=AF=E7=94=A8=E3=80=8D=EF=BC=8C=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E5=88=87=E6=8D=A2=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一版把「关闭」实现成不写登录项。实际在 Windows 上实测 Electron: setLoginItemSettings({ openAtLogin: true, enabled: false }) → openAtLogin=true、launchItems[0].enabled=false、executableWillLaunchAtLogin=false 即登录项已注册(任务管理器「启动」里可见、显示为已禁用)但不会随系统拉起。 所以改成始终注册登录项、只用 enabled 控制是否真正启动,语义与用户要求一致: - applyOpenAtLogin / setOpenAtLogin 都走 loginItemSettings(wanted) - macOS 无 enabled 参数,仍按注册/注销处理 - 状态快照的 openAtLogin 以系统实际状态为准(打包后读 executableWillLaunchAtLogin) - 文档同步:默认「已注册但未启用」 --- console/README.md | 6 +++--- console/main/config.js | 2 +- console/main/index.js | 37 +++++++++++++++++++++++++++---------- docs/部署指南.md | 4 ++-- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/console/README.md b/console/README.md index 8405238..f23f3ef 100644 --- a/console/README.md +++ b/console/README.md @@ -6,7 +6,7 @@ 2. 用 `screen.getAllDisplays()` 枚举显示器,把大屏页面投到扩展屏; 3. `powerSaveBlocker.start('prevent-display-sleep')` 防熄屏; 4. 提供启动/停止/状态/日志的管理 GUI 和托盘菜单; -5. 默认不随系统启动,可在服务管理窗口的「开机自启」开关里开启; +5. 默认注册登录项但不启用(不随系统启动),可在服务管理窗口的「开机自启」开关里开启; 6. 在服务管理窗口展示服务端在线的大屏实例(`GET /api/display/instances`)。 规范来源:`devdocs/06-大屏端设计.md` 第 6 节、`devdocs/10-部署与现场方案.md` 第 4 节。 @@ -82,7 +82,7 @@ scoreboard-windows-amd64/ | 停止服务 | 先 `POST server.shutdownPath` 并带 `X-Shutdown-Token`(启动时用 `crypto.randomBytes(32)` 生成,经环境变量 `SCOREBOARD_SHUTDOWN_TOKEN` 只交给该子进程)请求服务端优雅关闭;受理后最多等 `shutdownTimeoutMs`,超时或接口不可用(404/连接失败)再强杀 | | 已连接大屏实例 | 管理窗口打开时每 5 秒读一次服务端 `GET /api/display/instances`(复用管理面板的 `scoreboard_session` Cookie,需记录员及以上),显示实例数、页面名与在线状态;窗口隐藏即停表,未登录/服务未启动时显示占位文案 | | 关管理窗口 | 只隐藏到托盘,服务端继续运行;退出必须走托盘「退出」 | -| 开机自启 | 默认关闭;在服务管理窗口的「开机自启」开关里控制(`startup.openAtLogin`),仅打包后的 Windows/macOS 生效,开发态不注册 | +| 开机自启 | 默认「已注册但未启用」;在服务管理窗口的「开机自启」开关里控制(`startup.openAtLogin`),仅打包后的 Windows/macOS 生效,开发态不注册 | | 窗口外观 | 管理窗口与服务端管理面板窗口都是 `frame: false` 无边框,标题栏与最小化/最大化/关闭三键由页面自绘(拖拽区 `-webkit-app-region: drag`,按钮 `no-drag`,双击标题栏切最大化,关闭键 hover 变红) | | 服务端管理面板 | 用独立无边框 Electron 窗口加载 `http://127.0.0.1:/admin`(`shell-preload.js` 暴露 `window.desktopShell`),不再调系统浏览器;面板页在浏览器里直接打开时标题栏自动不渲染 | | 管理员账号 | 主进程 `spawn` 同目录 `scoreboard.exe passwd -config <同一份配置> -user <当前用户名> -new-username <新用户名> -password <新密码>`(留空 = 不改 / 随机生成)。本机进程、不经 HTTP,因此免登录验证;用户名/密码含空格、制表符或换行会被主进程拒绝,密码不进日志。`passwd` 会作废该用户全部会话,UI 提交前二次确认并提示 | @@ -111,7 +111,7 @@ scoreboard-windows-amd64/ | `display.reloadEveryHours` | `24` | 定时重载兜底,0 = 关闭 | | `display.waitForServerMs` | `20000` | 服务端未就绪时开大屏的兜底等待 | | `display.backgroundColor` | `#0b0f1a` | 窗口底色,减少加载白闪 | -| `startup.openAtLogin` | `false` | 默认不随系统启动;在服务管理窗口「开机自启」开关里控制(开发态不注册) | +| `startup.openAtLogin` | `false` | 默认注册但不启用(任务管理器可见、已禁用);在服务管理窗口「开机自启」开关里控制(开发态不注册) | | `admin.width` / `admin.height` | `1040` / `760` | 管理窗口初始尺寸(服务端管理面板窗口按 `width` 不小于 1100 取用) | | `log.maxLines` / `log.file` / `log.maxFileBytes` | `2000` / `logs/console.log` / `4194304` | 日志缓冲与文件 | diff --git a/console/main/config.js b/console/main/config.js index f92cd41..1efbded 100644 --- a/console/main/config.js +++ b/console/main/config.js @@ -39,7 +39,7 @@ const DEFAULTS = { backgroundColor: '#0b0f1a' }, startup: { - // 默认不随系统启动;服务管理窗口里有开关,打开后写入登录项 + // 默认注册登录项但不启用(任务管理器「启动」里可见);服务管理窗口的开关控制是否真的随系统启动 openAtLogin: false }, admin: { diff --git a/console/main/index.js b/console/main/index.js index d4cdc4b..e30320c 100644 --- a/console/main/index.js +++ b/console/main/index.js @@ -56,6 +56,17 @@ function main() { return path.join(__dirname, '..', 'config.json') } + // 开关显示以系统实际状态为准:打包后的 Windows 读登录项 enabled,其余回落配置值。 + function openAtLoginEffective() { + const configured = cfg ? cfg.values.startup.openAtLogin === true : false + if (!app.isPackaged || process.platform !== 'win32') return configured + try { + return app.getLoginItemSettings().executableWillLaunchAtLogin === true + } catch { + return configured + } + } + function statusSnapshot() { const status = service ? service.status : { state: 'stopped', stateLabel: '已停止' } return { @@ -64,7 +75,7 @@ function main() { configWarning: cfg ? cfg.warning : null, displays: displays ? displays.getInfo() : [], powerSaveBlocked: powerBlockerId !== null ? powerSaveBlocker.isStarted(powerBlockerId) : false, - openAtLogin: cfg ? cfg.values.startup.openAtLogin === true : false, + openAtLogin: openAtLoginEffective(), openAtLoginSupported: app.isPackaged && (process.platform === 'win32' || process.platform === 'darwin'), appVersion: app.getVersion() } @@ -295,14 +306,16 @@ function main() { tray.on('double-click', () => showAdmin()) } - // 开机自启:只对打包后的 Windows/macOS 程序有意义,开发态注册的是 electron 可执行文件 + // 开机自启:只对打包后的程序有意义,开发态注册的是 electron 可执行文件。 + // Windows 上始终注册登录项(任务管理器「启动」里能看到),用 enabled 控制是否真的拉起: + // 实测 {openAtLogin:true, enabled:false} → openAtLogin=true 且 executableWillLaunchAtLogin=false, + // 即「已注册但未启用」。macOS 没有 enabled 参数,只能注册/注销。 function applyOpenAtLogin() { if (!app.isPackaged || (process.platform !== 'win32' && process.platform !== 'darwin')) return - // 只有显式 true 才注册;缺省 / null / false 一律不写登录项 const wanted = cfg.values.startup?.openAtLogin === true try { - app.setLoginItemSettings({ openAtLogin: wanted }) - log.push('console', wanted ? '开机自启已启用' : '开机自启已关闭') + app.setLoginItemSettings(loginItemSettings(wanted)) + log.push('console', wanted ? '开机自启已启用' : '开机自启已注册但未启用') } catch (err) { log.push('console', `设置开机自启失败:${err.message}`) } @@ -315,9 +328,13 @@ function main() { }) } - // 服务管理窗口的「开机自启」开关。Windows 的 Run 项没有「已注册但禁用」的 - // 可靠中间态(任务管理器那种禁用走 StartupApproved 私有键,不可靠), - // 所以「不启动」= 不写登录项:打开时写入,关闭时删除。 + function loginItemSettings(enabled) { + return process.platform === 'win32' + ? { openAtLogin: true, enabled } + : { openAtLogin: enabled } + } + + // 服务管理窗口的「开机自启」开关:登录项一直保留,只切换是否真正随系统拉起。 function setOpenAtLogin(enabled) { if (!cfg) return { ok: false, message: '配置尚未加载' } const wanted = enabled === true @@ -334,7 +351,7 @@ function main() { if (app.isPackaged && (process.platform === 'win32' || process.platform === 'darwin')) { try { - app.setLoginItemSettings({ openAtLogin: wanted }) + app.setLoginItemSettings(loginItemSettings(wanted)) } catch (err) { // 登录项没写成功,配置也回滚,避免界面与系统状态不一致 cfg.values.startup.openAtLogin = previous @@ -348,7 +365,7 @@ function main() { } } - log.push('console', wanted ? '开机自启已启用' : '开机自启已关闭') + log.push('console', wanted ? '开机自启已启用' : '开机自启已关闭(登录项保留,任务管理器里可见)') return { ok: true, openAtLogin: wanted } } diff --git a/docs/部署指南.md b/docs/部署指南.md index 2a0f8be..5c4bd0e 100644 --- a/docs/部署指南.md +++ b/docs/部署指南.md @@ -65,7 +65,7 @@ deploy\build.bat - 打开服务端管理面板窗口; - 把大屏页面投到扩展屏; - 启用防熄屏; -- 不注册开机自启;需要随系统启动时,在服务管理窗口的「开机自启」开关里开启(仅打包后的 Windows 版本生效)。 +- 注册开机自启登录项但**不启用**(任务管理器「启动」里可见、状态为「已禁用」);需要随系统启动时,在服务管理窗口的「开机自启」开关里打开(仅打包后的 Windows 版本生效)。 部署目录结构(安装或解压后): @@ -216,7 +216,7 @@ scoreboard.exe passwd -config config.yaml | `display.reloadEveryHours` | `24` | 定时重载兜底,`0` = 关闭 | | `display.waitForServerMs` | `20000` | 服务端未就绪时,最多等这么久也先把大屏开出来 | | `display.backgroundColor` | `#0b0f1a` | 大屏窗口底色,减少加载白闪 | -| `startup.openAtLogin` | `false` | 默认不随系统启动;在服务管理窗口「开机自启」开关里控制 | +| `startup.openAtLogin` | `false` | 默认注册但不启用(任务管理器可见、已禁用);在服务管理窗口「开机自启」开关里控制 | | `admin.width` / `admin.height` | `1040` / `760` | 启动管理窗口初始尺寸 | | `log.maxLines` | `2000` | 内存中保留的日志行数 | | `log.file` | `logs/console.log` | 日志文件,相对 `config.json` 所在目录 |