147 lines
7.2 KiB
Vue
147 lines
7.2 KiB
Vue
<template>
|
|
<div class="h-full flex flex-col p-4 gap-4 bg-[#1b1026] overflow-y-auto custom-scrollbar">
|
|
|
|
<!-- Table Background styling -->
|
|
<div class="absolute inset-0 bg-[#231533] opacity-50 pointer-events-none" />
|
|
|
|
<!-- Main Grid Layout -->
|
|
<div class="flex-grow grid grid-cols-12 gap-2 z-10">
|
|
|
|
<!-- Left: Hand (Col 3) -->
|
|
<div class="col-span-3 flex flex-col gap-2">
|
|
<PixelFrame title="HAND" class="h-full bg-[#1b1026]" variant="inset">
|
|
<div class="flex flex-col gap-2 h-full overflow-y-auto pr-1 custom-scrollbar">
|
|
<div v-for="(card, i) in handCards" :key="i" class="bg-[#2b193f] border-2 border-[#4a3b5e] p-1.5 flex flex-col hover:-translate-y-1 transition-transform cursor-pointer group shadow-lg">
|
|
<div class="flex justify-between items-start mb-1">
|
|
<div class="w-5 h-5 bg-[#1b1026] flex items-center justify-center rounded-sm">
|
|
<span class="text-[#f6b26b] text-[10px] font-bold">{{ card.cost }}</span>
|
|
</div>
|
|
<component :is="card.icon" :size="14" :color="card.color" />
|
|
</div>
|
|
<span class="text-xs text-[#e0d8f0] uppercase tracking-wide group-hover:text-[#f6b26b]">{{ card.name }}</span>
|
|
</div>
|
|
<!-- Empty Slot -->
|
|
<div class="border-2 border-dashed border-[#4a3b5e] rounded h-16 opacity-30 flex items-center justify-center text-xs">EMPTY</div>
|
|
</div>
|
|
</PixelFrame>
|
|
</div>
|
|
|
|
<!-- Center: Action Grid (Col 6) -->
|
|
<div class="col-span-6 flex flex-col relative">
|
|
<PixelFrame class="h-full bg-[#2b193f]">
|
|
<div class="grid grid-cols-4 grid-rows-3 gap-2 h-full p-1">
|
|
<template v-for="(action, index) in gridItems" :key="index">
|
|
<button
|
|
v-if="action"
|
|
@click="handleActionClick(action.id)"
|
|
class="relative bg-[#1b1026] border-2 border-[#4a3b5e] hover:border-[#f6b26b] hover:bg-[#231533] active:bg-[#f6b26b] active:border-[#f6b26b] group flex flex-col items-center justify-center p-1 transition-colors"
|
|
>
|
|
<div class="mb-1 p-1 rounded-sm bg-[#231533] group-active:bg-[#1b1026]">
|
|
<component :is="action.icon" :size="20" :color="action.color" class="group-active:text-[#f6b26b]" />
|
|
</div>
|
|
<span class="text-[9px] md:text-[10px] text-[#8f80a0] uppercase tracking-wider group-hover:text-white group-active:text-[#1b1026] font-bold text-center leading-tight">
|
|
{{ action.label }}
|
|
</span>
|
|
|
|
<!-- Corner deco -->
|
|
<div class="absolute top-0 right-0 w-1 h-1 bg-[#4a3b5e] group-hover:bg-[#f6b26b]" />
|
|
<div class="absolute bottom-0 left-0 w-1 h-1 bg-[#4a3b5e] group-hover:bg-[#f6b26b]" />
|
|
</button>
|
|
<div v-else class="bg-[#150c1f] border-2 border-[#2b193f] flex items-center justify-center opacity-50 cursor-not-allowed">
|
|
<div class="w-2 h-2 bg-[#2b193f] rounded-full"></div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</PixelFrame>
|
|
</div>
|
|
|
|
<!-- Right: Stats & EQ (Col 3) -->
|
|
<div class="col-span-3 flex flex-col gap-2">
|
|
<!-- Stats Table -->
|
|
<PixelFrame title="STATS" class="bg-[#1b1026] h-2/3" variant="inset">
|
|
<div class="grid grid-cols-2 gap-x-2 content-start h-full p-1 overflow-y-auto custom-scrollbar">
|
|
<div v-for="(stat, i) in statsList" :key="i" class="flex justify-between items-center border-b border-[#2b193f] pb-0.5 mb-0.5">
|
|
<span class="text-[#8f80a0] text-[9px]">{{ stat.l }}</span>
|
|
<span class="font-mono text-[10px]" :style="{ color: stat.c || '#e0d8f0' }">{{ stat.v }}</span>
|
|
</div>
|
|
</div>
|
|
</PixelFrame>
|
|
|
|
<!-- Equipment Grid -->
|
|
<PixelFrame title="EQ" class="bg-[#1b1026] h-1/3" variant="inset">
|
|
<div
|
|
class="grid grid-cols-4 gap-1 h-full content-center cursor-pointer relative group"
|
|
@click="$emit('openInventory')"
|
|
title="Open Backpack"
|
|
>
|
|
<div v-for="(Icon, idx) in [Crown, Shirt, Hand, Footprints]" :key="idx" class="aspect-square bg-[#2b193f] border border-[#4a3b5e] flex items-center justify-center group-hover:border-[#f6b26b] transition-colors">
|
|
<component :is="Icon" :size="12" class="text-[#5a4b6e] group-hover:text-[#e0d8f0]" />
|
|
</div>
|
|
<!-- Hover Hint -->
|
|
<div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 flex items-center justify-center text-[10px] text-[#f6b26b] font-bold pointer-events-none">
|
|
OPEN
|
|
</div>
|
|
</div>
|
|
</PixelFrame>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import PixelFrame from './PixelFrame.vue';
|
|
import {
|
|
Sword, Shield, FlaskConical, Crown, Hand, Footprints, Shirt,
|
|
Utensils, Gamepad2, Dumbbell, Puzzle, Brush, Pill, Sun, Sparkles, ShoppingBag, Swords
|
|
} from 'lucide-vue-next';
|
|
import type { EntityStats } from '~/types/pixel';
|
|
|
|
interface Props {
|
|
playerStats?: EntityStats;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
const emit = defineEmits(['openInventory', 'openGodSystem', 'openShop', 'openAdventure']);
|
|
|
|
const ACTIONS = [
|
|
{ id: 'feed', icon: Utensils, color: '#9fd75b', label: 'FEED 餵食' },
|
|
{ id: 'play', icon: Gamepad2, color: '#f6b26b', label: 'PLAY 玩耍' },
|
|
{ id: 'train', icon: Dumbbell, color: '#d75b5b', label: 'TRAIN 訓練' },
|
|
{ id: 'puzzle', icon: Puzzle, color: '#2ce8f4', label: 'PUZZLE 益智' },
|
|
{ id: 'clean', icon: Brush, color: '#8f80a0', label: 'CLEAN 清理' },
|
|
{ id: 'heal', icon: Pill, color: '#9fd75b', label: 'HEAL 治療' },
|
|
{ id: 'fight', icon: Swords, color: '#d95763', label: 'FIGHT 戰鬥' },
|
|
{ id: 'wake', icon: Sun, color: '#ffe762', label: 'WAKE 起床' },
|
|
{ id: 'pray', icon: Sparkles, color: '#e0d8f0', label: 'PRAY 祈福' },
|
|
{ id: 'shop', icon: ShoppingBag, color: '#ffa500', label: 'SHOP 商店' },
|
|
];
|
|
|
|
const gridItems = computed(() => {
|
|
return Array.from({ length: 12 }).map((_, i) => ACTIONS[i] || null);
|
|
});
|
|
|
|
const handCards = [
|
|
{ name: 'Slash', cost: 2, icon: Sword, color: '#d75b5b' },
|
|
{ name: 'Block', cost: 1, icon: Shield, color: '#f6b26b' },
|
|
{ name: 'Heal', cost: 3, icon: FlaskConical, color: '#9fd75b' }
|
|
];
|
|
|
|
const statsList = computed(() => {
|
|
const s = props.playerStats || { str:0, int:0, dex:0, luck:0, atk:0, def:0, spd:0 };
|
|
return [
|
|
{l:'STR', v:s.str}, {l:'ATK', v:s.atk, c: '#d75b5b'},
|
|
{l:'INT', v:s.int}, {l:'DEF', v:s.def, c: '#f6b26b'},
|
|
{l:'DEX', v:s.dex}, {l:'SPD', v:s.spd},
|
|
{l:'LCK', v:s.luck},
|
|
];
|
|
});
|
|
|
|
const handleActionClick = (id: string) => {
|
|
if (id === 'pray') emit('openGodSystem');
|
|
else if (id === 'shop') emit('openShop');
|
|
else if (id === 'fight') emit('openAdventure');
|
|
};
|
|
</script>
|