feat(platform): Console 支持 Linux(arm64),Windows 行为逐字不变;版本 dev_0.5.10

需求:让积分榜能在飞腾 D2000(ARM64)的 Linux 上跑起来,**同时不破坏 Windows**。

## 一、Go 服务端:交叉编译即可(**已实测**)
- SQLite 用的是 `modernc.org/sqlite`(纯 Go 驱动),全项目**没有一处 `import "C"`** → `CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build` 直接出;
- 产物是**静态链接**(`ELF 64-bit LSB executable, ARM aarch64, statically linked`)→ **不依赖 glibc 版本**,对国产系统友好;
- 本机(arm64)**实际启动验证**:数据目录、局域网地址探测、数据库迁移(0001/0002/0003)全部正常;
- 产物:`debug/服务端-arm64/scoreboard`(29 MB,`-s -w`)。

## 二、Console(Electron 宿主):平台差异收口到一个新文件
- **新增 `console/main/platform.js`**:平台差异集中在这 45 行里,其余代码不写平台判断。
  ⚠️ **`platform` 参数缺省按 Windows 解析** —— 漏传参数走的就是改动前那条老路(这比"改了再小心"更保守:**默认行为不变,新行为必须显式传参**)。
- `userpaths.js` / `datadir.js`:Windows 分支 `%LOCALAPPDATA%\ScoreboardConsole\` **逐字保留**;Linux 分支按 XDG(`$XDG_DATA_HOME`,缺省 `~/.local/share`)。两平台各自的"拿不到用户目录"提示文案分开写(免得现场去设一个本平台不存在的环境变量)。
- `config.js`:服务端文件名按平台取(Windows `scoreboard.exe` / Linux `scoreboard`)。
- **新增 `console/main/autostart.js`**:Linux 用 `~/.config/autostart/*.desktop`(开=写文件、关=删文件;Linux 没有"已注册但未启用"这个中间态)。
- 托盘与窗口行为:Linux 的通知区多数不派发 double-click,因此按平台分支成单击。

## 三、怎么保证没弄坏 Windows
1. **设计上**:平台差异集中 + 缺省值就是 Windows(见上);
2. **逐行核对 diff**:Windows 分支那几行原样未动;
3. **测试**:Console 由 **68 → 96 例**,新增全是平台分支用例,**既有断言一条没改**;
4. **真机验证**:构建机重打 Windows 包并跑(这一道才算收口)。

## 四、自检
全量自检两轮**全绿、`EXIT=0`**:服务端 14 包 / 前端 **944 例** / Console **96 例**。

## 版本
`dev_0.5.9` → **`dev_0.5.10`**;安装器素材同步重生成(照例只动 `brand.bmp` 两张)。
This commit is contained in:
AskaEth committed 2026-09-15 00:53:16 +08:00
1 parent 55051fd633
commit efa049c827
19 files changed
+665 -58

No files matched your search

+31 -12
View File
@@ -3,17 +3,21 @@
const fs = require('fs')
const path = require('path')
const { APP_DIR_NAME, xdgDataHome } = require('./platform')
// 数据目录必须落在安装目录之外:electron-builder 的卸载器结尾是 `RMDir /r $INSTDIR`,
// 而覆盖安装会先静默跑一遍旧版卸载器 → 装在安装目录里的 data/ 会被整体删掉。
// 也不能放 %APPDATA%\<AppName>(卸载器同样会删),所以默认落在 %LOCALAPPDATA%\ScoreboardConsole\data。
// 配置与规则目录(userpaths.js)用同一个基座,这里导出给它复用。
const LOCAL_BASE = 'ScoreboardConsole'
// 也不能放 %APPDATA%\<AppName>(卸载器同样会删),所以 Windows 上默认落在
// %LOCALAPPDATA%\ScoreboardConsole\data;Linux 上同一套思路,换成 XDG 的数据目录
// ($XDG_DATA_HOME\ScoreboardConsole\data,缺省 ~/.local/share/…)。
// 目录名与配置/规则目录(userpaths.js)同一个基座,名字在 platform.js 里。
const DATA_SUBDIR = 'data'
// 解析最终数据目录。installDir 是安装目录(打包后 = exe 所在目录),
// 相对路径按安装目录解析,现场写 `data`、`D:\scoreboard-data` 都能用。
// 相对路径按安装目录解析,现场写 `data`、`D:\scoreboard-data`、`/srv/scoreboard-data` 都能用。
// passToServer=false 表示不给服务端加 -data,沿用服务端自身默认(开发态)。
function resolveDataDir({ values, installDir, env, isPackaged }) {
// platform 缺省按 Windows 解析(改动前的口径),生产调用点传 process.platform。
function resolveDataDir({ values, installDir, env, isPackaged, platform = 'win32' }) {
const base = path.resolve(installDir || '.')
const configured = values && values.server ? values.server.dataDir : ''
@@ -31,16 +35,31 @@ function resolveDataDir({ values, installDir, env, isPackaged }) {
return { dir: path.join(base, DATA_SUBDIR), source: 'default', passToServer: false }
}
const localAppData = env ? env.LOCALAPPDATA : ''
if (!localAppData) {
// 拿不到 %LOCALAPPDATA% 时退回老位置:这次覆盖安装仍有丢数据风险,
// 但总好过把数据指向一个猜出来的路径
if (platform === 'win32') {
const localAppData = env ? env.LOCALAPPDATA : ''
if (!localAppData) {
// 拿不到 %LOCALAPPDATA% 时退回老位置:这次覆盖安装仍有丢数据风险,
// 但总好过把数据指向一个猜出来的路径
return { dir: path.join(base, DATA_SUBDIR), source: 'fallback', passToServer: false }
}
return {
dir: path.join(localAppData, APP_DIR_NAME, DATA_SUBDIR),
source: 'localappdata',
passToServer: true
}
}
// Linux(UOS / 麒麟)按 XDG 规范取数据目录;取不到 $XDG_DATA_HOME 也取不到 $HOME 时
// 与 Windows 上拿不到 %LOCALAPPDATA% 一样退回安装目录,宁可记一条警告也不猜路径
const xdg = xdgDataHome(env)
if (!xdg) {
return { dir: path.join(base, DATA_SUBDIR), source: 'fallback', passToServer: false }
}
return {
dir: path.join(localAppData, LOCAL_BASE, DATA_SUBDIR),
source: 'localappdata',
dir: path.join(xdg, APP_DIR_NAME, DATA_SUBDIR),
source: 'xdg',
passToServer: true
}
}
@@ -120,4 +139,4 @@ function migrateLegacyData({ targetDir, legacyDir, log }) {
return { migrated: true, reason: 'moved' }
}
module.exports = { resolveDataDir, migrateLegacyData, isInside, LOCAL_BASE }
module.exports = { resolveDataDir, migrateLegacyData, isInside }