import { useEffect, useRef, useState } from "react"; const DIMS = [ { name: "钩子设计", score: 19, max: 20, hue: 145 }, { name: "角色塑造", score: 13, max: 15, hue: 155 }, { name: "剧情结构", score: 18, max: 20, hue: 165 }, { name: "逻辑严密", score: 14, max: 15, hue: 175 }, { name: "场景构建", score: 15, max: 15, hue: 185 }, { name: "内容深度", score: 15, max: 15, hue: 195 }, ]; function ScriptReviewVisual() { const [animated, setAnimated] = useState(false); const [activeDim, setActiveDim] = useState(null); const [score, setScore] = useState(0); const scoreRef = useRef(0); const frameRef = useRef(null); useEffect(() => { const el = document.getElementById("script-review-visual"); if (!el) return; const observer = new IntersectionObserver( ([entry]) => { if (entry?.isIntersecting) { setAnimated(true); observer.disconnect(); } }, { threshold: 0.3 } ); observer.observe(el); return () => observer.disconnect(); }, []); useEffect(() => { if (!animated) return; const start = performance.now(); const target = 94; const dur = 1400; function tick(now: number) { const t = Math.min((now - start) / dur, 1); const e = 1 - Math.pow(1 - t, 3); setScore(Math.round(e * target)); if (t < 1) frameRef.current = requestAnimationFrame(tick); } frameRef.current = requestAnimationFrame(tick); return () => { if (frameRef.current) cancelAnimationFrame(frameRef.current); }; }, [animated]); const totalScore = 94; const grade = "S"; return (
{score} / 100
{grade}级
击败全国 92% 剧本
{DIMS.map((dim, i) => { const pct = dim.score / dim.max; const lossPct = (dim.max - dim.score) / dim.max; const isPerfect = dim.score === dim.max; const height = animated ? pct * 76 : 0; const lossHeight = animated ? lossPct * 76 : 0; return (
setActiveDim(activeDim === i ? null : i)} >
{lossPct > 0 && (
)}
{dim.name}
); })}
{activeDim !== null && (() => { const d = DIMS[activeDim]!; return (
{d.name} {d.score}/{d.max} {d.score === d.max && " ★"}
); })()}
得分 扣分
); } export default ScriptReviewVisual;