Files
Scoreboard/console/main/config.js
T
AskaEth 407abec3b0 fix: 大屏背景图被兜底渐变盖住 + 配置/规则也搬到用户数据目录
1) 大屏背景图不生效:App.vue 里 .bg 与 .bg-fallback 都是 absolute inset:0,
   而兜底渐变是不透明的且在图片之后绘制 → 配了背景图也只看得到渐变。
   改成 v-if/v-else 互斥(用无头 Chromium 真渲染对照验证:修复后取到图片像素,
   回退版本只剩渐变)。新增 background-layer.test.ts 锁住层次关系。

2) 规则/config 仍在安装目录,覆盖安装/卸载会连配置一起删:现在三样都落在
   %LOCALAPPDATA%\ScoreboardConsole\(与数据目录同一基座):
   - console\config.json(Console 读写都用这份)
   - config.yaml(Console 用绝对路径 -config 传给服务端与 passwd)
   - rules\(首次从安装目录复制过去,之后只认用户目录那份)
   首次运行复制迁移、已存在不覆盖、不可写则回落安装目录并记日志(不影响启动);
   开发态行为不变。备份脚本与文档同步。
   注意 resolveExecutable 必须显式传部署包根目录,否则会去用户目录找 scoreboard.exe。

自检:服务端 11 包全绿;console 语法 16 文件 + node:test 20/20;
前端 vue-tsc + build、node --test 69/69。
2026-09-10 10:06:21 +08:00

127 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict'
const fs = require('fs')
const path = require('path')
// 默认值写死在代码里:config.json 只覆盖要改的项。
// 这样升级新增字段时老配置依然能用,现场也不容易配出半成品。
const DEFAULTS = {
server: {
exe: 'scoreboard.exe',
// -config 只声明「有个配置文件参数」,值在拉起服务端时被换成解析出的绝对路径:
// 打包态指向 <用户根>\config.yaml,开发态保持这里的相对值(见 userpaths.js)
args: ['-config', 'config.yaml'],
// 数据目录。空串 = 用 Console 选的默认位置(打包态是
// %LOCALAPPDATA%\ScoreboardConsole\data,装在安装目录之外,覆盖安装不会丢数据);
// 填了就按它来,相对路径以安装目录为基准(详见 datadir.js)
dataDir: '',
host: '127.0.0.1',
port: 19540,
autoStart: true,
healthIntervalMs: 5000,
restartDelayMs: 3000,
// 0 表示无限重启;服务端是现场唯一进程,崩了必须拉起来
maxRestarts: 0,
// 优雅关闭握手:服务端需实现该回环接口并校验 X-Shutdown-Token,
// 未实现(404/连接失败)时回退到进程信号
shutdownPath: '/api/shutdown',
// 服务端受理关闭请求后等待其自行退出的上限,超过则强杀
shutdownTimeoutMs: 5000,
// 连续健康这么久后清零崩溃计数,避免历史崩溃耗尽 maxRestarts
healthyResetMs: 60000
},
display: {
// all-secondary | all | specified
selection: 'all-secondary',
displays: [],
// 只能写大屏实际支持的页名(见 web/src/display/types.ts 的 PAGE_ALIASES)
pages: ['standings', 'medals', 'individual', 'results'],
pageParam: 'page',
kiosk: true,
ignoreMouse: true,
reloadEveryHours: 24,
// 服务端迟迟没就绪时的兜底:超时后也把大屏开出来,避免现场一片黑
waitForServerMs: 20000,
backgroundColor: '#0b0f1a'
},
startup: {
// 默认注册登录项但不启用(任务管理器「启动」里可见);服务管理窗口的开关控制是否真的随系统启动
openAtLogin: false
},
admin: {
width: 1280,
height: 860
},
log: {
maxLines: 2000,
file: 'logs/console.log',
maxFileBytes: 4 * 1024 * 1024
}
}
function isPlainObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}
function merge(base, override) {
const out = isPlainObject(base) ? { ...base } : {}
if (!isPlainObject(override)) return out
for (const [key, value] of Object.entries(override)) {
if (value === undefined) continue
out[key] = isPlainObject(value) && isPlainObject(base[key]) ? merge(base[key], value) : value
}
return out
}
function readJson(file) {
try {
const raw = fs.readFileSync(file, 'utf8')
return { values: JSON.parse(raw), warning: null }
} catch (err) {
if (err.code === 'ENOENT') return { values: {}, warning: `配置文件不存在,使用内置默认值:${file}` }
return { values: {}, warning: `配置文件无法解析,使用内置默认值:${file}(${err.message})` }
}
}
function loadConfig(configPath) {
const { values, warning } = readJson(configPath)
return {
path: configPath,
values: merge(DEFAULTS, values),
warning
}
}
// 就地改配置并写回文件:只动 raw 里已有的键,不把 DEFAULTS 全量落盘,
// 否则升级新增的默认值会被老文件冻结成显式值。
function updateConfig(configPath, mutate) {
const { values } = readJson(configPath)
const raw = isPlainObject(values) ? values : {}
mutate(raw)
fs.mkdirSync(path.dirname(configPath), { recursive: true })
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + '\n', 'utf8')
return raw
}
// 相对路径的 exe 需要按「部署包根目录」理解,而部署包根目录在不同形态下不同:
// 开发时是仓库的 console/,打包后是 exe 所在目录。逐个候选位置探测,避免写死。
// baseDir 是显式给出的部署包根目录;不传时退回「按 configPath 所在目录推断」——
// 打包态 config.json 已经搬到用户数据目录,那里推不出安装目录,必须显式传。
function resolveExecutable(exe, configPath, baseDir) {
if (!exe) return { path: null, candidates: [] }
if (path.isAbsolute(exe)) return { path: exe, candidates: [exe] }
const configDir = baseDir ? path.resolve(baseDir) : path.dirname(configPath)
const candidates = [
configDir,
process.cwd(),
path.resolve(configDir, '..'),
path.resolve(configDir, '..', 'dist')
].map((dir) => path.resolve(dir, exe))
const found = candidates.find((candidate) => fs.existsSync(candidate))
return { path: found || candidates[0], candidates: [...new Set(candidates)] }
}
module.exports = { loadConfig, updateConfig, resolveExecutable }