feat(display): 分项成绩与个人榜也显示单位 logo(个人榜在姓名前)

- 服务端补下发:dto.EventResultRow 与 athleteStandingDTO 各加 logoPath,
  分别取「该行单位」与「该运动员所属单位」的 teams.logo_path;空串统一收敛为 null
- 大屏抽出 TeamLogo.vue(1em / object-fit:contain / 加载失败保留占位、@load 复原),
  原先在积分榜与团体奖牌榜各写一遍的逻辑收敛成一份,四处复用
- 分项成绩页:单位列名字前显示 logo;个人积分榜:运动员姓名前显示其单位 logo
- guards/mock 同步解析与补字段;补 11 条测试(类型解析 + SFC 模板断言)

顺带修:管理端示例数据 athleteStandings 缺新必填字段导致的 vue-tsc 报错。

自检:服务端 11 包全绿;前端 vue-tsc + build、node --test 80/80。
This commit is contained in:
AskaEth committed 2026-09-10 10:32:19 +08:00
1 parent 6f47849e1e
commit 8c74a82da4
17 files changed
+391 -51

No files matched your search

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue'
import { resolveAssetUrl } from '@/shared'
import { BOARD_AVAILABLE_PX, fitRowFont } from '../../fitFont'
import { useScoreboard } from '../../useScoreboard'
import PageFrame from '../PageFrame.vue'
import TeamLogo from '../TeamLogo.vue'
const { standings } = useScoreboard()
@@ -11,12 +11,6 @@ const { standings } = useScoreboard()
const rows = computed(() => standings.value)
const rowFont = computed(() => fitRowFont(rows.value.length, BOARD_AVAILABLE_PX))
// 单位 logo 加载失败时不显示破图:失败藏起来、重新加载出图后再放出来,
// 两种情况下都保留占位,行内文字与行高都不会跳动。
function setLogoVisible(event: Event, visible: boolean): void {
;(event.target as HTMLImageElement).style.visibility = visible ? '' : 'hidden'
}
</script>
<template>
@@ -33,14 +27,7 @@ function setLogoVisible(event: Event, visible: boolean): void {
<div v-for="row in rows" :key="row.teamId" class="row">
<span class="rank" :class="`top-${Math.min(row.rank, 4)}`">{{ row.rank }}</span>
<span class="name">
<img
v-if="row.logoPath"
class="team-logo"
:src="resolveAssetUrl(row.logoPath)"
alt=""
@error="setLogoVisible($event, false)"
@load="setLogoVisible($event, true)"
/>
<TeamLogo :path="row.logoPath" />
<span class="team-name">{{ row.teamName }}</span>
</span>
<span class="medal gold">{{ row.gold }}</span>
@@ -108,13 +95,6 @@ function setLogoVisible(event: Event, visible: boolean): void {
min-width: 0;
overflow: hidden;
}
/* logo 尺寸用 em 跟着行字号走:既不会撑开行高,也不会挤动名次/奖牌列。 */
.team-logo {
flex: none;
width: 1em;
height: 1em;
object-fit: contain;
}
.team-name {
min-width: 0;
overflow: hidden;