功能:首頁日期預設當天、記分板語音改為報比分與發球區
摘要: - 首頁「指定日期」每次進入都預設為當天 - 記分板語音得分播報改為「比分(發球方先)+ 發球者左右發球區」,賽末點獲勝時整段改播「贏得比賽」 根本原因: - 日期原本從 localStorage 讀上次選的值,重開會停在舊日期 - 語音原本只唸「誰得分、誰發球」,現場較不直覺,球敘想聽到即時比分與發球位置 影響: - src/App.tsx:targetDate 改為直接用當天、移除日期的 localStorage 記憶;recordPoint 算出發球方比分與發球區,重整 voiceAnnouncement 欄位 - src/pages/ScoreboardPage.tsx:語音組字改為「X比Y」「OO左/右邊發球」,獲勝時改播「贏得比賽」;語音設定「播報誰得分」更名「播報比分」 修法: - 發球方分數先報;上方(左側)隊伍發球區做左右鏡像以對齊畫面,下方隊伍不鏡像 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-14
@@ -45,7 +45,6 @@ const STORAGE_KEYS = {
|
||||
areaA: 'badminton-scoreboard::area-a',
|
||||
areaB: 'badminton-scoreboard::area-b',
|
||||
history: 'badminton-scoreboard::history',
|
||||
targetDate: 'badminton-scoreboard::target-date',
|
||||
} as const
|
||||
|
||||
const defaultAreaA = ['柏威', '建喵', 'Yuki', '阿釧']
|
||||
@@ -86,9 +85,11 @@ type VictoryAnnouncement = {
|
||||
|
||||
type VoiceAnnouncement = {
|
||||
key: number
|
||||
scorerName: string
|
||||
serverChanged: boolean
|
||||
servingScore: number
|
||||
opponentScore: number
|
||||
serverName: string
|
||||
serverCourt: 'left' | 'right'
|
||||
winnerTeamName: string | null
|
||||
}
|
||||
|
||||
const STREAK_TITLES: Record<number, string> = {
|
||||
@@ -108,9 +109,7 @@ function App() {
|
||||
const navigate = useNavigate()
|
||||
const isScoreboardRoute = location.pathname === '/scoreboard'
|
||||
|
||||
const [targetDate, setTargetDate] = useState(() =>
|
||||
loadStoredText(STORAGE_KEYS.targetDate, formatDateInputValue()),
|
||||
)
|
||||
const [targetDate, setTargetDate] = useState(() => formatDateInputValue())
|
||||
const [areaAInput, setAreaAInput] = useState(() =>
|
||||
loadStoredText(STORAGE_KEYS.areaA, defaultAreaA.join('\n')),
|
||||
)
|
||||
@@ -155,10 +154,6 @@ function App() {
|
||||
const liveRoomId = liveRoomSession?.roomId ?? null
|
||||
const isNavigationLocked = Boolean(leftTeam && rightTeam && scoreState.serving !== null)
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem(STORAGE_KEYS.targetDate, targetDate)
|
||||
}, [targetDate])
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem(STORAGE_KEYS.areaA, areaAInput)
|
||||
}, [areaAInput])
|
||||
@@ -771,14 +766,31 @@ function App() {
|
||||
: scoreState.rightRightCourtPlayer,
|
||||
}
|
||||
|
||||
const reachedTarget = hasWonGame(nextScoreState)
|
||||
const winnerTeamName = reachedTarget
|
||||
? side === 'left'
|
||||
? getTeamDisplayName(leftTeam)
|
||||
: getTeamDisplayName(rightTeam)
|
||||
: null
|
||||
|
||||
// 得分方接著發球,報分以發球方分數為先;左側隊伍的發球區需鏡像對應畫面。
|
||||
const servingScore = side === 'left' ? nextScoreState.scoreLeft : nextScoreState.scoreRight
|
||||
const opponentScore = side === 'left' ? nextScoreState.scoreRight : nextScoreState.scoreLeft
|
||||
const serverCourt =
|
||||
side === 'left'
|
||||
? getMirroredCourt(getServiceCourt(servingScore))
|
||||
: getServiceCourt(servingScore)
|
||||
|
||||
setScoreHistory((current) => [...current, { pointLog, scoreState }])
|
||||
setPointLog(nextPointLog)
|
||||
setScoreState(nextScoreState)
|
||||
setVoiceAnnouncement({
|
||||
key: Date.now(),
|
||||
scorerName: side === 'left' ? leftTeam.playerA : rightTeam.playerA,
|
||||
serverChanged: side === scoreState.serving,
|
||||
servingScore,
|
||||
opponentScore,
|
||||
serverName: getNextServerName(nextScoreState, leftTeam, rightTeam, side),
|
||||
serverCourt,
|
||||
winnerTeamName,
|
||||
})
|
||||
|
||||
if (streakTitle) {
|
||||
@@ -790,8 +802,6 @@ function App() {
|
||||
})
|
||||
}
|
||||
|
||||
const reachedTarget = hasWonGame(nextScoreState)
|
||||
|
||||
if (reachedTarget) {
|
||||
setVictoryAnnouncement({
|
||||
key: Date.now() + 1,
|
||||
|
||||
@@ -60,9 +60,11 @@ type ScoreboardPageProps = {
|
||||
} | null
|
||||
voiceAnnouncement: {
|
||||
key: number
|
||||
scorerName: string
|
||||
serverChanged: boolean
|
||||
servingScore: number
|
||||
opponentScore: number
|
||||
serverName: string
|
||||
serverCourt: 'left' | 'right'
|
||||
winnerTeamName: string | null
|
||||
} | null
|
||||
targetDate: string
|
||||
onApplyMatchup: (
|
||||
@@ -245,17 +247,27 @@ export function ScoreboardPage({
|
||||
return
|
||||
}
|
||||
|
||||
if (voiceAnnouncement.winnerTeamName) {
|
||||
const winnerSpeech = voiceAnnouncement.winnerTeamName
|
||||
.split('/')
|
||||
.map((name) => getSpeechName(name.trim()))
|
||||
.filter(Boolean)
|
||||
.join('、')
|
||||
speakAnnouncement(`${winnerSpeech}贏得比賽`, voiceSettings.rate)
|
||||
return
|
||||
}
|
||||
|
||||
const parts: string[] = []
|
||||
|
||||
if (voiceSettings.announceScore) {
|
||||
parts.push(`${getSpeechName(voiceAnnouncement.scorerName)}得分`)
|
||||
parts.push(`${voiceAnnouncement.servingScore}比${voiceAnnouncement.opponentScore}`)
|
||||
}
|
||||
|
||||
if (voiceSettings.announceServer && voiceAnnouncement.serverName) {
|
||||
parts.push(
|
||||
`${getSpeechName(voiceAnnouncement.serverName)}${
|
||||
voiceAnnouncement.serverChanged ? '換邊發球' : '發球'
|
||||
}`,
|
||||
voiceAnnouncement.serverCourt === 'left' ? '左邊' : '右邊'
|
||||
}發球`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -983,7 +995,7 @@ function VoiceSettingsModal({
|
||||
<h3>播報內容</h3>
|
||||
|
||||
<label className="voice-setting-row">
|
||||
<span>播報誰得分</span>
|
||||
<span>播報比分</span>
|
||||
<input
|
||||
checked={settings.announceScore}
|
||||
type="checkbox"
|
||||
|
||||
Reference in New Issue
Block a user