pet_data/components/pixel/InventoryModal.vue

469 lines
9.7 KiB
Vue
Raw Normal View History

2025-11-25 10:04:01 +00:00
<template>
<div v-if="isOpen" class="inventory-modal" @click.self="close">
<div class="inventory-container">
<!-- 標題欄 -->
<div class="inventory-header">
<h3>🎒 Inventory</h3>
<button class="close-btn" @click="close"></button>
</div>
<!-- 主要內容 -->
<div class="inventory-content">
<!-- 左側角色裝備 -->
<div class="equipment-panel">
<div class="character-display">
<div class="pet-preview"></div>
</div>
<div class="equipment-slots">
<div class="equip-slot head" title="Head">
<span class="slot-icon">🎩</span>
</div>
<div class="equip-slot body" title="Body">
<span class="slot-icon">👕</span>
</div>
<div class="equip-slot accessory" title="Accessory">
<span class="slot-icon">💍</span>
</div>
</div>
<!-- 快速資訊 -->
<div class="quick-stats">
<div class="stat-row">
<span>💰</span>
<span>{{ coins }}</span>
</div>
<div class="stat-row">
<span></span>
<span>{{ attack }}</span>
</div>
<div class="stat-row">
<span>🛡</span>
<span>{{ defense }}</span>
</div>
</div>
</div>
<!-- 右側物品格子 -->
<div class="items-panel">
<!-- 分類標籤 -->
<div class="category-tabs">
<button
v-for="category in categories"
:key="category.id"
:class="['tab', { active: activeCategory === category.id }]"
@click="activeCategory = category.id"
>
{{ category.icon }} {{ category.name }}
</button>
</div>
<!-- 物品網格 -->
<div class="item-grid">
<div
v-for="i in 24"
:key="i"
class="item-slot"
:class="{ filled: items[i - 1] }"
@click="selectItem(i - 1)"
>
<span v-if="items[i - 1]" class="item-icon">
{{ items[i - 1].icon }}
</span>
<span v-if="items[i - 1] && items[i - 1].count > 1" class="item-count">
{{ items[i - 1].count }}
</span>
</div>
</div>
<!-- 底部操作欄 -->
<div class="action-bar">
<button class="action-btn" @click="useItem">Use</button>
<button class="action-btn" @click="dropItem">Drop</button>
<button class="action-btn sort" @click="sortItems">Sort</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
isOpen: {
type: Boolean,
default: false
},
coins: {
type: Number,
default: 0
},
attack: {
type: Number,
default: 10
},
defense: {
type: Number,
default: 5
}
})
const emit = defineEmits(['close', 'use-item', 'drop-item'])
const activeCategory = ref('all')
const selectedSlot = ref(null)
const categories = [
{ id: 'all', name: 'All', icon: '📦' },
{ id: 'equipment', name: 'Gear', icon: '⚔️' },
{ id: 'consumable', name: 'Food', icon: '🍖' },
{ id: 'material', name: 'Items', icon: '💎' }
]
// 示例物品數據
const items = ref([
{ icon: '🍖', count: 5 },
{ icon: '💊', count: 3 },
{ icon: '🗡️', count: 1 },
{ icon: '🛡️', count: 1 },
null, null, null, null,
{ icon: '💎', count: 10 },
null, null, null,
null, null, null, null,
null, null, null, null,
null, null, null, null
])
const close = () => {
emit('close')
}
const selectItem = (index) => {
selectedSlot.value = index
}
const useItem = () => {
if (selectedSlot.value !== null && items.value[selectedSlot.value]) {
emit('use-item', items.value[selectedSlot.value])
}
}
const dropItem = () => {
if (selectedSlot.value !== null && items.value[selectedSlot.value]) {
emit('drop-item', items.value[selectedSlot.value])
items.value[selectedSlot.value] = null
}
}
const sortItems = () => {
// 簡單排序邏輯
const nonNullItems = items.value.filter(item => item !== null)
items.value = [...nonNullItems, ...Array(24 - nonNullItems.length).fill(null)]
}
</script>
<style scoped>
.inventory-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.inventory-container {
width: 90%;
max-width: 800px;
max-height: 90vh;
background: var(--color-panel);
border: 3px solid var(--color-accent);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
overflow-y: auto;
}
/* 手機直向 */
@media (max-width: 480px) {
.inventory-container {
width: 95%;
max-width: none;
max-height: 95vh;
border-radius: 12px;
}
}
/* 平板 */
@media (min-width: 481px) and (max-width: 768px) {
.inventory-container {
width: 92%;
}
}
.inventory-header {
background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%);
padding: 16px 20px;
border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 3px solid #5a4a3a;
}
.inventory-header h3 {
margin: 0;
color: white;
font-size: 18px;
text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
}
.close-btn {
width: 32px;
height: 32px;
background: rgba(255, 255, 255, 0.2);
border: 2px solid white;
border-radius: 8px;
color: white;
font-size: 18px;
cursor: pointer;
transition: all 0.2s;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.1);
}
.inventory-content {
display: grid;
grid-template-columns: 200px 1fr;
gap: 16px;
padding: 16px;
min-height: 500px;
}
/* 手機直向:垂直堆疊 */
@media (max-width: 480px) {
.inventory-content {
grid-template-columns: 1fr;
gap: 12px;
padding: 12px;
min-height: auto;
}
.equipment-panel {
max-height: 200px;
}
}
/* 手機橫向 */
@media (max-width: 768px) and (orientation: landscape) {
.inventory-content {
min-height: 300px;
}
}
/* 左側裝備欄 */
.equipment-panel {
background: rgba(255, 255, 255, 0.6);
border: 3px solid #5a4a3a;
border-radius: 12px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.character-display {
width: 100%;
height: 150px;
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
border: 3px solid #5a4a3a;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.pet-preview {
width: 80px;
height: 80px;
background: #ff6b9d;
border: 3px solid #5a4a3a;
border-radius: 50%;
}
.equipment-slots {
display: flex;
flex-direction: column;
gap: 8px;
}
.equip-slot {
height: 48px;
background: rgba(255, 255, 255, 0.8);
border: 3px solid #5a4a3a;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
cursor: pointer;
transition: all 0.2s;
}
.equip-slot:hover {
background: #ffeaa7;
transform: scale(1.05);
}
.quick-stats {
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 8px;
border-top: 2px solid #5a4a3a;
}
.stat-row {
display: flex;
justify-content: space-between;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.5);
border-radius: 6px;
font-size: 14px;
font-weight: bold;
}
/* 右側物品欄 */
.items-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.category-tabs {
display: flex;
gap: 8px;
}
.tab {
padding: 8px 16px;
background: rgba(255, 255, 255, 0.6);
border: 3px solid #5a4a3a;
border-radius: 8px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.tab:hover {
background: rgba(255, 255, 255, 0.8);
}
.tab.active {
background: linear-gradient(135deg, #74b9ff 0%, #a29bfe 100%);
color: white;
transform: translateY(-2px);
}
.item-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
background: var(--color-panel-light);
border: 2px solid var(--color-border);
border-radius: 12px;
padding: 12px;
flex: 1;
}
/* 手機直向4列 */
@media (max-width: 480px) {
.item-grid {
grid-template-columns: repeat(4, 1fr);
gap: 6px;
padding: 8px;
}
}
/* 平板5列 */
@media (min-width: 481px) and (max-width: 768px) {
.item-grid {
grid-template-columns: repeat(5, 1fr);
}
}
.item-slot {
aspect-ratio: 1;
background: rgba(255, 255, 255, 0.8);
border: 3px solid #5a4a3a;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
cursor: pointer;
position: relative;
transition: all 0.2s;
}
.item-slot:hover {
background: #ffeaa7;
transform: scale(1.05);
}
.item-slot.filled {
background: rgba(255, 255, 255, 1);
}
.item-count {
position: absolute;
bottom: 2px;
right: 4px;
font-size: 10px;
font-weight: bold;
background: rgba(0, 0, 0, 0.6);
color: white;
padding: 2px 4px;
border-radius: 4px;
}
.action-bar {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.action-btn {
padding: 12px 24px;
background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
border: 3px solid #5a4a3a;
border-radius: 8px;
color: white;
font-weight: bold;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
}
.action-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.action-btn:active {
transform: translateY(0);
}
.action-btn.sort {
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
}
</style>