fix(admin): 组别填了年龄后「保存全部」无反应(number 字段被当字符串处理)

Vue 3 对 <input type="number"> 的 v-model 会自动 parseFloat(空值仍为 ''),
所以填了年龄后 row.ageMin/ageMax 是 number,而校验里写的是 row.ageMin.trim(),
点击处理器抛 TypeError 直接中断 —— 界面毫无反应;留空时是 '',.trim() 正常,所以能保存。

- CategoriesSection:新增 toAge() 统一取值(String() 包一层,非法值返回 NaN 交给校验标红),
  validate 与 buildInputs 都改用它;CategoryRow 的数值字段类型如实标注为 string | number
- AdjustmentsPage:调整分值的 formDelta.value.trim() 同样会崩,改为 String(...).trim()

自检:vue-tsc + pnpm build 通过,前端 node --test 26/26。
This commit is contained in:
AskaEth committed 2026-09-10 01:58:04 +08:00
1 parent c2696e768e
commit 7531ea29e1
2 files changed
+24 -9

No files matched your search

@@ -15,11 +15,12 @@ const toast = useToastStore()
interface CategoryRow {
key: number
name: string
multiplier: string
// type="number" 的 v-model 会被 Vue 自动转成 number(空值仍是 ''),这里如实标注
multiplier: string | number
gender: '' | Gender
ageMin: string
ageMax: string
sortOrder: string
ageMin: string | number
ageMax: string | number
sortOrder: string | number
}
interface RowError {
@@ -97,6 +98,20 @@ function moveRow(index: number, delta: number): void {
rows.value = next
}
/**
* 年龄取值。
*
* `type="number"` 的 v-model 会被 Vue 自动转成 number(空值仍是 ''),所以不能直接 `.trim()`:
* 填了数字后字段是 number,`.trim()` 抛 TypeError,点击处理器中断,表现为「点了没反应」。
* 非法输入返回 NaN,交给 validate 标红。
*/
function toAge(value: string | number): number | null {
const text = String(value).trim()
if (text === '') return null
const parsed = Number(text)
return Number.isFinite(parsed) ? parsed : Number.NaN
}
function validate(): boolean {
const next: Record<number, RowError> = {}
const seen = new Set<string>()
@@ -112,8 +127,8 @@ function validate(): boolean {
rowError.multiplier = '权重为 0–100 的正数'
}
const ageMin = row.ageMin.trim() === '' ? null : Number(row.ageMin)
const ageMax = row.ageMax.trim() === '' ? null : Number(row.ageMax)
const ageMin = toAge(row.ageMin)
const ageMax = toAge(row.ageMax)
if (ageMin !== null && (!Number.isInteger(ageMin) || ageMin < 0 || ageMin > 120)) rowError.age = '年龄为 0–120 的整数'
else if (ageMax !== null && (!Number.isInteger(ageMax) || ageMax < 0 || ageMax > 120)) rowError.age = '年龄为 0–120 的整数'
else if (ageMin !== null && ageMax !== null && ageMin > ageMax) rowError.age = '起始年龄不能大于结束年龄'
@@ -129,8 +144,8 @@ function buildInputs(): EventCategoryInput[] {
name: row.name.trim(),
multiplier: Number(row.multiplier),
gender: row.gender === '' ? null : row.gender,
ageMin: row.ageMin.trim() === '' ? null : Number(row.ageMin),
ageMax: row.ageMax.trim() === '' ? null : Number(row.ageMax),
ageMin: toAge(row.ageMin),
ageMax: toAge(row.ageMax),
sortOrder: Number(row.sortOrder) || index + 1,
}))
}
+1 -1
View File
@@ -181,7 +181,7 @@ function openCreate(): void {
function validate(): string | null {
if (formTeamId.value === null) return '请选择单位'
const delta = Number(formDelta.value)
if (formDelta.value.trim() === '' || !Number.isFinite(delta)) return '请填写分值'
if (String(formDelta.value).trim() === '' || !Number.isFinite(delta)) return '请填写分值'
if (delta === 0) return '分值不能为 0'
if (formReason.value.trim() === '') return '理由必填'
return null