Files
OJ2/apps/web/src/shared/components/RankAvatar.vue
T
xuyueandClaude Opus 5.5 e511d82f3b perf(暗色): 提交列表、排名首屏卡掉的一秒多回来了 —— 全站共用一个 useDark
VueUse 的 useDark 每个实例挂载时都插一段 style、getComputedStyle 逼整页重算样式,
亮色下它的「没东西要改就跳过」永远不成立。提交列表每行的结果胶囊(useTone)都调它,
一页 30 行就是 30 次整页重算,翻页、换条再来。

改成 shared/composables/dark.ts 的 createGlobalState,auto-import 用 priority 盖掉
@vueuse/core 的同名项,显式 import 的 7 处一并换掉。

生产构建、CPU 降速 6 倍实测:提交列表首屏主线程 2015 → 1092ms(列表出来 1.34 → 0.85 秒),
排名 1950 → 850ms,比赛 1060 → 757ms;右栏换到没看过的一条不再每次卡 100ms。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:55:33 +08:00

73 lines
2.0 KiB
Vue

<script setup lang="ts">
import { useDark } from 'shared/composables/dark'
import { rgba } from 'shared/composables/tone'
import { displayName } from '@oj2/contract'
/**
* 排名页的头像。九成以上的学生没换过头像(1797 个号是 default.png),一排一模一样的
* 默认图分不出人,所以默认头像画成「名字最后一个字 + 彩色圆」,颜色按用户名固定 ——
* 同一个人在领奖台、赛道、「你」那张卡里是同一个颜色。自己是实心绿。
*/
const props = defineProps<{
username: string
avatar: string | null
size: number
me?: boolean
}>()
const isDark = useDark()
/** 头像文件丢了(备份恢复后没拷上传目录、被删)就退回名字那个字,别露出破图 */
const broken = ref(false)
watch(
() => props.avatar,
() => (broken.value = false),
)
const PALETTE = ['#2f6fd0', '#c76a12', '#6a4bd8', '#0f7a6c', '#b4315f', '#4d5967', '#8a6a00']
const letter = computed(() => {
const name = displayName(props.username)
return Array.from(name).at(-1) ?? '?'
})
const colors = computed(() => {
if (props.me) return { background: '#18a058', color: '#ffffff' }
let hash = 0
for (const char of props.username) hash = (hash * 31 + char.codePointAt(0)!) >>> 0
const base = PALETTE[hash % PALETTE.length]!
return { background: rgba(base, isDark.value ? 0.28 : 0.13), color: base }
})
</script>
<template>
<img v-if="avatar && !broken" class="avatar" :src="avatar" alt="" @error="broken = true" :style="{ width: `${size}px`, height: `${size}px` }" />
<span
v-else
class="avatar letter"
aria-hidden="true"
:style="{
width: `${size}px`,
height: `${size}px`,
fontSize: `${Math.round(size * 0.5)}px`,
...colors,
}"
>{{ letter }}</span
>
</template>
<style scoped>
.avatar {
border-radius: 50%;
flex-shrink: 0;
object-fit: cover;
}
.letter {
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
line-height: 1;
}
</style>