## 现场现象
在「按单位手动录名次」的项目(例如篮球)的录成绩页,往底部「团体名次」输入框里填一个数字,
**整个录入页立刻变空白**,控制台 `TypeError: e.trim is not a function`(栈里是 computed 的 getter)。
## 根因(老 bug,不是联队引入)
- 模板是 `v-model="row.input"` + `type="number"`。
- **Vue 3 的 `vModelText` 对 `type="number"` 会自动做数值转换**
(`castToNumber = number || (vnode.props && vnode.props.type === 'number')`)→ 填了数字之后值是
`number`;只有**清空**时 `looseToNumber('')` 才留空串。当初的注释只写了后一半,漏了前一半。
- `teamRanks.ts` 的 `parseTeamRankInput(raw: string)` 里直接 `raw.trim()` → TypeError;
而它的三个调用点(保存载荷 / 重复名次提示 / 描红)**全在渲染期的 computed 里** → 整页白屏。
证据:0.5.2(联队之前)的 `EntryPage.vue` 里就已是这套写法,`teamRanks.ts` 本轮一个字没改过。
## 修复
- `teamRanks.ts`:`parseTeamRankInput` 参数与 `TeamRankRow.input` 类型如实标注 `string | number`,
解析前 `String(raw).trim()`;`teamRanksDirty` 归一后比较(否则 `number 3` 与 `string '3'` 会被判成
「已修改」,保存按钮空亮着却什么都没改)。
- `EntryPage.vue`:**同类第二处** —— 「批量设置批次」的 `heatInput` / `heatSizeInput` 同样是
`type="number"` 且下游 `.trim()`,**同样会整页白屏**,一并修掉。
- 写法与仓库既有的正确实现保持一致(`CategoriesSection.vue` / `AthletesSection.vue` 的 `toAge()`)。
## 同类隐患体检
把全仓库 **21 个 `type="number"` 的 v-model 绑定点**逐个追了下游用法(解析 / 比较 / `.trim()` / 传接口):
**真正会炸的只有上面这 3 处**,其余 18 处本来就用 `Number()` 归一,安全。
## 验证
- **先红后绿**:修复前 3 例红(`raw.trim is not a function` 原地复现:number 解析抛错、computed 调用链抛错、
脏判断误报 `true !== false`),修复后 13/13 通过。
- 前端用例 809 → **813**,0 fail;`vue-tsc` / `pnpm build` 退出码 0。
- 全量自检两轮全绿(服务端 14 包 / Console 68 例);退出码直接读,未用管道吞。
## 版本
`dev_0.5.3` → **`dev_0.5.4`**(0.5.3 已产出并记过哈希,不重出同名包);安装器素材同步重生成
(版本号烙在位图里,实测仍只有 `brand.bmp` 的 1x/2x 两张变化)。
88 lines
2.0 KiB
JSON
88 lines
2.0 KiB
JSON
{
|
|
"name": "scoreboard-console",
|
|
"productName": "Scoreboard Console",
|
|
"version": "0.5.4",
|
|
"private": true,
|
|
"author": "AskaEth",
|
|
"description": "赛事积分榜 · 桌面宿主:守护 Go 服务端并把大屏投到扩展屏",
|
|
"main": "main/index.js",
|
|
"scripts": {
|
|
"start": "electron .",
|
|
"check": "node scripts/check-syntax.js",
|
|
"dist": "electron-builder --win --x64",
|
|
"pack": "electron-builder --dir"
|
|
},
|
|
"devDependencies": {
|
|
"electron": "^38.0.0",
|
|
"electron-builder": "^26.0.12"
|
|
},
|
|
"build": {
|
|
"appId": "top.yeij.scoreboard.console",
|
|
"productName": "Scoreboard Console",
|
|
"directories": {
|
|
"output": "../dist/console",
|
|
"buildResources": "resources"
|
|
},
|
|
"files": [
|
|
"main/**/*",
|
|
"renderer/**/*",
|
|
"config.json",
|
|
"package.json"
|
|
],
|
|
"extraFiles": [
|
|
{
|
|
"from": "../dist/scoreboard.exe",
|
|
"to": "scoreboard.exe",
|
|
"filter": ["**/*"]
|
|
},
|
|
{
|
|
"from": "../config.yaml",
|
|
"to": "config.yaml"
|
|
},
|
|
{
|
|
"from": "config.json",
|
|
"to": "config.json"
|
|
},
|
|
{
|
|
"from": "scoreboard.png",
|
|
"to": "scoreboard.png"
|
|
},
|
|
{
|
|
"from": "../rules",
|
|
"to": "rules"
|
|
},
|
|
{
|
|
"from": "extra",
|
|
"to": ".",
|
|
"filter": ["**/*", "!.gitkeep"]
|
|
}
|
|
],
|
|
"win": {
|
|
"executableName": "Scoreboard_Console",
|
|
"icon": "resources/icon.ico",
|
|
"target": [
|
|
{
|
|
"target": "nsis",
|
|
"arch": ["x64"]
|
|
},
|
|
{
|
|
"target": "zip",
|
|
"arch": ["x64"]
|
|
}
|
|
],
|
|
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
|
},
|
|
"nsis": {
|
|
"script": "installer/installer.nsi",
|
|
"oneClick": false,
|
|
"allowToChangeInstallationDirectory": true,
|
|
"createDesktopShortcut": true,
|
|
"shortcutName": "赛事积分榜",
|
|
"license": "../LICENSE",
|
|
"include": "resources/installer-custom.nsh",
|
|
"selectPerMachineByDefault": true,
|
|
"language": "2052"
|
|
}
|
|
}
|
|
}
|