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。
170 lines
6.4 KiB
JavaScript
170 lines
6.4 KiB
JavaScript
'use strict'
|
||
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
const { LOCAL_BASE } = require('./datadir')
|
||
|
||
// 这三样以前随安装包落在安装目录里,但都属于「用户改出来的东西」,必须跟数据目录一样
|
||
// 挪到安装目录之外:卸载器结尾是 `RMDir /r $INSTDIR`,覆盖安装又会先静默跑一遍旧卸载器,
|
||
// 放在安装目录里等于用户改一次丢一次。
|
||
// console\config.json → <用户根>\console\config.json
|
||
// config.yaml → <用户根>\config.yaml
|
||
// rules\ → <用户根>\rules\
|
||
// 用户根与数据目录同一基座:%LOCALAPPDATA%\ScoreboardConsole\
|
||
const CONSOLE_SUBDIR = 'console'
|
||
const CONSOLE_CONFIG_FILE = 'config.json'
|
||
const SERVER_CONFIG_FILE = 'config.yaml'
|
||
const RULES_SUBDIR = 'rules'
|
||
|
||
const ITEM_NAMES = ['consoleConfig', 'serverConfig', 'rulesDir']
|
||
|
||
const ITEM_LABEL = {
|
||
consoleConfig: 'Console 配置文件',
|
||
serverConfig: '服务端配置文件',
|
||
rulesDir: '规则目录'
|
||
}
|
||
|
||
// 用户根目录;拿不到 %LOCALAPPDATA% 时返回 null,由调用方回落到安装目录
|
||
function resolveUserRoot(env) {
|
||
const localAppData = env ? env.LOCALAPPDATA : ''
|
||
const value = typeof localAppData === 'string' ? localAppData.trim() : ''
|
||
return value ? path.join(value, LOCAL_BASE) : null
|
||
}
|
||
|
||
// 解析落点,纯函数不碰磁盘。mode='user' 才接管到用户目录;
|
||
// 其余情况(开发态、拿不到 %LOCALAPPDATA%)三项一律用安装目录里随包的那份,
|
||
// 行为与以前完全一致 —— 开发态不能动仓库里的文件。
|
||
function planUserPaths({ installDir, userRoot, isPackaged }) {
|
||
const install = path.resolve(installDir || '.')
|
||
const templates = {
|
||
consoleConfig: path.join(install, CONSOLE_CONFIG_FILE),
|
||
serverConfig: path.join(install, SERVER_CONFIG_FILE),
|
||
rulesDir: path.join(install, RULES_SUBDIR)
|
||
}
|
||
const items = {}
|
||
const targets = {
|
||
consoleConfig: path.join(install, CONSOLE_CONFIG_FILE),
|
||
serverConfig: path.join(install, SERVER_CONFIG_FILE),
|
||
rulesDir: path.join(install, RULES_SUBDIR)
|
||
}
|
||
|
||
const reason = !isPackaged ? 'dev' : userRoot ? null : 'no-localappdata'
|
||
if (!reason) {
|
||
targets.consoleConfig = path.join(userRoot, CONSOLE_SUBDIR, CONSOLE_CONFIG_FILE)
|
||
targets.serverConfig = path.join(userRoot, SERVER_CONFIG_FILE)
|
||
targets.rulesDir = path.join(userRoot, RULES_SUBDIR)
|
||
}
|
||
|
||
for (const name of ITEM_NAMES) {
|
||
items[name] = { target: path.resolve(targets[name]), template: path.resolve(templates[name]) }
|
||
}
|
||
return {
|
||
mode: reason ? 'install' : 'user',
|
||
reason,
|
||
root: userRoot ? path.resolve(userRoot) : null,
|
||
installDir: install,
|
||
items
|
||
}
|
||
}
|
||
|
||
// 目标目录必须真的能写:mkdir 只保证目录建出来,权限不足或路径被占要在这一步就暴露,
|
||
// 好让调用方回落到安装目录,而不是等到写配置文件时才失败
|
||
function ensureDir(dir) {
|
||
fs.mkdirSync(dir, { recursive: true })
|
||
fs.accessSync(dir, fs.constants.W_OK)
|
||
}
|
||
|
||
function tryRemove(dir) {
|
||
try {
|
||
fs.rmSync(dir, { recursive: true, force: true })
|
||
} catch {
|
||
// 清不掉就留着:只是本次拷贝留下的临时目录,下次会原地重建
|
||
}
|
||
}
|
||
|
||
// 规则目录先拷到临时目录再整体改名到位:中途失败不会留下半个 rules\,
|
||
// 否则用户目录里一旦存在同名目录,下次启动就按「已存在」跳过、再也不补拷
|
||
function copyDirOnce(template, target) {
|
||
const tmp = `${target}.tmp-${process.pid}`
|
||
try {
|
||
tryRemove(tmp)
|
||
fs.cpSync(template, tmp, { recursive: true })
|
||
fs.renameSync(tmp, target)
|
||
} catch (err) {
|
||
tryRemove(tmp)
|
||
throw err
|
||
}
|
||
}
|
||
|
||
// 按 plan 落盘,返回最终生效的路径。判定只有两条:
|
||
// 目标不存在 + 安装目录里有 → 复制过去(安装目录那份留作模板,反正卸载器会删);
|
||
// 目标已存在 → 一律用用户目录那份,绝不覆盖用户改过的东西。
|
||
// 任一目录建不出来 / 拷不动都只记日志,该项继续用安装目录里的那份,绝不删用户文件、
|
||
// 也绝不因此启动失败。
|
||
function prepareUserPaths({ plan, log }) {
|
||
const notify = typeof log === 'function' ? log : () => {}
|
||
const result = {
|
||
mode: plan.mode,
|
||
reason: plan.reason,
|
||
root: plan.mode === 'user' ? plan.root : null,
|
||
consoleConfig: plan.items.consoleConfig.template,
|
||
rulesDir: plan.items.rulesDir.template,
|
||
// null = 不接管:拉起服务端与 passwd 时沿用 config.json 里 -config 的原值
|
||
serverConfig: null
|
||
}
|
||
|
||
if (plan.mode !== 'user') {
|
||
if (plan.reason === 'no-localappdata') {
|
||
notify(
|
||
'警告:拿不到 %LOCALAPPDATA%,config.json / config.yaml / rules 继续用安装目录里的那份,' +
|
||
'覆盖安装会连同它们一起丢失'
|
||
)
|
||
}
|
||
return result
|
||
}
|
||
|
||
for (const name of ITEM_NAMES) {
|
||
const { target, template } = plan.items[name]
|
||
const label = ITEM_LABEL[name]
|
||
if (path.resolve(target) === path.resolve(template)) continue
|
||
|
||
try {
|
||
ensureDir(path.dirname(target))
|
||
if (!fs.existsSync(target)) {
|
||
if (!fs.existsSync(template)) {
|
||
notify(`警告:${label}在用户目录与安装目录里都不存在(${template}),本次跳过复制`)
|
||
} else {
|
||
if (name === 'rulesDir') copyDirOnce(template, target)
|
||
else fs.copyFileSync(template, target)
|
||
notify(`已把 ${label} 复制到 ${target}(安装目录里那份留作模板)`)
|
||
}
|
||
}
|
||
} catch (err) {
|
||
notify(
|
||
`警告:准备 ${label} 失败(${err.message}),继续用安装目录里的 ${template};` +
|
||
'覆盖安装会丢失对它的改动'
|
||
)
|
||
continue
|
||
}
|
||
|
||
if (name === 'serverConfig') result.serverConfig = target
|
||
else result[name] = target
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// 服务端读哪份 config.yaml 由 Console 定:config.json 里的 server.args 只声明「有个 -config 参数」,
|
||
// 它的值在拉起时换成上面解析出的绝对路径(不传 configPath 时原样返回,开发态就走这条路)
|
||
function withConfigArg(args, configPath) {
|
||
const list = Array.isArray(args) ? [...args] : []
|
||
if (typeof configPath !== 'string' || configPath === '') return list
|
||
const index = list.indexOf('-config')
|
||
if (index !== -1 && typeof list[index + 1] === 'string') list[index + 1] = configPath
|
||
else list.push('-config', configPath)
|
||
return list
|
||
}
|
||
|
||
module.exports = { resolveUserRoot, planUserPaths, prepareUserPaths, withConfigArg }
|