Files
tianpu-ems/frontend/src/pages/BigScreen/components/AnimatedNumber.tsx
Du Wenbo 6a59f9af76 feat: add 3D interactive dashboard and 2D BigScreen pages
- New /bigscreen-3d route: React Three Fiber 3D campus with buildings,
  PV panels, heat pumps, meters, and sensors — all procedural geometry
- Interactive: hover highlight, click to select, camera fly-in to
  device detail views (PV inverter, heat pump, meter, heat meter, sensor)
- Real-time data: 15s polling for overview, 5s for selected device
- Energy flow particles along PV→Building, Grid→Building, Building→HP paths
- HUD overlay with date/clock, bottom metrics bar, device list panel
- New /bigscreen route: 2D dashboard with energy flow diagram, charts
- New /devices route: device management page
- Vite config: optimizeDeps.force for R3F dep consistency
- Data backfill script for testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:43:48 +08:00

39 lines
1.2 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
interface Props {
value: number;
duration?: number;
decimals?: number;
className?: string;
}
export default function AnimatedNumber({ value, duration = 1500, decimals = 0, className }: Props) {
const [display, setDisplay] = useState(0);
const rafRef = useRef<number>(0);
const startRef = useRef<number>(0);
const fromRef = useRef<number>(0);
useEffect(() => {
fromRef.current = display;
startRef.current = performance.now();
const animate = (now: number) => {
const elapsed = now - startRef.current;
const progress = Math.min(elapsed / duration, 1);
// ease-out cubic
const eased = 1 - Math.pow(1 - progress, 3);
const current = fromRef.current + (value - fromRef.current) * eased;
setDisplay(current);
if (progress < 1) {
rafRef.current = requestAnimationFrame(animate);
}
};
rafRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(rafRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value, duration]);
return <span className={className}>{display.toFixed(decimals)}</span>;
}