feat: 人员年龄/号码布、图片上传、多显示器菜单、Console 两栏、exe 改名等一批改动
人员与报名:
- 人员唯一标识改为「证件号 或 号码布」二选一(0013/0014 迁移 + 导入匹配/回填/冲突)
- 人员新增「年龄」(0015 迁移):表单/列表/导入模板表头 11 列;冲突比较只在两边
都有值且不同时才算差异,旧数据不误报
- 冲突决策键改为「证件号优先 → 号码布」(前端原先只用 idCard,号码布行会互相覆盖)
- 报名选择器支持无证件号但有号码布的运动员
大屏与 Console:
- 大屏控制新增「显示器」菜单:可给指定显示器重建全屏窗口;单屏/主显示器强制窗口化,
限制在 Console 侧判定,不信任页面传参
- Console 服务管理窗口改左右两栏,运行日志独占右栏满高;窗口默认尺寸 1280×860
- 大屏右上角时钟精确到秒(同时把刷新间隔 15s 改为 1s)
图片与配置:
- 新增图片上传 POST /api/uploads(recorder+,png/jpg/webp/gif,≤5MiB)与匿名静态
访问 GET /uploads/{name}(防目录穿越);文件落赛事项目目录 uploads/,随赛事包
一起导出/导入
- 「赛事信息」新增赛事会标 / 大屏会标 / 大屏背景图三个上传项
- 参赛单位 logo 输入框右侧加上传按钮
关于页与构建:
- 构建时间打通:version.BuildTime + /api/system/about 返回 + build.bat 注入
(用 PowerShell 取时间戳,避免本地化 %DATE% 的非 ASCII 星期)
安装包:
- 主程序改名 Scoreboard_Console.exe(win.executableName;productName/显示名不变)
- 记住上次安装目录:自定义 NSIS 宏优先沿用自存路径(卸载不清除),并在 customInit
里补写回 electron-builder 的键,避免安装模式页把 $INSTDIR 打回默认
自检:服务端 12 包全绿;前端 vue-tsc + build 通过、node --test 58/58;console 12 文件通过。
This commit is contained in:
1 parent
24fff8dd08
commit
6483acf9ad
50 files changed
+2980
-395
No files matched your search
+59
-1
@@ -8,7 +8,8 @@ const {
|
||||
Menu,
|
||||
Tray,
|
||||
ipcMain,
|
||||
powerSaveBlocker
|
||||
powerSaveBlocker,
|
||||
screen
|
||||
} = require('electron')
|
||||
|
||||
const { loadConfig, updateConfig, resolveExecutable } = require('./config')
|
||||
@@ -247,6 +248,57 @@ function main() {
|
||||
})
|
||||
}
|
||||
|
||||
// 服务端管理面板是大屏窗口的第二个入口:页名只收 ASCII slug,
|
||||
// 其余(含空值)交给 DisplayManager 回落配置里的默认页
|
||||
function safeDisplayPage(value) {
|
||||
return typeof value === 'string' && /^[a-z0-9-]{1,32}$/.test(value) ? value : null
|
||||
}
|
||||
|
||||
function findDisplay(displayId) {
|
||||
return screen.getAllDisplays().find((item) => String(item.id) === String(displayId)) || null
|
||||
}
|
||||
|
||||
// 全屏窗口只允许出现在「多屏 + 非主屏」上:单屏或主屏开全屏会把用户桌面整个盖住,
|
||||
// 现场又没有键盘能退出来。所以放不放行由主进程判定,页面传什么都不作数。
|
||||
function openDisplayWindow(displayId, options) {
|
||||
if (!displays) return { ok: false, created: false, mode: null, message: '显示器管理尚未就绪' }
|
||||
// IPC 入参一律当不可信处理
|
||||
const payload = options && typeof options === 'object' ? options : {}
|
||||
const target = findDisplay(displayId)
|
||||
if (!target) return { ok: false, created: false, mode: null, message: '未找到该显示器,可能已被拔出' }
|
||||
|
||||
const all = screen.getAllDisplays()
|
||||
const singleMachine = all.length <= 1
|
||||
const allowFullscreen = !singleMachine && target.id !== screen.getPrimaryDisplay().id
|
||||
const wantedFullscreen = payload.fullscreen === true
|
||||
const downgraded = wantedFullscreen && !allowFullscreen
|
||||
|
||||
const result = displays.openOn(target, {
|
||||
page: safeDisplayPage(payload.page),
|
||||
single: !(wantedFullscreen && allowFullscreen)
|
||||
})
|
||||
log.push('console', `管理面板${result.created ? '创建' : '聚焦'}大屏窗口 display=${target.id}(${result.mode === 'kiosk' ? '全屏 kiosk' : '窗口化'})`)
|
||||
if (downgraded) {
|
||||
const reason = singleMachine ? '当前只有一个显示器' : '主显示器'
|
||||
log.push('console', `管理面板请求在 display=${target.id} 上开全屏:${reason}只能窗口化,已降级`)
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
created: result.created,
|
||||
mode: result.mode,
|
||||
message: downgraded ? `${singleMachine ? '当前只有一个显示器' : '主显示器'}只能窗口化` : undefined
|
||||
}
|
||||
}
|
||||
|
||||
function closeDisplayWindow(displayId) {
|
||||
if (!displays) return { ok: false, closed: false, message: '显示器管理尚未就绪' }
|
||||
const target = findDisplay(displayId)
|
||||
// 显示器拔了但窗口还在时,也允许按原 id 直接收窗
|
||||
const closed = displays.closeDisplay(target ? target.id : Number(displayId))
|
||||
return { ok: true, closed }
|
||||
}
|
||||
|
||||
// 渲染层要随时知道最大化状态,才能在「最大化/还原」之间换图标
|
||||
function bindWindowStateEvents(win) {
|
||||
const notify = () => {
|
||||
@@ -455,6 +507,12 @@ function main() {
|
||||
if (displays && displays.enabled) displays.relayout()
|
||||
return statusSnapshot()
|
||||
})
|
||||
ipcMain.handle('display:list', () => {
|
||||
if (!displays) return { single: true, displays: [] }
|
||||
return displays.listDisplays()
|
||||
})
|
||||
ipcMain.handle('display:open', (event, displayId, options) => openDisplayWindow(displayId, options))
|
||||
ipcMain.handle('display:close', (event, displayId) => closeDisplayWindow(displayId))
|
||||
ipcMain.handle('app:open-admin-window', () => {
|
||||
showAdmin()
|
||||
return true
|
||||
|
||||
Reference in new issue
Block a user