- 圆环改用默认插槽渲染百分比,字号跟断点走(naive 自带指示器字号固定,76px 圆环放不下) - 卡片边框 1px → 2px - 置灰从整张卡挪到内容上,未获得的卡片不再把稀有度边框一起洗成灰 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
4.3 KiB
Vue
140 lines
4.3 KiB
Vue
<script setup lang="ts">
|
||
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
||
import { useRarityColor } from "shared/composables/rarity"
|
||
import { RARITY_COLOR, RARITY_LABEL } from "utils/constants"
|
||
import type { Achievement } from "utils/types"
|
||
|
||
const props = defineProps<{ achievement: Achievement }>()
|
||
|
||
// 边框用原色,tag 里的文字用跟主题走的那套
|
||
const rarityTextColor = useRarityColor()
|
||
|
||
// 隐藏且未解锁:后端已把名称/描述/图标和条件三件套都遮成 ??? 和 null,
|
||
// 这里只负责不要把 null 渲染出来,也不要画出会泄露门槛的进度条
|
||
const masked = computed(
|
||
() => props.achievement.hidden && !props.achievement.unlocked,
|
||
)
|
||
|
||
// 获得率低于 5% 的加稀有闪光边框
|
||
const isRare = computed(
|
||
() => props.achievement.unlock_rate > 0 && props.achievement.unlock_rate < 5,
|
||
)
|
||
|
||
// 只有"越多越好"的成就画进度条。lte 类(如最短 AC 代码 ≤ 50 字符)
|
||
// 画成百分比毫无意义,改成直接显示当前最好成绩
|
||
const showProgressBar = computed(
|
||
() =>
|
||
!masked.value &&
|
||
!props.achievement.unlocked &&
|
||
props.achievement.operator === "gte" &&
|
||
props.achievement.threshold !== null,
|
||
)
|
||
|
||
const showBestSoFar = computed(
|
||
() =>
|
||
!masked.value &&
|
||
!props.achievement.unlocked &&
|
||
props.achievement.operator === "lte" &&
|
||
props.achievement.threshold !== null,
|
||
)
|
||
|
||
const percent = computed(() => {
|
||
const { progress, threshold } = props.achievement
|
||
if (threshold === null || threshold <= 0) return 100
|
||
return Math.min(100, Math.round(((progress ?? 0) / threshold) * 100))
|
||
})
|
||
|
||
const unlockDate = computed(() => {
|
||
const { unlock_time, backfilled } = props.achievement
|
||
// 补发的记录不显示具体日期:一次补发会给几百人盖上同一个时间戳
|
||
if (backfilled || !unlock_time) return "已获得"
|
||
return `${new Date(unlock_time).toLocaleDateString()} 获得`
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<n-card
|
||
size="small"
|
||
:class="{ rare: isRare }"
|
||
:style="{
|
||
borderColor: RARITY_COLOR[achievement.rarity],
|
||
// 1px 的描边在卡片背景上几乎看不出稀有度,加粗一档
|
||
borderWidth: '2px',
|
||
}"
|
||
>
|
||
<!-- 置灰只作用在内容上:套在整张卡上会把稀有度边框也一并洗成灰色 -->
|
||
<n-thing :class="{ locked: !achievement.unlocked }">
|
||
<template #avatar>
|
||
<AchievementIcon :icon="achievement.icon" :size="32" />
|
||
</template>
|
||
|
||
<template #header>
|
||
<n-flex align="center" :size="8">
|
||
<n-text strong>{{ achievement.name }}</n-text>
|
||
<n-tag
|
||
size="tiny"
|
||
:color="{
|
||
borderColor: RARITY_COLOR[achievement.rarity],
|
||
textColor: rarityTextColor[achievement.rarity],
|
||
}"
|
||
>
|
||
{{ RARITY_LABEL[achievement.rarity] }}
|
||
</n-tag>
|
||
</n-flex>
|
||
</template>
|
||
|
||
<template #description>
|
||
<n-text depth="3">{{ achievement.description }}</n-text>
|
||
</template>
|
||
|
||
<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.unlock_rate }}% 的人获得
|
||
</n-text>
|
||
</template>
|
||
|
||
<template v-else-if="showProgressBar">
|
||
<n-progress
|
||
style="flex: 1"
|
||
type="line"
|
||
:percentage="percent"
|
||
:height="6"
|
||
:show-indicator="false"
|
||
/>
|
||
<n-text depth="3" class="nowrap">
|
||
{{ achievement.progress ?? 0 }} / {{ achievement.threshold }}
|
||
</n-text>
|
||
</template>
|
||
|
||
<template v-else-if="showBestSoFar">
|
||
<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.unlock_rate }}% 的人获得
|
||
</n-text>
|
||
</n-flex>
|
||
</n-thing>
|
||
</n-card>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.nowrap {
|
||
white-space: nowrap;
|
||
}
|
||
.locked {
|
||
filter: grayscale(1);
|
||
opacity: 0.55;
|
||
}
|
||
.rare {
|
||
box-shadow: 0 0 12px rgba(125, 211, 252, 0.55);
|
||
}
|
||
</style>
|