pet_data/app/components/pixel/AchievementsOverlay.vue

135 lines
5.2 KiB
Vue
Raw Normal View History

2025-11-26 06:53:44 +00:00
<template>
<div class="flex flex-col gap-4 h-full">
2025-11-26 15:31:46 +00:00
<!-- Stats Bar -->
<div class="flex justify-between items-center bg-[#231533] p-2 border-2 border-[#4a3b5e] text-xs font-mono">
2025-11-26 06:53:44 +00:00
<div class="flex gap-4">
2025-11-26 15:31:46 +00:00
<span class="text-[#e0d8f0]">總成就: <span class="text-[#f6b26b]">{{ total }}</span></span>
<span class="text-[#e0d8f0]">已解鎖: <span class="text-[#99e550]">{{ unlocked }}</span></span>
2025-11-26 06:53:44 +00:00
</div>
2025-11-26 15:31:46 +00:00
<div class="flex items-center gap-2">
<span class="text-[#e0d8f0]">達成率: <span class="text-[#2ce8f4]">{{ percentage }}%</span></span>
<div class="w-20">
<RetroProgressBar :progress="percentage" color="#2ce8f4" height="6px" />
</div>
2025-11-26 06:53:44 +00:00
</div>
</div>
<!-- Grid Layout for Achievements -->
2025-11-26 15:31:46 +00:00
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 overflow-y-auto pb-4 custom-scrollbar">
2025-11-26 06:53:44 +00:00
<div
v-for="achievement in achievements"
:key="achievement.id"
class="relative border-2 p-3 flex flex-col gap-2 min-h-[140px] transition-all group hover:bg-[#231533]"
:style="{ borderColor: achievement.unlocked ? '#99e550' : '#4a3b5e', backgroundColor: achievement.unlocked ? '#150c1f' : '#0f0816' }"
>
<!-- Header: Icon + Title -->
<div class="flex items-start gap-3">
2025-11-26 15:31:46 +00:00
<div class="p-2 rounded-sm border-2 flex items-center justify-center w-10 h-10" :class="achievement.unlocked ? 'border-[#99e550] bg-[#4b692f]/20' : 'border-[#4a3b5e] bg-[#2b193f]'">
<component :is="getIcon(achievement.icon)" :size="20" :color="achievement.unlocked ? '#99e550' : '#8f80a0'" />
2025-11-26 06:53:44 +00:00
</div>
<div class="flex flex-col">
<h4 class="font-bold tracking-wide leading-none mb-1" :class="achievement.unlocked ? 'text-[#2ce8f4]' : 'text-[#8f80a0]'">
2025-11-26 15:31:46 +00:00
{{ achievement.name }}
2025-11-26 06:53:44 +00:00
</h4>
2025-11-26 15:31:46 +00:00
<span v-if="achievement.unlocked" class="text-[10px] text-[#99e550] uppercase tracking-widest">已完成</span>
2025-11-26 06:53:44 +00:00
<span v-else class="text-[10px] text-[#8f80a0] uppercase tracking-widest flex items-center gap-1">
2025-11-26 15:31:46 +00:00
<Lock :size="10" /> 未解鎖
2025-11-26 06:53:44 +00:00
</span>
</div>
</div>
<!-- Description -->
2025-11-26 15:31:46 +00:00
<p class="text-xs text-[#e0d8f0] flex-grow leading-tight mt-1">
2025-11-26 06:53:44 +00:00
{{ achievement.description }}
</p>
<!-- Reward Section (if exists) -->
2025-11-26 15:31:46 +00:00
<div v-if="achievement.reward && achievement.reward.buffs" class="text-[10px] text-[#99e550] mt-2 border-t border-[#4a3b5e] pt-1">
<span class="text-[#99e550] opacity-70">獎勵: </span>
<div class="flex flex-wrap gap-1 mt-0.5">
<span v-for="(value, key) in achievement.reward.buffs" :key="key" class="bg-[#2b193f] px-1 rounded border border-[#4a3b5e]">
{{ formatBuffKey(key) }}+{{ formatValue(value) }}
</span>
</div>
2025-11-26 06:53:44 +00:00
</div>
<!-- Progress Bar (if incomplete) -->
2025-11-26 15:31:46 +00:00
<div v-if="!achievement.unlocked && achievement.maxValue" class="mt-auto pt-2">
2025-11-26 06:53:44 +00:00
<div class="flex justify-between text-[9px] text-[#8f80a0] mb-0.5">
<span>{{ achievement.currentValue }} / {{ achievement.maxValue }}</span>
<span>{{ achievement.progress }}%</span>
</div>
<div class="h-1 bg-[#2b193f] w-full">
<div class="h-full bg-[#4a3b5e]" :style="{ width: `${achievement.progress}%` }" />
</div>
</div>
<!-- Decorative corner if unlocked -->
<div v-if="achievement.unlocked" class="absolute top-0 right-0 w-4 h-4 overflow-hidden">
<div class="absolute top-0 right-0 w-2 h-2 bg-[#99e550]" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
2025-11-26 15:31:46 +00:00
import {
CheckCircle2, Lock, Trophy, Baby, CalendarDays, Egg, Sprout, Cake, Star, Diamond,
Milk, Utensils, Gamepad2, Sparkles, BookOpen, Search, Leaf, Dumbbell, Brush, Pill,
HandHeart, Heart, Moon
} from 'lucide-vue-next';
2025-11-26 09:53:03 +00:00
import RetroProgressBar from './RetroProgressBar.vue';
2025-11-26 15:31:46 +00:00
import { formatBuffKey } from '../../utils/formatters.js';
2025-11-26 09:53:03 +00:00
type Achievement = any;
2025-11-26 06:53:44 +00:00
interface Props {
achievements: Achievement[];
}
const props = defineProps<Props>();
const ICON_MAP: Record<string, any> = {
2025-11-26 15:31:46 +00:00
'👶': Baby,
'📅': CalendarDays,
'🥚': Egg,
'🌱': Sprout,
'🎂': Cake,
'⭐': Star,
'💎': Diamond,
'🍼': Milk,
'🍽️': Utensils,
'🎮': Gamepad2,
'🧹': Brush,
'💊': Pill,
'🙏': HandHeart,
'🕯️': Sparkles,
'👼': Heart,
'🌟': Star,
'🎴': BookOpen,
'🔍': Search,
'🍀': Leaf,
'💪': Dumbbell,
'✨': Sparkles,
'🌙': Moon
};
const getIcon = (iconKey: string) => {
return ICON_MAP[iconKey] || Trophy;
};
const formatValue = (value: number) => {
if (value < 1 && value > 0) {
return Math.round(value * 100) + '%';
}
return value;
2025-11-26 06:53:44 +00:00
};
const total = computed(() => props.achievements.length);
const unlocked = computed(() => props.achievements.filter(a => a.unlocked).length);
2025-11-26 15:31:46 +00:00
const percentage = computed(() => total.value > 0 ? Math.round((unlocked.value / total.value) * 100) : 0);
2025-11-26 06:53:44 +00:00
</script>