Files
text-adventure-game/components/drawers/InventoryDrawer.vue
Claude 16223c89a5 feat: 实现游戏核心系统和UI组件
核心系统:
- combatSystem: 战斗逻辑、伤害计算、战斗状态管理
- skillSystem: 技能系统、技能解锁、经验值、里程碑
- taskSystem: 任务系统、任务类型、任务执行和完成
- eventSystem: 事件系统、随机事件处理
- environmentSystem: 环境系统、时间流逝、区域效果
- levelingSystem: 升级系统、属性成长
- soundSystem: 音效系统

配置文件:
- enemies: 敌人配置、掉落表
- events: 事件配置、事件效果
- items: 物品配置、装备属性
- locations: 地点配置、探索事件
- skills: 技能配置、技能树

UI组件:
- CraftingDrawer: 制造界面
- InventoryDrawer: 背包界面
- 其他UI优化和动画

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:20:10 +08:00

340 lines
7.6 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="inventory-drawer">
<view class="drawer-header">
<text class="drawer-title">📦 背包</text>
<text class="drawer-close" @click="close">×</text>
</view>
<FilterTabs
:tabs="filterTabs"
:modelValue="currentFilter"
@update:modelValue="currentFilter = $event"
/>
<scroll-view class="inventory-list" scroll-y>
<view
v-for="item in filteredItems"
:key="item.uniqueId || item.id"
class="inventory-item"
:class="{ 'inventory-item--equipped': isEquipped(item) }"
@click="selectItem(item)"
>
<text class="inventory-item__icon">{{ item.icon || '📦' }}</text>
<view class="inventory-item__info">
<text class="inventory-item__name" :style="{ color: item.qualityColor || '#fff' }">
{{ item.name }}
<text v-if="item.qualityName" class="quality-badge"> [{{ item.qualityName }}]</text>
</text>
<text v-if="item.count > 1" class="inventory-item__count">x{{ item.count }}</text>
</view>
<text v-if="isEquipped(item)" class="inventory-item__equipped-badge">E</text>
</view>
<view v-if="filteredItems.length === 0" class="inventory-empty">
<text class="inventory-empty__text">背包空空如也</text>
</view>
</scroll-view>
<!-- 物品详情 -->
<view v-if="selectedItem" class="item-detail">
<view class="item-detail__header">
<text class="item-detail__name">{{ selectedItem.name }}</text>
<text class="item-detail__close" @click="selectedItem = null">×</text>
</view>
<text class="item-detail__desc">{{ selectedItem.description }}</text>
<text v-if="isEquipped(selectedItem)" class="item-detail__equipped"> 已装备</text>
<view class="item-detail__actions">
<TextButton
v-if="canEquip && !isEquipped(selectedItem)"
text="装备"
type="primary"
@click="equipItem"
/>
<TextButton
v-if="canEquip && isEquipped(selectedItem)"
text="卸下"
type="warning"
@click="unequipItem"
/>
<TextButton
v-if="canUse"
text="使用"
@click="useItem"
/>
<TextButton
text="出售"
type="default"
@click="sellItem"
/>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { usePlayerStore } from '@/store/player'
import { useGameStore } from '@/store/game'
import { equipItem as equipItemUtil, unequipItemBySlot, useItem as useItemUtil } from '@/utils/itemSystem.js'
import FilterTabs from '@/components/common/FilterTabs.vue'
import TextButton from '@/components/common/TextButton.vue'
const player = usePlayerStore()
const game = useGameStore()
const currentFilter = ref('all')
const selectedItem = ref(null)
const emit = defineEmits(['close'])
const filterTabs = [
{ id: 'all', label: '全部' },
{ id: 'weapon', label: '武器' },
{ id: 'armor', label: '防具' },
{ id: 'consumable', label: '消耗品' },
{ id: 'material', label: '素材' }
]
const filteredItems = computed(() => {
const items = player.inventory || []
if (currentFilter.value === 'all') {
return items
}
return items.filter(item => item.type === currentFilter.value)
})
const canEquip = computed(() => {
if (!selectedItem.value) return false
return ['weapon', 'armor', 'shield', 'accessory'].includes(selectedItem.value.type)
})
const canUse = computed(() => {
if (!selectedItem.value) return false
return selectedItem.value.type === 'consumable'
})
// 装备槽位映射
const slotMap = {
weapon: 'weapon',
armor: 'armor',
shield: 'shield',
accessory: 'accessory'
}
// 检查物品是否已装备
function isEquipped(item) {
if (!item || !item.type) return false
const slot = slotMap[item.type]
if (!slot) return false
const equipped = player.equipment[slot]
// 通过 uniqueId 或 id 比较
return equipped && (equipped.uniqueId === item.uniqueId || equipped.id === item.id)
}
function close() {
emit('close')
}
function selectItem(item) {
selectedItem.value = item
}
function equipItem() {
if (!selectedItem.value) return
const result = equipItemUtil(player, game, selectedItem.value.uniqueId)
if (result.success) {
selectedItem.value = null
}
}
function unequipItem() {
if (!selectedItem.value) return
const item = selectedItem.value
const slot = slotMap[item.type]
if (!slot) return
const result = unequipItemBySlot(player, game, slot)
if (result.success) {
selectedItem.value = null
}
}
function useItem() {
if (!selectedItem.value) return
const result = useItemUtil(player, game, selectedItem.value.id, 1)
if (result.success) {
// 如果阅读任务,可以在这里处理
selectedItem.value = null
}
}
function sellItem() {
if (!selectedItem.value) return
// 已装备的物品不能出售
if (isEquipped(selectedItem.value)) {
game.addLog('已装备的物品需要先卸下才能出售', 'error')
return
}
game.addLog(`出售了 ${selectedItem.value.name}`, 'info')
// 出售逻辑待完善
selectedItem.value = null
}
</script>
<style lang="scss" scoped>
.inventory-drawer {
height: 100%;
display: flex;
flex-direction: column;
background-color: $bg-secondary;
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid $border-color;
}
.drawer-title {
color: $text-primary;
font-size: 32rpx;
font-weight: bold;
}
.drawer-close {
color: $text-secondary;
font-size: 48rpx;
padding: 0 16rpx;
&:active {
color: $text-primary;
}
}
.inventory-list {
flex: 1;
padding: 16rpx;
}
.inventory-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx;
background-color: $bg-primary;
border-radius: 8rpx;
margin-bottom: 8rpx;
&:active {
background-color: $bg-tertiary;
}
&--equipped {
background-color: rgba($accent, 0.15);
border: 1rpx solid $accent;
}
&__icon {
font-size: 40rpx;
}
&__info {
flex: 1;
}
&__name {
display: block;
color: $text-primary;
font-size: 28rpx;
}
&__count {
display: block;
color: $text-secondary;
font-size: 22rpx;
margin-top: 4rpx;
}
}
.quality-badge {
font-size: 18rpx;
opacity: 0.8;
margin-left: 8rpx;
}
&__equipped-badge {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: $accent;
color: $bg-primary;
font-size: 18rpx;
font-weight: bold;
border-radius: 50%;
}
}
.inventory-empty {
padding: 80rpx;
text-align: center;
&__text {
color: $text-muted;
font-size: 28rpx;
}
}
.item-detail {
padding: 24rpx;
background-color: $bg-primary;
border-top: 1rpx solid $border-color;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
&__name {
color: $text-primary;
font-size: 30rpx;
font-weight: bold;
}
&__close {
color: $text-secondary;
font-size: 40rpx;
padding: 0 16rpx;
}
&__desc {
display: block;
color: $text-secondary;
font-size: 24rpx;
line-height: 1.6;
margin-bottom: 16rpx;
}
&__equipped {
display: inline-block;
padding: 4rpx 12rpx;
background-color: rgba($accent, 0.2);
color: $accent;
font-size: 22rpx;
border-radius: 4rpx;
margin-bottom: 12rpx;
}
&__actions {
display: flex;
gap: 12rpx;
flex-wrap: wrap;
}
}
</style>