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:
1 parent
6f47849e1e
commit
8c74a82da4
17 files changed
+391
-51
No files matched your search
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { resolveAssetUrl } from '@/shared'
|
||||
|
||||
defineProps<{ path: string | null | undefined }>()
|
||||
|
||||
// 加载失败时不显示破图:藏起来但保留占位,重新加载出图后再放出来,
|
||||
// 两种情况行内的名字与行高都不会跳动。
|
||||
function setVisible(event: Event, visible: boolean): void {
|
||||
;(event.target as HTMLImageElement).style.visibility = visible ? '' : 'hidden'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
v-if="path"
|
||||
class="team-logo"
|
||||
:src="resolveAssetUrl(path)"
|
||||
alt=""
|
||||
@error="setVisible($event, false)"
|
||||
@load="setVisible($event, true)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 尺寸用 em 跟着所在行字号浮动:既不会撑开行高,也不会挤动名次/分数列。 */
|
||||
.team-logo {
|
||||
flex: none;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@ import { BOARD_AVAILABLE_PX, fitRowFont } from '../../fitFont'
|
||||
import { useScoreboard } from '../../useScoreboard'
|
||||
import AnimatedNumber from '../AnimatedNumber.vue'
|
||||
import PageFrame from '../PageFrame.vue'
|
||||
import TeamLogo from '../TeamLogo.vue'
|
||||
|
||||
const { athletes, settings } = useScoreboard()
|
||||
|
||||
@@ -36,7 +37,10 @@ const rowFont = computed(() =>
|
||||
</div>
|
||||
<div v-for="row in visibleRows" :key="row.athleteId" class="row">
|
||||
<span class="rank" :class="`top-${Math.min(row.rank, 4)}`">{{ row.rank }}</span>
|
||||
<span class="name">{{ row.athleteName }}</span>
|
||||
<span class="name">
|
||||
<TeamLogo :path="row.logoPath" />
|
||||
<span class="athlete-name">{{ row.athleteName }}</span>
|
||||
</span>
|
||||
<span class="team">{{ row.shortName ?? row.teamName }}</span>
|
||||
<span class="points"><AnimatedNumber :value="row.totalPoints" /></span>
|
||||
<template v-if="showMedals">
|
||||
@@ -102,14 +106,24 @@ const rowFont = computed(() =>
|
||||
.top-3 {
|
||||
color: #e0995e;
|
||||
}
|
||||
.name,
|
||||
.team {
|
||||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.28em;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.athlete-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.team {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.82em;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { formatResultText } from '@/shared'
|
||||
import type { EventResultRow, ResultStatus } from '@/shared'
|
||||
import { useScoreboard } from '../../useScoreboard'
|
||||
import PageFrame from '../PageFrame.vue'
|
||||
import TeamLogo from '../TeamLogo.vue'
|
||||
|
||||
const { latestResults, options } = useScoreboard()
|
||||
|
||||
@@ -48,7 +49,10 @@ function displayName(row: EventResultRow): string {
|
||||
>
|
||||
<span class="rk">{{ row.rank ?? '—' }}</span>
|
||||
<span class="nm">{{ displayName(row) }}</span>
|
||||
<span class="team">{{ row.shortName ?? row.teamName }}</span>
|
||||
<span class="team">
|
||||
<TeamLogo :path="row.logoPath" />
|
||||
<span class="team-name">{{ row.shortName ?? row.teamName }}</span>
|
||||
</span>
|
||||
<span class="rs">{{ displayResult(row) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,8 +132,16 @@ h3 {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.team {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.28em;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
font-size: 22px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.team-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { formatRankChange, resolveAssetUrl } from '@/shared'
|
||||
import { formatRankChange } from '@/shared'
|
||||
import { BOARD_AVAILABLE_PX, fitRowFont } from '../../fitFont'
|
||||
import { useScoreboard } from '../../useScoreboard'
|
||||
import AnimatedNumber from '../AnimatedNumber.vue'
|
||||
import PageFrame from '../PageFrame.vue'
|
||||
import TeamLogo from '../TeamLogo.vue'
|
||||
|
||||
const { standings, rankDeltaFor } = useScoreboard()
|
||||
|
||||
@@ -12,12 +13,6 @@ const rows = computed(() => standings.value)
|
||||
|
||||
// 行高由 flex 均分,字号按行数算,单位再多也不会顶到轮播条下面。
|
||||
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>
|
||||
@@ -37,14 +32,7 @@ function setLogoVisible(event: Event, visible: boolean): void {
|
||||
>
|
||||
<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="delta">
|
||||
@@ -112,13 +100,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user