新增連勝與獲勝特效並更新 README

This commit is contained in:
2026-04-16 16:54:59 +08:00
parent b3809b5d4f
commit 860e7adc0e
4 changed files with 282 additions and 0 deletions

View File

@@ -57,6 +57,29 @@ type SettlementState = {
uploading: boolean
}
type StreakAnnouncement = {
count: number
key: number
teamName: string
title: string
}
type VictoryAnnouncement = {
key: number
scoreLabel: string
teamName: string
title: string
}
const STREAK_TITLES: Record<number, string> = {
3: '大殺特殺',
4: '暴走',
5: '無人能擋',
6: '主宰比賽',
7: '像神一般的',
8: '成為傳說',
}
function App() {
const location = useLocation()
const isScoreboardRoute = location.pathname === '/scoreboard'
@@ -90,6 +113,8 @@ function App() {
open: false,
uploading: false,
})
const [streakAnnouncement, setStreakAnnouncement] = useState<StreakAnnouncement | null>(null)
const [victoryAnnouncement, setVictoryAnnouncement] = useState<VictoryAnnouncement | null>(null)
const parsedAreaA = useMemo(() => parseRoster(areaAInput), [areaAInput])
const parsedAreaB = useMemo(() => parseRoster(areaBInput), [areaBInput])
@@ -125,10 +150,36 @@ function App() {
return () => window.clearTimeout(timer)
}, [loadMessage, loadStatus])
useEffect(() => {
if (!streakAnnouncement) {
return
}
const timer = window.setTimeout(() => {
setStreakAnnouncement(null)
}, 1800)
return () => window.clearTimeout(timer)
}, [streakAnnouncement])
useEffect(() => {
if (!victoryAnnouncement) {
return
}
const timer = window.setTimeout(() => {
setVictoryAnnouncement(null)
}, 2200)
return () => window.clearTimeout(timer)
}, [victoryAnnouncement])
const resetScoring = (nextState: ScoreState = initialScoreState) => {
setScoreState(nextState)
setScoreHistory([])
setPointLog([])
setStreakAnnouncement(null)
setVictoryAnnouncement(null)
setSettlement({
error: '',
open: false,
@@ -294,6 +345,8 @@ function App() {
const winner: 0 | 1 = side === 'left' ? 0 : 1
const previousPoint = pointLog.at(-1)
const winCount = previousPoint?.winner === winner ? previousPoint.winCount + 1 : 0
const streakCount = winCount + 1
const streakTitle = STREAK_TITLES[streakCount]
const nextPointLog = [
...pointLog,
@@ -323,6 +376,28 @@ function App() {
setScoreHistory((current) => [...current, { pointLog, scoreState }])
setPointLog(nextPointLog)
setScoreState(nextScoreState)
if (streakTitle) {
setStreakAnnouncement({
count: streakCount,
key: Date.now(),
teamName: side === 'left' ? getTeamDisplayName(leftTeam) : getTeamDisplayName(rightTeam),
title: streakTitle,
})
}
const reachedTarget =
nextScoreState.scoreLeft >= nextScoreState.targetScore ||
nextScoreState.scoreRight >= nextScoreState.targetScore
if (reachedTarget) {
setVictoryAnnouncement({
key: Date.now() + 1,
scoreLabel: `${nextScoreState.scoreLeft} : ${nextScoreState.scoreRight}`,
teamName: side === 'left' ? getTeamDisplayName(leftTeam) : getTeamDisplayName(rightTeam),
title: '拿下勝利',
})
}
}
const undoLastPoint = () => {
@@ -335,6 +410,8 @@ function App() {
setScoreHistory((current) => current.slice(0, -1))
setPointLog(previous.pointLog)
setScoreState(previous.scoreState)
setStreakAnnouncement(null)
setVictoryAnnouncement(null)
}
const openSettlementDialog = () => {
@@ -506,6 +583,8 @@ function App() {
rightTeam={rightTeam}
scoreState={scoreState}
selectedGroup={selectedGroup}
streakAnnouncement={streakAnnouncement}
victoryAnnouncement={victoryAnnouncement}
targetDate={targetDate}
onApplyMatchup={applyMatchup}
onCloseFinishDialog={closeSettlementDialog}