'use client'; import { useEffect, useRef, useState, useMemo, useLayoutEffect } from 'react'; import { PinIcon, PinOffIcon, EyeIcon, EyeOffIcon, SwitchIcon } from './Icons'; // 数字滚动组件(初始化时无动画,后续变更再动画) function CountUp({ value, prefix = '', suffix = '', decimals = 2, className = '', style = {} }) { const [displayValue, setDisplayValue] = useState(value); const previousValue = useRef(value); const isFirstChange = useRef(true); useEffect(() => { if (previousValue.current === value) return; if (isFirstChange.current) { isFirstChange.current = false; previousValue.current = value; setDisplayValue(value); return; } const start = previousValue.current; const end = value; const duration = 400; const startTime = performance.now(); const animate = (currentTime) => { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); const ease = 1 - Math.pow(1 - progress, 4); const current = start + (end - start) * ease; setDisplayValue(current); if (progress < 1) { requestAnimationFrame(animate); } else { previousValue.current = value; } }; requestAnimationFrame(animate); }, [value]); return ( {prefix} {Math.abs(displayValue).toFixed(decimals)} {suffix} ); } export default function GroupSummary({ funds, holdings, groupName, getProfit, stickyTop, masked, onToggleMasked, }) { const [showPercent, setShowPercent] = useState(true); const [isMasked, setIsMasked] = useState(masked ?? false); const [isSticky, setIsSticky] = useState(false); const rowRef = useRef(null); const [assetSize, setAssetSize] = useState(24); const [metricSize, setMetricSize] = useState(18); const [winW, setWinW] = useState(0); useEffect(() => { if (typeof window !== 'undefined') { setWinW(window.innerWidth); const onR = () => setWinW(window.innerWidth); window.addEventListener('resize', onR); return () => window.removeEventListener('resize', onR); } }, []); useEffect(() => { if (typeof masked === 'boolean') { setIsMasked(masked); } }, [masked]); const summary = useMemo(() => { let totalAsset = 0; let totalProfitToday = 0; let totalHoldingReturn = 0; let totalCost = 0; let hasHolding = false; let hasAnyTodayData = false; funds.forEach((fund) => { const holding = holdings[fund.code]; const profit = getProfit(fund, holding); if (profit) { hasHolding = true; totalAsset += profit.amount; if (profit.profitToday != null) { totalProfitToday += Math.round(profit.profitToday * 100) / 100; hasAnyTodayData = true; } if (profit.profitTotal !== null) { totalHoldingReturn += profit.profitTotal; if (holding && typeof holding.cost === 'number' && typeof holding.share === 'number') { totalCost += holding.cost * holding.share; } } } }); const returnRate = totalCost > 0 ? (totalHoldingReturn / totalCost) * 100 : 0; return { totalAsset, totalProfitToday, totalHoldingReturn, hasHolding, returnRate, hasAnyTodayData, }; }, [funds, holdings, getProfit]); useLayoutEffect(() => { const el = rowRef.current; if (!el) return; const height = el.clientHeight; const tooTall = height > 80; if (tooTall) { setAssetSize((s) => Math.max(16, s - 1)); setMetricSize((s) => Math.max(12, s - 1)); } }, [ winW, summary.totalAsset, summary.totalProfitToday, summary.totalHoldingReturn, summary.returnRate, showPercent, assetSize, metricSize, ]); if (!summary.hasHolding) return null; return (
setIsSticky(!isSticky)} style={{ position: 'absolute', top: 4, right: 4, width: 24, height: 24, padding: 4, opacity: 0.6, zIndex: 10, color: 'var(--muted)', }} > {isSticky ? ( ) : ( )}
{groupName}
¥ {isMasked ? ( ****** ) : ( )}
当日收益
0 ? 'up' : summary.totalProfitToday < 0 ? 'down' : '' : 'muted' } style={{ fontSize: '18px', fontWeight: 700, fontFamily: 'var(--font-mono)', }} > {isMasked ? ( ****** ) : summary.hasAnyTodayData ? ( <> {summary.totalProfitToday > 0 ? '+' : summary.totalProfitToday < 0 ? '-' : ''} ) : ( -- )}
持有收益{showPercent ? '(%)' : ''}{' '}
0 ? 'up' : summary.totalHoldingReturn < 0 ? 'down' : '' } style={{ fontSize: '18px', fontWeight: 700, fontFamily: 'var(--font-mono)', cursor: 'pointer', }} onClick={() => setShowPercent(!showPercent)} title="点击切换金额/百分比" > {isMasked ? ( ****** ) : ( <> {summary.totalHoldingReturn > 0 ? '+' : summary.totalHoldingReturn < 0 ? '-' : ''} {showPercent ? ( ) : ( )} )}
); }