493 lines
28 KiB
Vue
493 lines
28 KiB
Vue
<template>
|
||
<div class="flex flex-col h-full gap-2 overflow-visible">
|
||
|
||
<!-- 1. 已装备装备栏 -->
|
||
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 overflow-visible">
|
||
<div v-for="(slotConfig, key) in EQUIPMENT_SLOTS" :key="key" class="border border-[#4a3b5e] bg-[#0f0816] p-2 flex flex-col gap-2 overflow-visible">
|
||
<!-- 装备槽标题 -->
|
||
<div class="flex items-center gap-2 justify-center border-b border-[#2b193f] pb-1">
|
||
<component :is="SLOT_ICONS[key]" :size="14" class="text-[#8f80a0]" />
|
||
<span class="text-[#2ce8f4] text-xs font-bold uppercase">{{ slotConfig.name }}</span>
|
||
</div>
|
||
|
||
<!-- 实际装备槽 -->
|
||
<div class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center relative">
|
||
<span class="text-[9px] text-[#8f80a0] mb-1">装备</span>
|
||
<button
|
||
v-if="getEquippedItem(key, false)"
|
||
class="flex flex-col items-center gap-1 hover:bg-[#321e4a] transition-all p-1 rounded w-full"
|
||
@mouseenter="onEquippedItemMouseEnter(`${key}-equipment`)"
|
||
@mouseleave="onItemMouseLeave"
|
||
>
|
||
<div class="relative w-8 h-8">
|
||
<PixelItemIcon :category="getEquippedItem(key, false)!.category" :color="ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color" />
|
||
</div>
|
||
<span class="text-xs text-center truncate w-full px-1 font-bold" :style="{ color: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color }">{{ getEquippedItem(key, false)!.name }}</span>
|
||
|
||
<!-- 懸浮卡片 -->
|
||
<div
|
||
v-show="hoveredEquippedSlot === `${key}-equipment`"
|
||
@mouseenter="onTooltipMouseEnter"
|
||
@mouseleave="onTooltipMouseLeave"
|
||
class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl pointer-events-auto z-[100] w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar"
|
||
>
|
||
<!-- 物品头部 -->
|
||
<div class="flex gap-3 mb-3 pb-3 border-b border-[#4a3b5e]">
|
||
<div class="w-16 h-16 bg-[#1b1026] border-2 rounded flex items-center justify-center p-2 flex-shrink-0"
|
||
:style="{ borderColor: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color }">
|
||
<PixelItemIcon
|
||
:category="getEquippedItem(key, false)!.category"
|
||
:color="ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color"
|
||
/>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<div class="text-sm font-bold mb-1"
|
||
:style="{ color: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color }">
|
||
{{ getEquippedItem(key, false)!.name }}
|
||
</div>
|
||
<div class="flex gap-2 text-[10px] flex-wrap">
|
||
<span class="px-2 py-0.5 bg-[#1b1026] border border-[#4a3b5e] rounded text-[#8f80a0]">
|
||
{{ getItemTypeName(getEquippedItem(key, false)!.type) }}
|
||
</span>
|
||
<span class="px-2 py-0.5 border rounded font-bold"
|
||
:style="{
|
||
borderColor: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color,
|
||
color: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color
|
||
}">
|
||
{{ ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.name }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 描述 -->
|
||
<p class="text-[11px] text-[#e0d8f0] italic mb-3 leading-relaxed">
|
||
"{{ getEquippedItem(key, false)!.description }}"
|
||
</p>
|
||
|
||
<!-- 效果 -->
|
||
<div v-if="getEquippedItem(key, false)!.effects" class="mb-3 space-y-1">
|
||
<div class="text-[10px] text-[#99e550] font-bold mb-1">效果</div>
|
||
<div v-if="getEquippedItem(key, false)!.effects.flat" class="space-y-1">
|
||
<div v-for="(val, effectKey) in getEquippedItem(key, false)!.effects.flat" :key="effectKey"
|
||
class="text-[10px] text-[#9fd75b] flex justify-between border-b border-[#2b193f] border-dashed pb-1">
|
||
<span class="text-[#8f80a0]">{{ formatBuffKey(effectKey) }}</span>
|
||
<span class="font-mono font-bold">+{{ val }}</span>
|
||
</div>
|
||
</div>
|
||
<div v-if="getEquippedItem(key, false)!.effects.percent" class="space-y-1 mt-1">
|
||
<div v-for="(val, effectKey) in getEquippedItem(key, false)!.effects.percent" :key="effectKey"
|
||
class="text-[10px] text-[#2ce8f4] flex justify-between border-b border-[#2b193f] border-dashed pb-1">
|
||
<span class="text-[#8f80a0]">{{ formatBuffKey(effectKey) }}</span>
|
||
<span class="font-mono font-bold">+{{ (val * 100).toFixed(0) }}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 卸下按钮 -->
|
||
<div class="space-y-2 pt-2 border-t border-[#4a3b5e]">
|
||
<button
|
||
@click.stop="$emit('unequip', key, false)"
|
||
class="w-full px-3 py-2 bg-[#8f80a0] text-[#1b1026] rounded text-xs font-bold hover:bg-[#a598b8] transition-colors"
|
||
>
|
||
卸下裝備
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
<span v-else class="text-[10px] text-[#4a3b5e] z-10 opacity-50">空</span>
|
||
</div>
|
||
|
||
<!-- 外观槽 -->
|
||
<div class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center relative">
|
||
<span class="text-[9px] text-[#8f80a0] mb-1">外觀</span>
|
||
<button
|
||
v-if="getEquippedItem(key, true)"
|
||
class="flex flex-col items-center gap-1 hover:bg-[#321e4a] transition-all p-1 rounded w-full"
|
||
@mouseenter="onEquippedItemMouseEnter(`${key}-appearance`)"
|
||
@mouseleave="onItemMouseLeave"
|
||
>
|
||
<div class="relative w-8 h-8">
|
||
<PixelItemIcon :category="getEquippedItem(key, true)!.category" :color="ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color" />
|
||
</div>
|
||
<span class="text-xs text-center truncate w-full px-1 font-bold" :style="{ color: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color }">{{ getEquippedItem(key, true)!.name }}</span>
|
||
|
||
<!-- 懸浮卡片 -->
|
||
<div
|
||
v-show="hoveredEquippedSlot === `${key}-appearance`"
|
||
@mouseenter="onTooltipMouseEnter"
|
||
@mouseleave="onTooltipMouseLeave"
|
||
class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl pointer-events-auto z-[100] w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar"
|
||
>
|
||
<!-- 物品头部 -->
|
||
<div class="flex gap-3 mb-3 pb-3 border-b border-[#4a3b5e]">
|
||
<div class="w-16 h-16 bg-[#1b1026] border-2 rounded flex items-center justify-center p-2 flex-shrink-0"
|
||
:style="{ borderColor: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color }">
|
||
<PixelItemIcon
|
||
:category="getEquippedItem(key, true)!.category"
|
||
:color="ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color"
|
||
/>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<div class="text-sm font-bold mb-1"
|
||
:style="{ color: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color }">
|
||
{{ getEquippedItem(key, true)!.name }}
|
||
</div>
|
||
<div class="flex gap-2 text-[10px] flex-wrap">
|
||
<span class="px-2 py-0.5 bg-[#1b1026] border border-[#4a3b5e] rounded text-[#8f80a0]">
|
||
{{ getItemTypeName(getEquippedItem(key, true)!.type) }}
|
||
</span>
|
||
<span class="px-2 py-0.5 border rounded font-bold"
|
||
:style="{
|
||
borderColor: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color,
|
||
color: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color
|
||
}">
|
||
{{ ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.name }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 描述 -->
|
||
<p class="text-[11px] text-[#e0d8f0] italic mb-3 leading-relaxed">
|
||
"{{ getEquippedItem(key, true)!.description }}"
|
||
</p>
|
||
|
||
<!-- 卸下按钮 -->
|
||
<div class="space-y-2 pt-2 border-t border-[#4a3b5e]">
|
||
<button
|
||
@click.stop="$emit('unequip', key, true)"
|
||
class="w-full px-3 py-2 bg-[#d584fb] text-[#1b1026] rounded text-xs font-bold hover:bg-[#e5a4ff] transition-colors"
|
||
>
|
||
卸下外觀
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
<span v-else class="text-[10px] text-[#4a3b5e] z-10 opacity-50">空</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2. 背包区域 - 固定高度 + 滚动 -->
|
||
<div class="flex-grow overflow-visible mt-2">
|
||
<PixelFrame class="h-full flex flex-col bg-[#1b1026]" :title="`背包 (${items.filter(i => !i.isEquipped).length})`">
|
||
<div class="overflow-y-auto overflow-x-visible p-3 custom-scrollbar" style="height: 400px;">
|
||
<div class="grid grid-cols-6 gap-2">
|
||
<!-- 动态格子数量:基础30格,有道具时自动扩展 -->
|
||
<div v-for="index in totalSlots" :key="index" class="relative aspect-square">
|
||
|
||
<!-- 有物品:显示物品 + 悬浮卡片 -->
|
||
<template v-if="getInventorySlotItem(index - 1)">
|
||
<button
|
||
class="w-full h-full p-2 flex flex-col items-center justify-center gap-1 border-2 transition-all group rounded-sm border-[#4a3b5e] hover:border-[#8f80a0] hover:bg-[#321e4a] bg-[#2b193f]"
|
||
@mouseenter="onItemMouseEnter(index - 1)"
|
||
@mouseleave="onItemMouseLeave"
|
||
>
|
||
<div class="relative w-10 h-10">
|
||
<PixelItemIcon
|
||
:category="getInventorySlotItem(index - 1).category"
|
||
:color="ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color"
|
||
/>
|
||
|
||
<!-- 稀有度发光 -->
|
||
<div
|
||
v-if="['rare', 'epic', 'legendary'].includes(getInventorySlotItem(index - 1).rarity)"
|
||
class="absolute inset-0 rounded-full opacity-40 blur-md pointer-events-none"
|
||
:style="{ backgroundColor: ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color }"
|
||
></div>
|
||
|
||
<span
|
||
v-if="getInventorySlotItem(index - 1).quantity && getInventorySlotItem(index - 1).quantity > 1"
|
||
class="absolute -bottom-1 -right-1 text-[9px] bg-black text-white px-1 border border-[#4a3b5e] rounded-sm z-10 shadow-sm font-mono"
|
||
>{{ getInventorySlotItem(index - 1).quantity }}</span>
|
||
</div>
|
||
|
||
<span
|
||
class="text-[10px] text-center leading-tight line-clamp-2 w-full font-medium relative z-10"
|
||
:style="{
|
||
color: ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color,
|
||
textShadow: ['rare', 'epic', 'legendary'].includes(getInventorySlotItem(index - 1).rarity)
|
||
? `0 0 3px ${ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color}80`
|
||
: 'none'
|
||
}"
|
||
>{{ getInventorySlotItem(index - 1).name }}</span>
|
||
|
||
<!-- 详细悬浮卡片 -->
|
||
<div
|
||
v-show="hoveredItemIndex === index - 1"
|
||
@mouseenter="onTooltipMouseEnter"
|
||
@mouseleave="onTooltipMouseLeave"
|
||
class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl pointer-events-auto z-[100] w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar"
|
||
>
|
||
<!-- 物品头部 -->
|
||
<div class="flex gap-3 mb-3 pb-3 border-b border-[#4a3b5e]">
|
||
<div class="w-16 h-16 bg-[#1b1026] border-2 rounded flex items-center justify-center p-2 flex-shrink-0"
|
||
:style="{ borderColor: ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color }">
|
||
<PixelItemIcon
|
||
:category="getInventorySlotItem(index - 1).category"
|
||
:color="ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color"
|
||
/>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<div class="text-sm font-bold mb-1"
|
||
:style="{ color: ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color }">
|
||
{{ getInventorySlotItem(index - 1).name }}
|
||
</div>
|
||
<div class="flex gap-2 text-[10px] flex-wrap">
|
||
<span class="px-2 py-0.5 bg-[#1b1026] border border-[#4a3b5e] rounded text-[#8f80a0]">
|
||
{{ getItemTypeName(getInventorySlotItem(index - 1).type) }}
|
||
</span>
|
||
<span class="px-2 py-0.5 border rounded font-bold"
|
||
:style="{
|
||
borderColor: ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color,
|
||
color: ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.color
|
||
}">
|
||
{{ ITEM_RARITY[getInventorySlotItem(index - 1).rarity]?.name }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 描述 -->
|
||
<p class="text-[11px] text-[#e0d8f0] italic mb-3 leading-relaxed">
|
||
"{{ getInventorySlotItem(index - 1).description }}"
|
||
</p>
|
||
|
||
<!-- 效果 -->
|
||
<div v-if="getInventorySlotItem(index - 1).effects" class="mb-3 space-y-1">
|
||
<div class="text-[10px] text-[#99e550] font-bold mb-1">效果</div>
|
||
<div v-if="getInventorySlotItem(index - 1).effects.flat" class="space-y-1">
|
||
<div v-for="(val, key) in getInventorySlotItem(index - 1).effects.flat" :key="key"
|
||
class="text-[10px] text-[#9fd75b] flex justify-between border-b border-[#2b193f] border-dashed pb-1">
|
||
<span class="text-[#8f80a0]">{{ formatBuffKey(key) }}</span>
|
||
<span class="font-mono font-bold">+{{ val }}</span>
|
||
</div>
|
||
</div>
|
||
<div v-if="getInventorySlotItem(index - 1).effects.percent" class="space-y-1 mt-1">
|
||
<div v-for="(val, key) in getInventorySlotItem(index - 1).effects.percent" :key="key"
|
||
class="text-[10px] text-[#2ce8f4] flex justify-between border-b border-[#2b193f] border-dashed pb-1">
|
||
<span class="text-[#8f80a0]">{{ formatBuffKey(key) }}</span>
|
||
<span class="font-mono font-bold">+{{ (val * 100).toFixed(0) }}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 耐久度 -->
|
||
<div v-if="getInventorySlotItem(index - 1).maxDurability && getInventorySlotItem(index - 1).maxDurability !== Infinity" class="mb-3">
|
||
<div class="flex justify-between text-[10px] text-[#8f80a0] mb-1">
|
||
<span>耐久度</span>
|
||
<span class="font-mono">{{ getInventorySlotItem(index - 1).durability }} / {{ getInventorySlotItem(index - 1).maxDurability }}</span>
|
||
</div>
|
||
<div class="h-1.5 w-full bg-[#1b1026] border border-[#4a3b5e] rounded-full overflow-hidden">
|
||
<div class="h-full bg-[#f6b26b] transition-all"
|
||
:style="{ width: `${(getInventorySlotItem(index - 1).durability / getInventorySlotItem(index - 1).maxDurability) * 100}%` }">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="space-y-2 pt-2 border-t border-[#4a3b5e]">
|
||
<!-- 装备按钮 -->
|
||
<button
|
||
v-if="getInventorySlotItem(index - 1).slot && getInventorySlotItem(index - 1).type !== ItemType.Appearance"
|
||
@click.stop="$emit('equip', getInventorySlotItem(index - 1).id, false)"
|
||
class="w-full px-3 py-2 bg-[#9fd75b] text-[#1b1026] rounded text-xs font-bold hover:bg-[#b5e87b] transition-colors"
|
||
>
|
||
裝備
|
||
</button>
|
||
|
||
<!-- 外观按钮 -->
|
||
<button
|
||
v-if="getInventorySlotItem(index - 1).type === ItemType.Appearance"
|
||
@click.stop="$emit('equip', getInventorySlotItem(index - 1).id, true)"
|
||
class="w-full px-3 py-2 bg-[#d584fb] text-[#1b1026] rounded text-xs font-bold hover:bg-[#e5a4ff] transition-colors"
|
||
>
|
||
裝備外觀
|
||
</button>
|
||
|
||
<!-- 使用按钮 -->
|
||
<button
|
||
v-if="getInventorySlotItem(index - 1).type === ItemType.Consumable"
|
||
@click.stop="$emit('use', getInventorySlotItem(index - 1).id)"
|
||
class="w-full px-3 py-2 bg-[#2ce8f4] text-[#1b1026] rounded text-xs font-bold hover:bg-[#5cf4ff] transition-colors"
|
||
>
|
||
使用物品
|
||
</button>
|
||
|
||
<!-- 修理按钮 -->
|
||
<button
|
||
v-if="getInventorySlotItem(index - 1).maxDurability && getInventorySlotItem(index - 1).maxDurability !== Infinity && getInventorySlotItem(index - 1).durability < getInventorySlotItem(index - 1).maxDurability"
|
||
@click.stop="$emit('repair', getInventorySlotItem(index - 1).id)"
|
||
class="w-full px-3 py-2 bg-[#f6b26b] text-[#1b1026] rounded text-xs font-bold hover:bg-[#ffc68b] transition-colors flex items-center justify-center gap-2"
|
||
>
|
||
<Hammer :size="12" />
|
||
修理 ({{ Math.ceil((getInventorySlotItem(index - 1).maxDurability - getInventorySlotItem(index - 1).durability) * 0.5) }} G)
|
||
</button>
|
||
|
||
<!-- 删除按钮 -->
|
||
<button
|
||
@click.stop="$emit('delete', getInventorySlotItem(index - 1).id)"
|
||
class="w-full px-3 py-2 bg-[#d95763] text-white rounded text-xs font-bold hover:bg-[#ff6b7a] transition-colors flex items-center justify-center gap-2"
|
||
>
|
||
<Trash2 :size="12" />
|
||
丟棄
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</template>
|
||
|
||
<!-- 空格子:占位符 -->
|
||
<div
|
||
v-else
|
||
class="w-full h-full border-2 border-[#2b193f] bg-[#150c1f] rounded-sm flex items-center justify-center opacity-30"
|
||
>
|
||
<div class="w-8 h-8 border border-[#2b193f] rounded opacity-20"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</PixelFrame>
|
||
</div>
|
||
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue';
|
||
import { Sword, Shield, Crown, Gem, Sparkles, Star, HelpCircle, Trash2, X, Hammer } from 'lucide-vue-next';
|
||
import PixelFrame from './PixelFrame.vue';
|
||
import PixelButton from './PixelButton.vue';
|
||
import PixelItemIcon from './PixelItemIcon.vue';
|
||
import { formatBuffKey } from '../../utils/formatters.js';
|
||
|
||
import { ITEM_TYPE, ITEM_RARITY, EQUIPMENT_SLOTS, ITEM_TYPES } from '../../../../data/items.js';
|
||
|
||
// Use any types since we're using data directly
|
||
type Item = any;
|
||
|
||
interface Props {
|
||
items: Item[];
|
||
}
|
||
|
||
const props = defineProps<Props>();
|
||
defineEmits(['equip', 'unequip', 'use', 'delete', 'repair']);
|
||
|
||
const selectedItemId = ref<string | null>(null);
|
||
const hoveredItemIndex = ref<number | null>(null);
|
||
const hoveredEquippedSlot = ref<string | null>(null); // 新增:用於追蹤裝備槽的懸浮狀態
|
||
const isHoveringTooltip = ref<boolean>(false);
|
||
const hideTooltipTimeout = ref<number | null>(null);
|
||
|
||
const selectedItem = computed(() => props.items.find(i => i.id === selectedItemId.value));
|
||
|
||
// 计算总格子数量:基础30格,有道具时自动扩展
|
||
const totalSlots = computed(() => {
|
||
const unequippedItems = props.items.filter(i => !i.isEquipped);
|
||
const minSlots = 30;
|
||
const itemCount = unequippedItems.length;
|
||
// 如果道具數量超過30,則自動擴展到下一個6的倍數
|
||
if (itemCount > minSlots) {
|
||
return Math.ceil(itemCount / 6) * 6;
|
||
}
|
||
return minSlots;
|
||
});
|
||
|
||
const setSelectedItemId = (id: string | undefined) => {
|
||
if (id) selectedItemId.value = id;
|
||
};
|
||
|
||
// 處理裝備槽的滑鼠進入事件
|
||
const onEquippedItemMouseEnter = (slotKey: string) => {
|
||
// 清除任何待處理的隱藏 timeout
|
||
if (hideTooltipTimeout.value) {
|
||
clearTimeout(hideTooltipTimeout.value);
|
||
hideTooltipTimeout.value = null;
|
||
}
|
||
// 設置當前懸停的裝備槽
|
||
hoveredEquippedSlot.value = slotKey;
|
||
// 清除背包道具的懸停狀態
|
||
hoveredItemIndex.value = null;
|
||
};
|
||
|
||
// 處理背包道具的滑鼠進入事件
|
||
const onItemMouseEnter = (itemIndex: number) => {
|
||
// 清除任何待處理的隱藏 timeout
|
||
if (hideTooltipTimeout.value) {
|
||
clearTimeout(hideTooltipTimeout.value);
|
||
hideTooltipTimeout.value = null;
|
||
}
|
||
// 設置當前懸停的道具索引
|
||
hoveredItemIndex.value = itemIndex;
|
||
// 清除裝備槽的懸停狀態
|
||
hoveredEquippedSlot.value = null;
|
||
};
|
||
|
||
const onItemMouseLeave = () => {
|
||
// 清除之前的timeout(如果有)
|
||
if (hideTooltipTimeout.value) {
|
||
clearTimeout(hideTooltipTimeout.value);
|
||
}
|
||
|
||
// 延遲較長時間再檢查,給使用者足夠時間移動到tooltip上
|
||
hideTooltipTimeout.value = setTimeout(() => {
|
||
if (!isHoveringTooltip.value) {
|
||
hoveredItemIndex.value = null;
|
||
hoveredEquippedSlot.value = null;
|
||
}
|
||
}, 300); // 增加到 300ms
|
||
};
|
||
|
||
const onTooltipMouseEnter = () => {
|
||
// 取消隱藏的timeout
|
||
if (hideTooltipTimeout.value) {
|
||
clearTimeout(hideTooltipTimeout.value);
|
||
hideTooltipTimeout.value = null;
|
||
}
|
||
isHoveringTooltip.value = true;
|
||
};
|
||
|
||
const onTooltipMouseLeave = () => {
|
||
// 滑鼠離開 tooltip,立即隱藏
|
||
isHoveringTooltip.value = false;
|
||
hoveredItemIndex.value = null;
|
||
hoveredEquippedSlot.value = null;
|
||
if (hideTooltipTimeout.value) {
|
||
clearTimeout(hideTooltipTimeout.value);
|
||
hideTooltipTimeout.value = null;
|
||
}
|
||
};
|
||
|
||
// Map data constants to local helpers for template compatibility
|
||
const ItemType = {
|
||
Equipment: ITEM_TYPE.EQUIPMENT,
|
||
Consumable: ITEM_TYPE.CONSUMABLE,
|
||
Talisman: ITEM_TYPE.TALISMAN,
|
||
Special: ITEM_TYPE.SPECIAL,
|
||
Appearance: ITEM_TYPE.APPEARANCE
|
||
};
|
||
|
||
const SLOT_ICONS: Record<string, any> = {
|
||
weapon: Sword,
|
||
armor: Shield,
|
||
hat: Crown,
|
||
accessory: Gem,
|
||
talisman: Sparkles,
|
||
special: Star,
|
||
};
|
||
|
||
const getEquippedItem = (slot: string, isAppearance: boolean) => {
|
||
return props.items.find(i => i.isEquipped && i.slot === slot && !!i.isAppearance === isAppearance);
|
||
};
|
||
|
||
const getInventorySlotItem = (index: number) => {
|
||
const unequippedItems = props.items.filter(i => !i.isEquipped);
|
||
return unequippedItems[index] || null;
|
||
};
|
||
|
||
const getItemTypeName = (type: string) => {
|
||
return ITEM_TYPES[type]?.name || type;
|
||
};
|
||
</script>
|