2025-11-26 06:53:44 +00:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="flex flex-col h-full gap-4">
|
|
|
|
|
|
<!-- Header: Deity System Title -->
|
|
|
|
|
|
<div class="text-xl font-bold tracking-widest text-[#2ce8f4] border-b-2 border-[#4a3b5e] pb-2">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
神明系統
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Top Action Buttons Grid -->
|
|
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
|
|
|
|
|
<PixelButton
|
|
|
|
|
|
v-for="action in TAB_ACTIONS"
|
|
|
|
|
|
:key="action.id"
|
|
|
|
|
|
@click="activeTab = action.id"
|
|
|
|
|
|
:variant="activeTab === action.id ? 'primary' : 'secondary'"
|
|
|
|
|
|
class="text-xs md:text-sm flex items-center justify-center gap-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<component :is="action.icon" :size="16" />
|
|
|
|
|
|
{{ action.label }}
|
|
|
|
|
|
</PixelButton>
|
|
|
|
|
|
<PixelButton :variant="activeTab === 'LIST' ? 'primary' : 'secondary'" @click="activeTab = 'LIST'" class="text-xs md:text-sm">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
神明列表
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Main Content Area -->
|
|
|
|
|
|
<div class="flex-grow bg-[#150c1f] border border-[#4a3b5e] p-4 relative overflow-hidden">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Background Decor -->
|
|
|
|
|
|
<div class="absolute inset-0 opacity-20 pointer-events-none flex items-center justify-center">
|
|
|
|
|
|
<div class="w-64 h-64 border-[20px] border-[#2b193f] rounded-full"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- --- VIEW: PRAY --- -->
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<div v-if="activeTab === 'PRAY'" class="flex flex-col h-full gap-4 overflow-y-auto custom-scrollbar">
|
|
|
|
|
|
<!-- Deity Avatar & Basic Info -->
|
|
|
|
|
|
<div class="flex flex-col items-center gap-3">
|
|
|
|
|
|
<div class="relative">
|
|
|
|
|
|
<PixelAvatar
|
|
|
|
|
|
:deityId="currentDeity"
|
|
|
|
|
|
:stageLevel="activeDeityState?.stageLevel || 1"
|
|
|
|
|
|
class="w-32 h-32"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<h2 class="text-2xl text-[#f6b26b] font-bold">{{ activeStage?.name || activeDeity.name }}</h2>
|
|
|
|
|
|
<div class="text-[#99e550] text-sm tracking-wider mb-1">【{{ activeStage?.title || activeDeity.personality }}】</div>
|
|
|
|
|
|
<p class="text-[#8f80a0] text-xs max-w-md italic leading-relaxed">"{{ activeStage?.description }}"</p>
|
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
2025-11-27 08:42:55 +00:00
|
|
|
|
|
|
|
|
|
|
<!-- Progress Section -->
|
|
|
|
|
|
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
|
|
|
|
|
<!-- Max Level Badge -->
|
|
|
|
|
|
<div v-if="isMaxLevel" class="text-center mb-3">
|
|
|
|
|
|
<span class="text-[#f6b26b] font-bold text-sm border border-[#f6b26b] px-3 py-1 rounded bg-[#2b193f] inline-block">
|
|
|
|
|
|
✨ 已達最高階 ✨
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Favor -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="flex justify-between text-xs text-[#8f80a0] mb-1">
|
|
|
|
|
|
<span>好感度</span>
|
|
|
|
|
|
<span>{{ activeDeityFavor }}/100</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="h-2 bg-[#2b193f] border border-[#4a3b5e] rounded-full overflow-hidden relative">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="h-full bg-[#d95763] transition-all duration-500"
|
|
|
|
|
|
:style="{ width: `${activeDeityFavor}%` }"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
2025-11-27 08:42:55 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Current Stage Buffs -->
|
|
|
|
|
|
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
|
|
|
|
|
<div class="text-[#f6b26b] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
|
|
|
|
|
【神力】
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-2 text-xs">
|
2025-11-26 06:53:44 +00:00
|
|
|
|
<div
|
2025-11-27 08:42:55 +00:00
|
|
|
|
v-for="(value, key) in currentStageBuffs"
|
|
|
|
|
|
:key="key"
|
|
|
|
|
|
class="bg-[#0f0816] border border-[#4a3b5e] px-2 py-1 rounded"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="text-[#99e550]">{{ formatBuffKey(key) }}:</span>
|
|
|
|
|
|
<span class="text-[#e0d8f0] ml-1">{{ formatBuffValue(value) }}</span>
|
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<!-- Quests for Current Stage -->
|
|
|
|
|
|
<div v-if="!isMaxLevel && currentStageQuests.length > 0" class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
|
|
|
|
|
<div class="text-[#2ce8f4] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
|
|
|
|
|
【修行】當前階段任務
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="quest in currentStageQuests"
|
|
|
|
|
|
:key="quest.id"
|
|
|
|
|
|
class="bg-[#0f0816] border border-[#4a3b5e] p-2 rounded"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="flex justify-between items-center mb-1">
|
|
|
|
|
|
<span class="text-[#e0d8f0] text-xs">{{ quest.description }}</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
class="text-xs font-bold"
|
|
|
|
|
|
:class="questProgress(quest.id) >= quest.target ? 'text-[#99e550]' : 'text-[#8f80a0]'"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ questProgress(quest.id) }}/{{ quest.target }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="h-1 bg-[#2b193f] rounded-full overflow-hidden">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="h-full transition-all duration-500"
|
|
|
|
|
|
:class="questProgress(quest.id) >= quest.target ? 'bg-[#99e550]' : 'bg-[#8f80a0]'"
|
|
|
|
|
|
:style="{ width: `${Math.min(100, (questProgress(quest.id) / quest.target) * 100)}%` }"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Evolve Button (when all quests completed) -->
|
|
|
|
|
|
<div v-if="canEvolveDeity" class="mt-3 text-center">
|
|
|
|
|
|
<PixelButton
|
|
|
|
|
|
class="w-full py-2 text-sm bg-[#2ce8f4] hover:bg-[#1ad4e0] animate-pulse"
|
|
|
|
|
|
@click="handleEvolve"
|
|
|
|
|
|
>
|
|
|
|
|
|
✨ 進化到下一階段 ✨
|
|
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Lot Types -->
|
|
|
|
|
|
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
|
|
|
|
|
<div class="text-[#ffe762] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
|
|
|
|
|
【求籤】
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="lotType in activeDeity.lotTypes"
|
|
|
|
|
|
:key="lotType"
|
|
|
|
|
|
class="bg-[#0f0816] border border-[#4a3b5e] px-3 py-1 rounded text-xs text-[#e0d8f0]"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ formatLotType(lotType) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Deity Dialogue (Random One) -->
|
|
|
|
|
|
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
|
|
|
|
|
<div class="text-[#d95763] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
|
|
|
|
|
【神諭】
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="bg-[#0f0816] border-l-2 border-[#d95763] px-3 py-2 text-sm text-[#e0d8f0] italic text-center">
|
|
|
|
|
|
"{{ randomDialogue }}"
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Pray Button -->
|
|
|
|
|
|
<div class="flex flex-col items-center gap-2 mt-2 mb-4">
|
|
|
|
|
|
<PixelButton
|
|
|
|
|
|
class="w-48 py-3 text-base"
|
|
|
|
|
|
:class="{ 'animate-pulse': canPray }"
|
|
|
|
|
|
:disabled="!canPray"
|
|
|
|
|
|
@click="$emit('addFavor', 10)"
|
|
|
|
|
|
>
|
|
|
|
|
|
🙏 祈福 ({{ dailyPrayerCount || 0 }}/3)
|
|
|
|
|
|
</PixelButton>
|
|
|
|
|
|
<p v-if="(dailyPrayerCount || 0) >= 3" class="text-xs text-[#8f80a0]">今日祈福次數已用完</p>
|
|
|
|
|
|
<p v-else-if="activeDeityFavor >= 100" class="text-xs text-[#8f80a0]">好感度已滿</p>
|
|
|
|
|
|
<p v-else class="text-xs text-[#8f80a0]">增加好感度與修煉值 (剩餘 {{ 3 - (dailyPrayerCount || 0) }} 次)</p>
|
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<!-- --- VIEW: JIAOBEI (Free Toss) --- -->
|
2025-11-26 06:53:44 +00:00
|
|
|
|
<!-- --- VIEW: JIAOBEI (Free Toss) --- -->
|
|
|
|
|
|
<div v-else-if="activeTab === 'JIAOBEI'" class="flex flex-col items-center justify-center h-full gap-8">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<h3 class="text-[#f6b26b] text-lg uppercase tracking-widest">擲筊問事</h3>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex flex-col items-center gap-4">
|
|
|
|
|
|
<JiaobeiBlocks :result="lastResult" :isTossing="isTossing" />
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="lastResult && !isTossing" class="text-xl font-bold text-[#e0d8f0]">
|
|
|
|
|
|
{{ getJiaobeiResultText(lastResult) }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="mt-8">
|
|
|
|
|
|
<PixelButton @click="handleToss(false)" :disabled="isTossing" class="w-40">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
{{ isTossing ? '擲筊中...' : '開始擲筊' }}
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- --- VIEW: LOT (Draw & Verify) --- -->
|
|
|
|
|
|
<div v-else-if="activeTab === 'LOT'" class="flex flex-col items-center justify-center h-full gap-4 text-center w-full">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Phase: Idle -->
|
|
|
|
|
|
<template v-if="lotPhase === LotPhase.Idle">
|
|
|
|
|
|
<Scroll :size="64" class="text-[#ffe762] mb-4" />
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<h3 class="text-xl text-[#e0d8f0] mb-2">誠心求籤</h3>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
<p class="text-sm text-[#8f80a0] max-w-xs mb-6">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
搖動籤筒求出一支籤,並需連續三個聖杯確認。
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
<PixelButton @click="handleDrawLot" class="w-48">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
開始求籤
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Phase: Drawing (Animation) -->
|
|
|
|
|
|
<div v-else-if="lotPhase === LotPhase.Drawing" class="animate-bounce">
|
|
|
|
|
|
<div class="w-16 h-24 bg-[#4a2e18] border-2 border-[#f6b26b] mx-auto mb-4 relative rounded-sm">
|
|
|
|
|
|
<div class="absolute top-0 left-0 w-full h-full flex items-center justify-center text-[#f6b26b] font-bold">
|
|
|
|
|
|
...
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<span class="text-[#f6b26b] tracking-widest">搖籤中...</span>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Phase: Verify -->
|
|
|
|
|
|
<template v-else-if="lotPhase === LotPhase.PendingVerify || lotPhase === LotPhase.Verifying">
|
|
|
|
|
|
<div class="text-2xl font-bold text-[#e0d8f0] border-2 border-[#f6b26b] px-4 py-2 mb-4 bg-[#2b193f]">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
{{ drawnLot?.no }}
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p class="text-sm text-[#8f80a0] mb-4">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
需連續三個聖杯確認
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex gap-2 mb-6 justify-center">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="i in 3"
|
|
|
|
|
|
:key="i"
|
|
|
|
|
|
class="w-4 h-4 rounded-full border border-[#4a3b5e]"
|
|
|
|
|
|
:class="i <= saintCupCount ? 'bg-[#d95763] shadow-[0_0_10px_#d95763]' : 'bg-[#1b1026]'"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<JiaobeiBlocks :result="lastResult" :isTossing="isTossing" />
|
|
|
|
|
|
|
|
|
|
|
|
<div class="mt-8">
|
|
|
|
|
|
<PixelButton @click="handleToss(true)" :disabled="isTossing" class="w-40">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
擲筊確認 ({{ saintCupCount }}/3)
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Phase: Failed -->
|
|
|
|
|
|
<template v-else-if="lotPhase === LotPhase.Failed">
|
|
|
|
|
|
<div class="text-[#d95763] text-4xl mb-4">✖</div>
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<h3 class="text-lg text-[#d95763] mb-2">笑杯/陰杯</h3>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
<p class="text-sm text-[#8f80a0] mb-6">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
神明指示此籤不對,<br/>請重新求籤。
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
<PixelButton @click="resetLot" variant="danger">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
重新求籤
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Phase: Success - Detailed Result -->
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<div v-else-if="lotPhase === LotPhase.Result && drawnLot" class="w-full h-full overflow-y-auto custom-scrollbar p-2">
|
2025-11-26 06:53:44 +00:00
|
|
|
|
<PixelFrame class="bg-[#1b1026] border-4 border-[#f6b26b] relative shadow-[0_0_20px_rgba(246,178,107,0.3)]">
|
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
|
<div class="text-center border-b-2 border-[#4a3b5e] pb-3 mb-3 bg-[#231533] p-2">
|
|
|
|
|
|
<div class="text-[#99e550] text-sm md:text-lg font-bold mb-1 flex items-center justify-center gap-2 animate-pulse">
|
|
|
|
|
|
<Sparkles :size="16" />
|
|
|
|
|
|
<span>三聖筊!{{ activeDeity.name }}允准解籤</span>
|
|
|
|
|
|
<Sparkles :size="16" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-[#f6b26b] text-xl md:text-3xl font-bold tracking-widest mt-2 font-serif">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
【{{ drawnLot.no }}】 {{ drawnLot.grade }}
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Poem (Block) -->
|
|
|
|
|
|
<div class="bg-[#2b193f] p-4 text-center mb-4 border-l-4 border-[#f6b26b] mx-2 shadow-inner">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<div class="text-lg md:text-xl text-[#e0d8f0] tracking-[0.2em] leading-loose font-serif drop-shadow-md whitespace-pre-line">
|
|
|
|
|
|
{{ drawnLot.poem1 }}
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Details Grid -->
|
|
|
|
|
|
<div class="flex flex-col gap-4 text-left px-2">
|
|
|
|
|
|
<!-- Meaning -->
|
|
|
|
|
|
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
|
|
|
|
|
<span class="text-[#f6b26b] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
【聖意】 Meaning
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</span>
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ drawnLot.meaning }}</p>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Interpretation -->
|
|
|
|
|
|
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
|
|
|
|
|
<span class="text-[#99e550] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
【解曰】 Interpretation
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</span>
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ drawnLot.explanation }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Oracle -->
|
|
|
|
|
|
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
|
|
|
|
|
<span class="text-[#2ce8f4] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
|
|
|
|
|
【仙機】 Oracle
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div class="text-[#e0d8f0] text-sm leading-relaxed mt-1 whitespace-pre-wrap">
|
|
|
|
|
|
{{ drawnLot.oracle }}
|
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Story -->
|
|
|
|
|
|
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
|
|
|
|
|
<span class="text-[#2ce8f4] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
【典故】 Story
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
<div class="text-[#8f80a0] text-xs leading-relaxed mt-1">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
{{ getFirstStory(drawnLot.story) }}
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="mt-6 flex justify-center mb-2">
|
|
|
|
|
|
<PixelButton @click="resetLot" class="w-full md:w-auto px-8 py-3">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
收下
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</PixelButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</PixelFrame>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- --- VIEW: LIST/SWITCH --- -->
|
|
|
|
|
|
<div v-else-if="activeTab === 'LIST' || activeTab === 'VERIFY'" class="flex flex-col gap-4">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<div class="text-xs text-[#8f80a0] uppercase mb-2">▼ 切換神明</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="deity in Object.values(deities)"
|
|
|
|
|
|
:key="deity.id"
|
|
|
|
|
|
@click="$emit('switchDeity', deity.id)"
|
|
|
|
|
|
class="border-2 p-3 flex items-center gap-3 transition-all relative"
|
|
|
|
|
|
:class="currentDeity === deity.id ? 'border-[#99e550] bg-[#2b193f]' : 'border-[#4a3b5e] bg-[#0f0816] hover:bg-[#150c1f]'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="w-10 h-10 relative">
|
2025-11-27 08:42:55 +00:00
|
|
|
|
<PixelAvatar :deityId="deity.id" :stageLevel="getDeityStageLevel(deity.id)" />
|
2025-11-26 06:53:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex flex-col items-start">
|
|
|
|
|
|
<span class="font-bold" :class="currentDeity === deity.id ? 'text-[#99e550]' : 'text-[#e0d8f0]'">
|
|
|
|
|
|
{{ deity.name }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="text-[10px] text-[#8f80a0]">{{ deity.title }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="currentDeity === deity.id" class="absolute top-2 right-2 w-2 h-2 bg-[#99e550] rounded-full shadow-[0_0_5px_#99e550]"></div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-11-26 09:53:03 +00:00
|
|
|
|
import { ref, computed, watch } from 'vue';
|
|
|
|
|
|
import { Heart, Sparkles, Scroll, Repeat, CheckCircle2, Gift } from 'lucide-vue-next';
|
2025-11-26 06:53:44 +00:00
|
|
|
|
import PixelButton from './PixelButton.vue';
|
|
|
|
|
|
import PixelAvatar from './PixelAvatar.vue';
|
|
|
|
|
|
import PixelFrame from './PixelFrame.vue';
|
|
|
|
|
|
import JiaobeiBlocks from './JiaobeiBlocks.vue';
|
2025-11-27 08:42:55 +00:00
|
|
|
|
import guanyinLots from '../../../guanyin_100_lots.json';
|
2025-11-26 09:53:03 +00:00
|
|
|
|
|
|
|
|
|
|
type Deity = any;
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
|
|
|
|
|
interface Props {
|
2025-11-26 09:53:03 +00:00
|
|
|
|
currentDeity: string;
|
|
|
|
|
|
deities: Record<string, Deity>;
|
2025-11-27 08:42:55 +00:00
|
|
|
|
deitySystemState?: any;
|
|
|
|
|
|
petSystemState?: any;
|
|
|
|
|
|
dailyPrayerCount?: number;
|
2025-11-26 06:53:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
2025-11-27 08:42:55 +00:00
|
|
|
|
const emit = defineEmits(['switchDeity', 'addFavor', 'onJiaobei', 'evolve']);
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
|
|
|
|
|
const activeTab = ref('PRAY');
|
|
|
|
|
|
const isTossing = ref(false);
|
2025-11-26 09:53:03 +00:00
|
|
|
|
const lastResult = ref<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const LotPhase = {
|
|
|
|
|
|
Idle: 'idle',
|
|
|
|
|
|
Drawing: 'drawing',
|
|
|
|
|
|
Verifying: 'verifying',
|
|
|
|
|
|
Result: 'result',
|
|
|
|
|
|
PendingVerify: 'pending_verify',
|
|
|
|
|
|
Success: 'success',
|
|
|
|
|
|
Failed: 'failed'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const JiaobeiResult = {
|
|
|
|
|
|
Saint: 'Saint',
|
|
|
|
|
|
Smile: 'Smile',
|
|
|
|
|
|
Cry: 'Cry'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const lotPhase = ref<string>(LotPhase.Idle);
|
2025-11-27 08:42:55 +00:00
|
|
|
|
const drawnLot = ref<any>(null);
|
2025-11-26 06:53:44 +00:00
|
|
|
|
const saintCupCount = ref(0);
|
|
|
|
|
|
|
|
|
|
|
|
const TAB_ACTIONS = [
|
2025-11-27 08:42:55 +00:00
|
|
|
|
{ id: 'PRAY', label: '祈禱', icon: Sparkles },
|
|
|
|
|
|
{ id: 'LOT', label: '求籤', icon: Scroll },
|
|
|
|
|
|
{ id: 'JIAOBEI', label: '擲筊', icon: Repeat },
|
2025-11-26 06:53:44 +00:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
const activeDeity = computed(() => props.deities[props.currentDeity] || Object.values(props.deities)[0]);
|
|
|
|
|
|
|
|
|
|
|
|
// Computed for Stage Logic
|
|
|
|
|
|
const activeDeityState = computed(() => {
|
|
|
|
|
|
if (!props.deitySystemState?.collectedDeities) return null;
|
|
|
|
|
|
return props.deitySystemState.collectedDeities.find((d: any) => d.id === props.currentDeity);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const activeStage = computed(() => {
|
|
|
|
|
|
if (!activeDeity.value || !activeDeityState.value) return null;
|
|
|
|
|
|
return activeDeity.value.stages.find((s: any) => s.level === activeDeityState.value.stageLevel);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const nextStageExp = computed(() => {
|
|
|
|
|
|
if (!activeDeity.value || !activeDeityState.value) return 100;
|
|
|
|
|
|
const nextStage = activeDeity.value.stages.find((s: any) => s.level === activeDeityState.value.stageLevel + 1);
|
|
|
|
|
|
return nextStage ? nextStage.requiredExp : 9999;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const isMaxLevel = computed(() => {
|
|
|
|
|
|
if (!activeDeity.value || !activeDeityState.value) return false;
|
|
|
|
|
|
return activeDeityState.value.stageLevel >= activeDeity.value.stages.length;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const expPercentage = computed(() => {
|
|
|
|
|
|
if (!activeDeityState.value) return 0;
|
|
|
|
|
|
if (isMaxLevel.value) return 100;
|
|
|
|
|
|
return Math.min(100, (activeDeityState.value.exp / nextStageExp.value) * 100);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const activeDeityFavor = computed(() => {
|
|
|
|
|
|
if (!props.petSystemState?.deityFavors) return 0;
|
|
|
|
|
|
return props.petSystemState.deityFavors[props.currentDeity] || 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const canPray = computed(() => {
|
|
|
|
|
|
const prayCount = props.dailyPrayerCount || 0;
|
|
|
|
|
|
return prayCount < 3 && activeDeityFavor.value < 100;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const questProgress = (questId: string | number) => {
|
|
|
|
|
|
return activeDeityState.value?.quests?.[questId] || 0;
|
2025-11-26 06:53:44 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
const getDeityStageLevel = (deityId: string) => {
|
|
|
|
|
|
if (!props.deitySystemState?.collectedDeities) return 1;
|
|
|
|
|
|
const deity = props.deitySystemState.collectedDeities.find((d: any) => d.id === deityId);
|
|
|
|
|
|
return deity ? deity.stageLevel : 1;
|
|
|
|
|
|
};
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
const canEvolveDeity = computed(() => {
|
|
|
|
|
|
if (!activeDeityState.value || !activeDeity.value) return false;
|
|
|
|
|
|
if (isMaxLevel.value) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// Check if all current stage quests are completed
|
|
|
|
|
|
const quests = currentStageQuests.value;
|
|
|
|
|
|
if (!quests || quests.length === 0) return false;
|
|
|
|
|
|
|
|
|
|
|
|
return quests.every((q: any) => questProgress(q.id) >= q.target);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const handleEvolve = () => {
|
|
|
|
|
|
if (canEvolveDeity.value) {
|
|
|
|
|
|
emit('evolve');
|
|
|
|
|
|
}
|
2025-11-26 06:53:44 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
const currentStageBuffs = computed(() => {
|
|
|
|
|
|
return activeStage.value?.buffs || {};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const currentStageQuests = computed(() => {
|
|
|
|
|
|
if (!activeDeity.value || !activeDeityState.value) return [];
|
|
|
|
|
|
// Get current stage quests (quests to complete this stage)
|
|
|
|
|
|
const currentStage = activeDeity.value.stages.find((s: any) => s.level === activeDeityState.value.stageLevel);
|
|
|
|
|
|
return currentStage?.quests || [];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const formatBuffKey = (key: string | number): string => {
|
|
|
|
|
|
const keyStr = String(key);
|
|
|
|
|
|
const keyMap: Record<string, string> = {
|
|
|
|
|
|
luck: '幸運',
|
|
|
|
|
|
gameSuccessRate: '遊戲成功率',
|
|
|
|
|
|
sicknessReduction: '抗病',
|
|
|
|
|
|
evolutionSpeed: '進化速度',
|
|
|
|
|
|
cleanlinessRetention: '清潔保持',
|
|
|
|
|
|
moodRetention: '心情保持',
|
|
|
|
|
|
trainingExp: '訓練經驗',
|
|
|
|
|
|
workGold: '工作金幣',
|
|
|
|
|
|
happinessRecovery: '快樂恢復',
|
|
|
|
|
|
healthRecovery: '健康恢復',
|
|
|
|
|
|
sicknessImmune: '免疫生病',
|
|
|
|
|
|
dropRate: '掉落率',
|
|
|
|
|
|
resourceGain: '資源獲得',
|
|
|
|
|
|
intGain: '智力成長',
|
|
|
|
|
|
miniGameBonus: '小遊戲獎勵',
|
|
|
|
|
|
breedingSuccess: '繁殖成功率',
|
|
|
|
|
|
badEventReduction: '壞事件減免'
|
|
|
|
|
|
};
|
|
|
|
|
|
return keyMap[keyStr] || keyStr;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const formatBuffValue = (value: any): string => {
|
|
|
|
|
|
if (typeof value === 'boolean') return value ? '是' : '否';
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
|
|
if (value < 1) return `+${(value * 100).toFixed(0)}%`;
|
|
|
|
|
|
return `+${value}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return String(value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getJiaobeiResultText = (result: string): string => {
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
|
case 'Saint': return '聖杯 (允杯)';
|
|
|
|
|
|
case 'Smile': return '笑杯';
|
|
|
|
|
|
case 'Cry': return '陰杯 (怒杯)';
|
|
|
|
|
|
default: return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const formatLotType = (lotType: string): string => {
|
|
|
|
|
|
const typeMap: Record<string, string> = {
|
|
|
|
|
|
guanyin_100: '觀音一百籤',
|
|
|
|
|
|
mazu_60: '媽祖六十甲子籤',
|
|
|
|
|
|
moon_blocks: '擲筊',
|
|
|
|
|
|
tarot_major: '塔羅大牌占卜'
|
|
|
|
|
|
};
|
|
|
|
|
|
return typeMap[lotType] || lotType;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Random dialogue - stable until deity changes
|
|
|
|
|
|
const randomDialogue = ref('');
|
|
|
|
|
|
watch(() => props.currentDeity, () => {
|
|
|
|
|
|
if (!activeDeity.value?.dialogues || activeDeity.value.dialogues.length === 0) {
|
|
|
|
|
|
randomDialogue.value = '';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * activeDeity.value.dialogues.length);
|
|
|
|
|
|
randomDialogue.value = activeDeity.value.dialogues[randomIndex];
|
2025-11-26 06:53:44 +00:00
|
|
|
|
}
|
2025-11-27 08:42:55 +00:00
|
|
|
|
}, { immediate: true });
|
|
|
|
|
|
|
|
|
|
|
|
const getFirstStory = (storyText: string) => {
|
|
|
|
|
|
if (!storyText) return '';
|
|
|
|
|
|
const parts = storyText.split(/2\./);
|
|
|
|
|
|
return parts[0].trim();
|
2025-11-26 06:53:44 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-27 08:42:55 +00:00
|
|
|
|
// --- Methods ---
|
|
|
|
|
|
|
2025-11-26 06:53:44 +00:00
|
|
|
|
const handleDrawLot = () => {
|
2025-11-27 08:42:55 +00:00
|
|
|
|
lotPhase.value = LotPhase.Drawing;
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * guanyinLots.length);
|
|
|
|
|
|
drawnLot.value = guanyinLots[randomIndex];
|
|
|
|
|
|
lotPhase.value = LotPhase.PendingVerify;
|
|
|
|
|
|
saintCupCount.value = 0;
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleToss = (isVerifying: boolean) => {
|
|
|
|
|
|
if (isTossing.value) return;
|
|
|
|
|
|
isTossing.value = true;
|
2025-11-26 06:53:44 +00:00
|
|
|
|
lastResult.value = null;
|
2025-11-27 08:42:55 +00:00
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const rand = Math.random();
|
|
|
|
|
|
let result = '';
|
|
|
|
|
|
if (rand < 0.5) result = JiaobeiResult.Saint;
|
|
|
|
|
|
else if (rand < 0.75) result = JiaobeiResult.Smile;
|
|
|
|
|
|
else result = JiaobeiResult.Cry;
|
|
|
|
|
|
|
|
|
|
|
|
lastResult.value = result;
|
|
|
|
|
|
isTossing.value = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (activeTab.value === 'JIAOBEI') {
|
|
|
|
|
|
emit('onJiaobei', result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isVerifying) {
|
|
|
|
|
|
if (result === JiaobeiResult.Saint) {
|
|
|
|
|
|
saintCupCount.value++;
|
|
|
|
|
|
if (saintCupCount.value >= 3) {
|
|
|
|
|
|
lotPhase.value = LotPhase.Result;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
saintCupCount.value = 0;
|
|
|
|
|
|
lotPhase.value = LotPhase.Failed;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1500);
|
2025-11-26 06:53:44 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const resetLot = () => {
|
2025-11-27 08:42:55 +00:00
|
|
|
|
lotPhase.value = LotPhase.Idle;
|
|
|
|
|
|
drawnLot.value = null;
|
|
|
|
|
|
saintCupCount.value = 0;
|
|
|
|
|
|
lastResult.value = null;
|
2025-11-26 06:53:44 +00:00
|
|
|
|
};
|
|
|
|
|
|
</script>
|