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。
114 lines
4.7 KiB
JavaScript
114 lines
4.7 KiB
JavaScript
'use strict'
|
||
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
// 数据目录必须落在安装目录之外:electron-builder 的卸载器结尾是 `RMDir /r $INSTDIR`,
|
||
// 而覆盖安装会先静默跑一遍旧版卸载器 → 装在安装目录里的 data/ 会被整体删掉。
|
||
// 也不能放 %APPDATA%\<AppName>(卸载器同样会删),所以默认落在 %LOCALAPPDATA%\ScoreboardConsole\data。
|
||
// 配置与规则目录(userpaths.js)用同一个基座,这里导出给它复用。
|
||
const LOCAL_BASE = 'ScoreboardConsole'
|
||
const DATA_SUBDIR = 'data'
|
||
|
||
// 解析最终数据目录。installDir 是安装目录(打包后 = exe 所在目录),
|
||
// 相对路径按安装目录解析,现场写 `data`、`D:\scoreboard-data` 都能用。
|
||
// passToServer=false 表示不给服务端加 -data,沿用服务端自身默认(开发态)。
|
||
function resolveDataDir({ values, installDir, env, isPackaged }) {
|
||
const base = path.resolve(installDir || '.')
|
||
const configured = values && values.server ? values.server.dataDir : ''
|
||
|
||
if (typeof configured === 'string' && configured.trim() !== '') {
|
||
const value = configured.trim()
|
||
return {
|
||
dir: path.isAbsolute(value) ? path.resolve(value) : path.resolve(base, value),
|
||
source: 'config',
|
||
passToServer: true
|
||
}
|
||
}
|
||
|
||
// 开发态不接管:继续用服务端默认(工作目录下的 data),免得仓库里的 data/ 被顶掉
|
||
if (!isPackaged) {
|
||
return { dir: path.join(base, DATA_SUBDIR), source: 'default', passToServer: false }
|
||
}
|
||
|
||
const localAppData = env ? env.LOCALAPPDATA : ''
|
||
if (!localAppData) {
|
||
// 拿不到 %LOCALAPPDATA% 时退回老位置:这次覆盖安装仍有丢数据风险,
|
||
// 但总好过把数据指向一个猜出来的路径
|
||
return { dir: path.join(base, DATA_SUBDIR), source: 'fallback', passToServer: false }
|
||
}
|
||
|
||
return {
|
||
dir: path.join(localAppData, LOCAL_BASE, DATA_SUBDIR),
|
||
source: 'localappdata',
|
||
passToServer: true
|
||
}
|
||
}
|
||
|
||
// 判断 dir 是否落在 base 之内(含 base 本身):用于提醒「数据还在安装目录里」,
|
||
// 这种配置在覆盖安装时会被卸载器连目录一起删掉
|
||
function isInside(base, dir) {
|
||
const rel = path.relative(path.resolve(base), path.resolve(dir))
|
||
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))
|
||
}
|
||
|
||
// missing = 不存在,empty = 存在但空,has-data = 有内容
|
||
// 读不出来时返回 error,由调用方按「可能有数据」处理,宁可不迁移
|
||
function dirState(dir) {
|
||
let entries
|
||
try {
|
||
entries = fs.readdirSync(dir)
|
||
} catch (err) {
|
||
return err.code === 'ENOENT' ? 'missing' : 'error'
|
||
}
|
||
return entries.length > 0 ? 'has-data' : 'empty'
|
||
}
|
||
|
||
// 一次性迁移:只在「目标为空且旧目录有数据」时把旧目录整体改名过去。
|
||
// 任何一步失败都只记日志、旧数据保持原地,绝不删除数据;
|
||
// 两边都有数据时不做任何动作,只提示现场人工确认。
|
||
function migrateLegacyData({ targetDir, legacyDir, log }) {
|
||
const notify = typeof log === 'function' ? log : () => {}
|
||
if (!targetDir || !legacyDir) return { migrated: false, reason: 'invalid' }
|
||
if (path.resolve(targetDir) === path.resolve(legacyDir)) return { migrated: false, reason: 'same-dir' }
|
||
|
||
const target = dirState(targetDir)
|
||
const legacy = dirState(legacyDir)
|
||
|
||
if (target === 'error') {
|
||
notify(`警告:读不到数据目录 ${targetDir},跳过旧数据迁移;旧数据仍在 ${legacyDir}`)
|
||
return { migrated: false, reason: 'target-unreadable' }
|
||
}
|
||
|
||
if (target === 'has-data') {
|
||
if (legacy === 'has-data') {
|
||
notify(
|
||
`警告:旧数据目录 ${legacyDir} 与新数据目录 ${targetDir} 都有数据,未做迁移。` +
|
||
'请人工确认要保留哪一份(两份都不会被自动删除)'
|
||
)
|
||
return { migrated: false, reason: 'both' }
|
||
}
|
||
return { migrated: false, reason: 'target-has-data' }
|
||
}
|
||
|
||
if (legacy !== 'has-data') return { migrated: false, reason: 'no-legacy' }
|
||
|
||
try {
|
||
// Windows 上 rename 到已存在的目录会失败,先摘掉这个空目录(空目录里没有数据)
|
||
if (target === 'empty') fs.rmdirSync(targetDir)
|
||
fs.mkdirSync(path.dirname(targetDir), { recursive: true })
|
||
fs.renameSync(legacyDir, targetDir)
|
||
} catch (err) {
|
||
notify(
|
||
`警告:迁移旧数据失败(${err.message}),旧数据仍保留在 ${legacyDir};` +
|
||
`本次服务端将使用 ${targetDir},请人工把旧目录内容拷过去`
|
||
)
|
||
return { migrated: false, reason: 'failed' }
|
||
}
|
||
|
||
notify(`已把旧数据从 ${legacyDir} 迁移到 ${targetDir}`)
|
||
return { migrated: true, reason: 'moved' }
|
||
}
|
||
|
||
module.exports = { resolveDataDir, migrateLegacyData, isInside, LOCAL_BASE }
|