feat(成就): 成就页改成「快拿到了 + 本班」加一条条成就线,不再是一墙灰卡
Deploy / deploy (push) Canceled after 0s

- 顶上「快拿到了」三个只说还差什么,旁边「本班」:最近谁拿到了什么、成就最多前五(自己不在就钉在底下)
- 38 个成就按指标收成一条条线、分五组,同一串几档排在一根进度线上,下一档标「差几」;
  悬停看说明、哪天拿到、本班几人有、全站几人有
- 还没找到的隐藏成就只露问号;本班动态里也只说「彩蛋」,不漏名字
- 题单奖章并进「题单和比赛」那组;看别人的成就只有成就线
- 去掉「仅 X% 的人获得」(分母是全站历届账号)和完成度圆环

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-13 00:26:37 +08:00
1 parent 85108df9d8
commit cbeb0737ef
9 files changed
+1203 -145

No files matched your search

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { useThemeVars } from "naive-ui"
import AchievementIcon from "shared/components/AchievementIcon.vue"
import type { UserBadge } from "utils/types"
import type { Group } from "../series"
import Medal from "./Medal.vue"
import SeriesRow from "./SeriesRow.vue"
/** 一组成就线(做题 / 坚持 / …),「彩蛋」组末尾是还没找到的问号,「题单和比赛」组末尾是拿到的题单奖章 */
const props = defineProps<{ group: Group; badges?: UserBadge[]; compact?: boolean }>()
const theme = useThemeVars()
const showBadges = computed(() => props.group.name === "题单和比赛" && !!props.badges?.length)
</script>
<template>
<section class="card" :class="{ compact }">
<h3>
{{ group.name }}
<small>{{ group.got }} / {{ group.total }}</small>
</h3>
<SeriesRow v-for="item in group.series" :key="item.metric" :series="item" :compact="compact" />
<div v-if="group.masked" class="masked">
<span class="masked-title">还有 {{ group.masked }} 个没找到</span>
<div class="masked-list">
<Medal v-for="i in group.masked" :key="i" state="unknown" :size="compact ? 30 : 34" />
</div>
</div>
<div v-if="showBadges" class="badges">
<span class="masked-title">拿到的题单奖章</span>
<div class="badge-list">
<n-tooltip v-for="b in badges" :key="b.id" :delay="150">
<template #trigger>
<router-link
:to="{ name: 'problemset', params: { problemSetId: b.problemSet.id } }"
class="badge"
>
<AchievementIcon :icon="b.badge.icon" :size="32" />
<span>{{ b.badge.name }}</span>
</router-link>
</template>
{{ b.badge.description }} · 来自题单「{{ b.problemSet.title }}」
</n-tooltip>
</div>
</div>
</section>
</template>
<style scoped>
.card {
padding: 14px 20px 20px;
border: 1px solid v-bind("theme.borderColor");
border-radius: var(--oj-radius);
background: v-bind("theme.cardColor");
}
h3 {
margin: 0;
display: flex;
align-items: baseline;
gap: 8px;
font-size: var(--oj-fs-h2);
}
h3 small {
font-size: var(--oj-fs-meta);
font-weight: 400;
color: v-bind("theme.textColor3");
font-variant-numeric: tabular-nums;
}
.masked,
.badges {
margin-top: 22px;
display: flex;
flex-direction: column;
gap: 8px;
}
.masked-title {
font-size: 13px;
color: v-bind("theme.textColor2");
}
.masked-list,
.badge-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.badge {
width: 72px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
font-size: 12px;
line-height: 15px;
text-align: center;
color: v-bind("theme.textColor1");
text-decoration: none;
}
.badge span {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.compact {
padding: 12px 14px 16px;
}
</style>