This commit is contained in:
2026-04-28 19:36:39 +08:00
parent a9febe7678
commit f0471a4f8d
206 changed files with 18456 additions and 10133 deletions

View File

@@ -1,7 +1,11 @@
import { useMemo, useState } from 'react';
import type {
RuntimeStoryEquipmentSlotView,
RuntimeStoryForgeRecipeView,
RuntimeStoryInventoryItemView,
} from '../../packages/shared/src/contracts/rpgRuntimeStoryState';
import { formatCurrency } from '../data/economy';
import { type ForgeRecipeView } from '../data/forgeSystem';
import { buildInitialPlayerInventory } from '../data/npcInteractions';
import {
Character,
@@ -25,9 +29,12 @@ interface InventoryPanelProps {
playerMana: number;
playerMaxMana: number;
inBattle: boolean;
currencyText?: string | null;
backpackItems?: RuntimeStoryInventoryItemView[];
equipmentSlots?: RuntimeStoryEquipmentSlotView[];
onUseItem: (itemId: string) => Promise<boolean>;
onEquipItem: (itemId: string) => Promise<boolean>;
forgeRecipes: ForgeRecipeView[];
forgeRecipes: RuntimeStoryForgeRecipeView[];
onCraftRecipe: (recipeId: string) => Promise<boolean>;
onDismantleItem: (itemId: string) => Promise<boolean>;
onReforgeItem: (itemId: string) => Promise<boolean>;
@@ -42,8 +49,15 @@ export function InventoryPanel({
playerInventory,
playerCurrency,
inBattle,
currencyText = null,
backpackItems = [],
equipmentSlots: _equipmentSlots = [],
onUseItem: _onUseItem,
onEquipItem: _onEquipItem,
forgeRecipes,
onCraftRecipe,
onDismantleItem: _onDismantleItem,
onReforgeItem: _onReforgeItem,
continueGameDigest = null,
narrativeCodex = [],
narrativeQaReport = null,
@@ -51,12 +65,24 @@ export function InventoryPanel({
const [selectedItem, setSelectedItem] = useState<InventoryItem | null>(null);
const [forgeActionKey, setForgeActionKey] = useState<string | null>(null);
const serverInventoryItems = useMemo(
() =>
backpackItems
.map((view) => view.item as unknown as InventoryItem)
.filter(
(item) =>
typeof item.id === 'string' && typeof item.name === 'string',
),
[backpackItems],
);
const inventoryItems = useMemo(
() =>
playerInventory.length > 0
? playerInventory
: buildInitialPlayerInventory(playerCharacter, worldType),
[playerCharacter, playerInventory, worldType],
serverInventoryItems.length > 0
? serverInventoryItems
: playerInventory.length > 0
? playerInventory
: buildInitialPlayerInventory(playerCharacter, worldType),
[playerCharacter, playerInventory, serverInventoryItems, worldType],
);
const documentItems = useMemo(
() => inventoryItems.filter((item) => item.category === '文书' || item.tags.includes('document')),
@@ -141,7 +167,7 @@ export function InventoryPanel({
<div className="mb-2 flex items-center justify-between gap-3 text-xs uppercase tracking-[0.2em] text-zinc-500">
<span></span>
<span className="text-emerald-200/80">
{formatCurrency(playerCurrency, worldType)}
{currencyText ?? formatCurrency(playerCurrency, worldType)}
</span>
</div>
<div className="space-y-3">
@@ -169,6 +195,7 @@ export function InventoryPanel({
type="button"
disabled={
!recipe.canCraft ||
!recipe.action.enabled ||
inBattle ||
forgeActionKey === recipe.id
}
@@ -181,7 +208,7 @@ export function InventoryPanel({
}
}}
className={`rounded-lg border px-3 py-1.5 text-xs transition ${
recipe.canCraft && !inBattle
recipe.canCraft && recipe.action.enabled && !inBattle
? 'border-emerald-400/30 bg-emerald-500/10 text-emerald-100 hover:bg-emerald-500/20'
: 'border-white/8 bg-black/20 text-zinc-500'
}`}
@@ -208,6 +235,12 @@ export function InventoryPanel({
</span>
))}
</div>
{(!recipe.canCraft || !recipe.action.enabled) &&
(recipe.disabledReason || recipe.action.reason) && (
<div className="mt-2 text-[11px] text-zinc-500">
{recipe.disabledReason ?? recipe.action.reason}
</div>
)}
</div>
))}
</div>