5c41eb410b
- 新增HUD图标、面板和状态指示器资产 - 实现HUD图标组件和错误边界组件 - 重构顶部状态栏和底部导航栏 - 更新路线面板样式和交互 - 添加HUD资产清单和切片脚本 - 移除未使用的资产文件 - 调整API安全配置和生产环境设置
143 lines
5.2 KiB
TypeScript
143 lines
5.2 KiB
TypeScript
import type { GameAction, GameView } from '@tinywaste/game-core';
|
|
import { useState } from 'react';
|
|
import {
|
|
edgeDestination,
|
|
getCurrentPlace,
|
|
getExpeditionStatus,
|
|
getPlaceAtmosphere,
|
|
getRiskDots,
|
|
getRouteRiskTier,
|
|
getVisibleRoutes,
|
|
} from '../hudModel';
|
|
import { getPlaceThumbStyle } from '../uiAssets';
|
|
import { HudIcon } from './HudIcon';
|
|
|
|
export function RoutePanel({
|
|
isWorking,
|
|
onSendAction,
|
|
view,
|
|
}: {
|
|
isWorking: boolean;
|
|
onSendAction: (action: GameAction) => void;
|
|
view: GameView;
|
|
}) {
|
|
const [safeOnly, setSafeOnly] = useState(false);
|
|
const currentPlace = getCurrentPlace(view);
|
|
const placeId = currentPlace?.id ?? view.place.id;
|
|
const expedition = getExpeditionStatus(view);
|
|
const atmosphere = getPlaceAtmosphere(placeId);
|
|
const allRoutes = getVisibleRoutes(view, 10);
|
|
const routes = safeOnly
|
|
? allRoutes.filter((edge) => !edge.blockedReason && getRouteRiskTier(edge.risk).tone === 'safe')
|
|
: allRoutes.slice(0, 3);
|
|
const launchRoute = routes[0] ?? null;
|
|
|
|
return (
|
|
<section className="panel panel-route expedition-panel hud-shell-panel">
|
|
<header className="expedition-panel-head">
|
|
<div>
|
|
<p className="eyebrow">探索</p>
|
|
<h2>{currentPlace?.name ?? view.header.placeName}</h2>
|
|
</div>
|
|
<span className={`hud-chip ${expedition.tone === 'good' ? 'safe' : expedition.tone}`}>{view.header.riskLabel}</span>
|
|
</header>
|
|
|
|
<section className="expedition-location-card">
|
|
<p className="eyebrow">当前位置</p>
|
|
<strong>{view.header.placeName}</strong>
|
|
<div className="expedition-weather-block">
|
|
<div className="expedition-weather-main">
|
|
<HudIcon name="weather" className="expedition-weather-icon" />
|
|
<span>{atmosphere.weather}</span>
|
|
</div>
|
|
<div className="expedition-weather-meta">
|
|
<span>{atmosphere.temperature}</span>
|
|
<span>{atmosphere.wind}</span>
|
|
</div>
|
|
</div>
|
|
<div className="expedition-risk-line">
|
|
<span>风险等级</span>
|
|
<strong>{view.header.riskLabel}</strong>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="expedition-routes-card">
|
|
<div className="expedition-section-head">
|
|
<span>路线</span>
|
|
<button className={`expedition-toggle ${safeOnly ? 'active' : ''}`} type="button" onClick={() => setSafeOnly((prev) => !prev)}>
|
|
{safeOnly ? '显示全部' : '仅安全路线'}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="expedition-route-stack">
|
|
{routes.map((edge, index) => {
|
|
const destination = edgeDestination(view, edge);
|
|
if (!destination) return null;
|
|
|
|
const riskTier = getRouteRiskTier(edge.risk);
|
|
const riskDots = getRiskDots(edge.risk);
|
|
|
|
return (
|
|
<button
|
|
key={`${edge.from}-${edge.to}`}
|
|
className={`expedition-route-card ${index === 0 ? 'active' : ''} tone-${riskTier.tone}`}
|
|
disabled={Boolean(edge.blockedReason) || isWorking}
|
|
onClick={() => onSendAction({ type: 'travel', toPlaceId: edge.to })}
|
|
type="button"
|
|
>
|
|
<div className="route-card-thumb" style={getPlaceThumbStyle(destination.id)} />
|
|
<div className="route-card-copy">
|
|
<div className="route-card-topline">
|
|
<strong>{destination.name}</strong>
|
|
<span>{destination.visited ? '已探明' : '未知'}</span>
|
|
</div>
|
|
<p>{destination.desc}</p>
|
|
<div className="route-card-detail">
|
|
<span>{edge.travelTimeMin} 分钟</span>
|
|
<span>{riskTier.label}</span>
|
|
</div>
|
|
<div className="risk-dots" aria-hidden="true">
|
|
{riskDots.map((active, dotIndex) => (
|
|
<span key={`${destination.id}-${dotIndex}`} className={active ? 'active' : ''} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="expedition-status-card">
|
|
<div className="expedition-section-head">
|
|
<span>探索状态</span>
|
|
</div>
|
|
<div className="expedition-status-grid">
|
|
<article className="expedition-status-cell">
|
|
<small>装备</small>
|
|
<strong>{expedition.readiness}%</strong>
|
|
</article>
|
|
<article className="expedition-status-cell">
|
|
<small>状态</small>
|
|
<strong>{Math.round((view.player.stats.life / view.player.maxStats.life) * 100)}%</strong>
|
|
</article>
|
|
<article className="expedition-status-cell">
|
|
<small>负重</small>
|
|
<strong>
|
|
{view.player.inventoryUsage}/{view.player.inventoryCapacity}
|
|
</strong>
|
|
</article>
|
|
</div>
|
|
<button
|
|
className="primary-button expedition-launch-button"
|
|
disabled={!launchRoute || isWorking}
|
|
onClick={() => launchRoute && onSendAction({ type: 'travel', toPlaceId: launchRoute.to })}
|
|
type="button"
|
|
>
|
|
准备出发
|
|
</button>
|
|
</section>
|
|
</section>
|
|
);
|
|
}
|