ems-core v1.0.0: Standard EMS platform core
Shared backend + frontend for multi-customer EMS deployments. - 12 enterprise modules: quota, cost, charging, maintenance, analysis, etc. - 120+ API endpoints, 37 database tables - Customer config mechanism (CUSTOMER env var + YAML config) - Collectors: Modbus TCP, MQTT, HTTP API, Sungrow iSolarCloud - Frontend: React 19 + Ant Design + ECharts + Three.js - Infrastructure: Redis cache, rate limiting, aggregation engine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
107
frontend/src/pages/BigScreen3D/components/PVPanels.tsx
Normal file
107
frontend/src/pages/BigScreen3D/components/PVPanels.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { useRef, useMemo } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { DEVICE_POSITIONS, PV_ARRAY, COLORS } from '../constants';
|
||||
|
||||
interface PVPanelsProps {
|
||||
devices: Array<{ id: number; code: string; status: string; power?: number; rated_power?: number }>;
|
||||
hoveredId: number | null;
|
||||
onHover: (id: number | null) => void;
|
||||
onClick: (device: { id: number; code: string; status: string; power?: number; rated_power?: number }) => void;
|
||||
detailMode?: boolean;
|
||||
}
|
||||
|
||||
const PV_ZONES = [
|
||||
{ code: 'PV-INV-01', center: DEVICE_POSITIONS['PV-INV-01'].position },
|
||||
{ code: 'PV-INV-02', center: DEVICE_POSITIONS['PV-INV-02'].position },
|
||||
{ code: 'PV-INV-03', center: DEVICE_POSITIONS['PV-INV-03'].position },
|
||||
] as const;
|
||||
|
||||
function PVZone({
|
||||
center,
|
||||
device,
|
||||
isHovered,
|
||||
onHover,
|
||||
onClick,
|
||||
}: {
|
||||
center: readonly [number, number, number];
|
||||
device: { id: number; code: string; status: string; power?: number; rated_power?: number } | undefined;
|
||||
isHovered: boolean;
|
||||
onHover: (id: number | null) => void;
|
||||
onClick: (device: { id: number; code: string; status: string; power?: number; rated_power?: number }) => void;
|
||||
}) {
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
|
||||
const panels = useMemo(() => {
|
||||
const items: { pos: [number, number, number] }[] = [];
|
||||
const { cols, rows, panelWidth, panelHeight, gap } = PV_ARRAY;
|
||||
const totalW = cols * panelWidth + (cols - 1) * gap;
|
||||
const totalD = rows * panelHeight + (rows - 1) * gap;
|
||||
|
||||
for (let r = 0; r < rows; r++) {
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const x = -totalW / 2 + panelWidth / 2 + c * (panelWidth + gap);
|
||||
const z = -totalD / 2 + panelHeight / 2 + r * (panelHeight + gap);
|
||||
items.push({ pos: [x, 0, z] });
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}, []);
|
||||
|
||||
const ratio = device && device.rated_power ? (device.power ?? 0) / device.rated_power : 0;
|
||||
const emissiveIntensity = Math.min(ratio * 0.5, 0.5);
|
||||
|
||||
return (
|
||||
<group
|
||||
ref={groupRef}
|
||||
position={[center[0], center[1], center[2]]}
|
||||
onPointerOver={(e) => { e.stopPropagation(); if (device) onHover(device.id); }}
|
||||
onPointerOut={(e) => { e.stopPropagation(); onHover(null); }}
|
||||
onClick={(e) => { e.stopPropagation(); if (device) onClick(device); }}
|
||||
>
|
||||
{panels.map((p, i) => (
|
||||
<mesh
|
||||
key={i}
|
||||
position={p.pos}
|
||||
rotation={[-PV_ARRAY.tiltAngle, 0, 0]}
|
||||
castShadow
|
||||
>
|
||||
<boxGeometry args={[PV_ARRAY.panelWidth, PV_ARRAY.panelDepth, PV_ARRAY.panelHeight]} />
|
||||
<meshStandardMaterial
|
||||
color={isHovered ? '#2a347e' : '#1a237e'}
|
||||
metalness={0.8}
|
||||
roughness={0.3}
|
||||
emissive={COLORS.pvGreen}
|
||||
emissiveIntensity={isHovered ? 0.5 : emissiveIntensity}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PVPanels({ devices, hoveredId, onHover, onClick }: PVPanelsProps) {
|
||||
const deviceMap = useMemo(() => {
|
||||
const map = new Map<string, (typeof devices)[number]>();
|
||||
devices.forEach((d) => map.set(d.code, d));
|
||||
return map;
|
||||
}, [devices]);
|
||||
|
||||
return (
|
||||
<group>
|
||||
{PV_ZONES.map((zone) => {
|
||||
const device = deviceMap.get(zone.code);
|
||||
return (
|
||||
<PVZone
|
||||
key={zone.code}
|
||||
center={zone.center}
|
||||
device={device}
|
||||
isHovered={device ? hoveredId === device.id : false}
|
||||
onHover={onHover}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user