update
Deploy / deploy (push) Canceled after 0s

This commit is contained in:
xuyue committed 2026-09-27 19:36:28 +08:00
1 parent 23844ae301
commit d25b5dbe6d
268 files changed
+3684 -8861

No files matched your search

+1 -5
View File
@@ -1,9 +1,5 @@
import api from "utils/api"
import type {
AchievementList,
AchievementSummary,
PendingAchievement,
} from "utils/types"
import type { AchievementList, AchievementSummary, PendingAchievement } from "utils/types"
export function getAchievements(name?: string) {
return api.get<AchievementList>("achievements", {
@@ -12,14 +12,10 @@ const rarityTextColor = useRarityColor()
// 隐藏且未解锁:后端已把名称/描述/图标和条件三件套都遮成 ??? 和 null,
// 这里只负责不要把 null 渲染出来,也不要画出会泄露门槛的进度条
const masked = computed(
() => props.achievement.hidden && !props.achievement.unlocked,
)
const masked = computed(() => props.achievement.hidden && !props.achievement.unlocked)
// 获得率低于 5% 的加稀有闪光边框
const isRare = computed(
() => props.achievement.unlockRate > 0 && props.achievement.unlockRate < 5,
)
const isRare = computed(() => props.achievement.unlockRate > 0 && props.achievement.unlockRate < 5)
// 只有"越多越好"的成就画进度条。lte 类(如最短 AC 代码 ≤ 50 字符)
// 画成百分比毫无意义,改成直接显示当前最好成绩
@@ -88,9 +84,7 @@ const unlockDate = computed(() => {
<n-flex align="center" :size="8" :wrap="false">
<template v-if="achievement.unlocked">
<n-text depth="3" class="nowrap">{{ unlockDate }}</n-text>
<n-text depth="3" class="nowrap">
仅 {{ achievement.unlockRate }}% 的人获得
</n-text>
<n-text depth="3" class="nowrap"> 仅 {{ achievement.unlockRate }}% 的人获得 </n-text>
</template>
<template v-else-if="showProgressBar">
@@ -107,17 +101,13 @@ const unlockDate = computed(() => {
</template>
<template v-else-if="showBestSoFar">
<n-text depth="3" class="nowrap">
目标 ≤ {{ achievement.threshold }}
</n-text>
<n-text depth="3" class="nowrap"> 目标 ≤ {{ achievement.threshold }} </n-text>
<n-text v-if="achievement.progress !== null" depth="3" class="nowrap">
当前最好 {{ achievement.progress }}
</n-text>
</template>
<n-text v-else depth="3" class="nowrap">
仅 {{ achievement.unlockRate }}% 的人获得
</n-text>
<n-text v-else depth="3" class="nowrap"> 仅 {{ achievement.unlockRate }}% 的人获得 </n-text>
</n-flex>
</n-thing>
</n-card>
+10 -46
View File
@@ -3,12 +3,7 @@ import { getAchievements, getAchievementSummary } from "oj/achievement/api"
import { getUserBadges } from "oj/api"
import { useBreakpoints } from "shared/composables/breakpoints"
import { useRarityColor } from "shared/composables/rarity"
import type {
Achievement,
AchievementRarity,
AchievementSummary,
UserBadge,
} from "utils/types"
import type { Achievement, AchievementRarity, AchievementSummary, UserBadge } from "utils/types"
import AchievementCard from "./components/AchievementCard.vue"
const route = useRoute()
@@ -29,9 +24,7 @@ const RARITY_RANK: Record<AchievementRarity, number> = {
}
const rarities = computed(() =>
[...(summary.value?.rarity ?? [])].sort(
(a, b) => RARITY_RANK[a.rarity] - RARITY_RANK[b.rarity],
),
[...(summary.value?.rarity ?? [])].sort((a, b) => RARITY_RANK[a.rarity] - RARITY_RANK[b.rarity]),
)
const badges = ref<UserBadge[]>([])
const { isDesktop } = useBreakpoints()
@@ -39,10 +32,8 @@ const tab = ref("all")
const loading = ref(true)
const filtered = computed(() => {
if (tab.value === "unlocked")
return achievements.value.filter((a) => a.unlocked)
if (tab.value === "locked")
return achievements.value.filter((a) => !a.unlocked)
if (tab.value === "unlocked") return achievements.value.filter((a) => a.unlocked)
if (tab.value === "locked") return achievements.value.filter((a) => !a.unlocked)
return achievements.value
})
@@ -73,11 +64,7 @@ watch(name, load)
<n-card v-if="summary">
<n-flex align="center" :wrap="false" :size="isDesktop ? 32 : 16">
<n-flex vertical align="center" :size="6">
<n-progress
type="circle"
:percentage="summary.percent"
:stroke-width="8"
>
<n-progress type="circle" :percentage="summary.percent" :stroke-width="8">
<n-text strong :style="{ fontSize: isDesktop ? '20px' : '14px' }">
{{ summary.percent }}%
</n-text>
@@ -88,13 +75,7 @@ watch(name, load)
</n-flex>
<n-flex vertical :size="8" class="rarity">
<n-flex
v-for="r in rarities"
:key="r.rarity"
align="center"
:wrap="false"
:size="10"
>
<n-flex v-for="r in rarities" :key="r.rarity" align="center" :wrap="false" :size="10">
<n-text strong :style="{ color: rarityColor[r.rarity] }">
{{ r.label }}
</n-text>
@@ -108,9 +89,7 @@ watch(name, load)
:color="rarityColor[r.rarity]"
:show-indicator="false"
/>
<n-text depth="3" class="nowrap">
{{ r.unlocked }} / {{ r.total }}
</n-text>
<n-text depth="3" class="nowrap"> {{ r.unlocked }} / {{ r.total }} </n-text>
</n-flex>
</n-flex>
</n-flex>
@@ -124,13 +103,7 @@ watch(name, load)
</n-tabs>
<template v-if="tab !== 'badges'">
<n-grid
v-if="filtered.length"
responsive="screen"
cols="1 s:2 l:3"
:x-gap="12"
:y-gap="12"
>
<n-grid v-if="filtered.length" responsive="screen" cols="1 s:2 l:3" :x-gap="12" :y-gap="12">
<n-gi v-for="a in filtered" :key="a.id">
<AchievementCard :achievement="a" />
</n-gi>
@@ -140,19 +113,10 @@ watch(name, load)
</template>
<template v-else>
<n-grid
v-if="badges.length"
responsive="screen"
cols="1 s:2 l:3"
:x-gap="12"
:y-gap="12"
>
<n-grid v-if="badges.length" responsive="screen" cols="1 s:2 l:3" :x-gap="12" :y-gap="12">
<n-gi v-for="b in badges" :key="b.id">
<n-card size="small">
<n-thing
:title="b.badge?.name"
:description="b.badge?.description"
>
<n-thing :title="b.badge?.name" :description="b.badge?.description">
<template #avatar v-if="b.badge?.icon">
<n-avatar
:size="40"