diff --git a/VERSION b/VERSION index a527171..3380654 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -dev_0.1.8 +dev_0.1.9 diff --git a/console/package.json b/console/package.json index 29b92cc..cd0c517 100644 --- a/console/package.json +++ b/console/package.json @@ -1,7 +1,7 @@ { "name": "scoreboard-console", "productName": "Scoreboard Console", - "version": "0.1.8", + "version": "0.1.9", "private": true, "author": "AskaEth", "description": "赛事积分榜 · 桌面宿主:守护 Go 服务端并把大屏投到扩展屏", diff --git a/web/src/admin/components/baseData/AthletesSection.vue b/web/src/admin/components/baseData/AthletesSection.vue index 794f19a..4ff671a 100644 --- a/web/src/admin/components/baseData/AthletesSection.vue +++ b/web/src/admin/components/baseData/AthletesSection.vue @@ -70,6 +70,28 @@ const batchPurgeOpen = ref(false) /** 彻底删除的预览(dryRun 响应):提交前把「会删掉什么、重算哪些小项」摆给操作员看。 */ const bulkPurgePreview = ref(null) +// filtered 必须声明在 filteredIds / watch 之前: +// watch 注册时会同步求值一次源(Vue 的行为),若 filtered 还在 TDZ 里, +// setup 会直接抛 "Cannot access 'filtered' before initialization", +// 整个分区渲染不出来(页面只剩标签栏、下面全空白)。2026-09-11 实机复现。 +const filtered = computed(() => { + const keyword = search.value.trim().toLowerCase() + return data.athletes.value.filter((athlete) => { + if (teamFilter.value !== 'all' && String(athlete.teamId) !== teamFilter.value) return false + if (statusFilter.value === 'active' && !athlete.active) return false + if (statusFilter.value === 'inactive' && athlete.active) return false + if (keyword === '') return true + const team = data.teamById(athlete.teamId) + return ( + athlete.name.toLowerCase().includes(keyword) || + (athlete.employeeNo ?? '').toLowerCase().includes(keyword) || + (athlete.idCard ?? '').toLowerCase().includes(keyword) || + (athlete.bib ?? '').toLowerCase().includes(keyword) || + (team?.name ?? '').toLowerCase().includes(keyword) + ) + }) +}) + const filteredIds = computed(() => filtered.value.map((athlete) => athlete.id)) const selectedCount = computed(() => selectedIds.value.size) const selectedAthletes = computed(() => data.athletes.value.filter((athlete) => selectedIds.value.has(athlete.id))) @@ -269,24 +291,6 @@ function toAge(value: string | number): number | null { return Number.isFinite(parsed) ? parsed : Number.NaN } -const filtered = computed(() => { - const keyword = search.value.trim().toLowerCase() - return data.athletes.value.filter((athlete) => { - if (teamFilter.value !== 'all' && String(athlete.teamId) !== teamFilter.value) return false - if (statusFilter.value === 'active' && !athlete.active) return false - if (statusFilter.value === 'inactive' && athlete.active) return false - if (keyword === '') return true - const team = data.teamById(athlete.teamId) - return ( - athlete.name.toLowerCase().includes(keyword) || - (athlete.employeeNo ?? '').toLowerCase().includes(keyword) || - (athlete.idCard ?? '').toLowerCase().includes(keyword) || - (athlete.bib ?? '').toLowerCase().includes(keyword) || - (team?.name ?? '').toLowerCase().includes(keyword) - ) - }) -}) - const activeCount = computed(() => data.athletes.value.filter((athlete) => athlete.active).length) function openCreate(): void {