功能:新增更新紀錄分頁、勝利後鎖記分與結算後自動選隊、報分語音改唸個位數
摘要: - 新增「更新紀錄」分頁,依 git commit 自動產生日誌(scripts/generate-changelog.mjs → src/data/changelog.json) - 比賽分出勝負後鎖住記分按鈕,需先結算才能開下一場 - 結算(上傳或不上傳)完成後自動打開選隊伍面板並清空已選,方便直接排下一場 - 得分播報只唸個位數(10→0、19→9、20→0),同分改唸「N 平」 根本原因: - 想在 App 內直接看到版本更新內容,原本沒有頁面 - 勝利後仍可繼續點加分,容易誤觸超過該局比分 - 一場打完要手動回去重設下一場對戰,流程多一步 - 報分連十位數一起唸(十九、二十)冗長,現場聽不直覺 影響: - 新增 scripts/generate-changelog.mjs、src/pages/ChangelogPage.tsx;package.json 加 predev/prebuild/gen:changelog;tsconfig.app.json 開 resolveJsonModule;.gitignore 排除產生的 changelog.json - src/types.ts 新增 ChangelogEntry/ChangelogData;src/App.tsx 加導覽與路由、結算後發 nextMatchSignal、recordPoint 加勝負防呆 - src/lib/match.ts 將 hasWonGame 抽出並 export 供記分板共用 - src/pages/ScoreboardPage.tsx:勝負時 canScore=false、收到 nextMatchSignal 自動清空並打開選隊 picker、報分組字改個位數與「平」 - src/App.css 新增更新紀錄卡片樣式 修法: - changelog 於 dev/build 前自動產生,正式版讀打包後的 JSON 顯示 - hasWonGame 統一判斷(達標分/Deuce 領先 2 分/30 分上限),App 與記分板共用 - 結算成功才遞增 nextMatchSignal,記分板以「render 期間依 prop 變化調整 state」開啟選隊面板 - 報分以 servingScore % 10 / opponentScore % 10 組字,同分輸出「N 平」 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-19
@@ -19,9 +19,11 @@ import {
|
||||
getServingPlayer,
|
||||
getTeamDisplayName,
|
||||
getWinnerName,
|
||||
hasWonGame,
|
||||
parseRoster,
|
||||
swapCourtPositions,
|
||||
} from './lib/match'
|
||||
import { ChangelogPage } from './pages/ChangelogPage'
|
||||
import { HistoryPage } from './pages/HistoryPage'
|
||||
import { RoomListPage } from './pages/RoomListPage'
|
||||
import { RoomSpectatorPage } from './pages/RoomSpectatorPage'
|
||||
@@ -143,6 +145,8 @@ function App() {
|
||||
const [pwaUpdateReady, setPwaUpdateReady] = useState(false)
|
||||
const [liveRoomSession, setLiveRoomSession] = useState<LiveRoomSession | null>(null)
|
||||
const [navigationLockMessage, setNavigationLockMessage] = useState('')
|
||||
// 結算完成後遞增,通知記分板自動打開選隊伍面板讓人選下一場。
|
||||
const [nextMatchSignal, setNextMatchSignal] = useState(0)
|
||||
const currentAppVersionRef = useRef<string | null>(null)
|
||||
const creatingRoomRef = useRef(false)
|
||||
const lastSyncedRoomSignatureRef = useRef('')
|
||||
@@ -730,6 +734,11 @@ function App() {
|
||||
return
|
||||
}
|
||||
|
||||
// 已分出勝負就不再計分,需先結算才能開始下一場。
|
||||
if (hasWonGame(scoreState)) {
|
||||
return
|
||||
}
|
||||
|
||||
const starter = getServerHistoryIndex(scoreState, leftTeam, rightTeam)
|
||||
|
||||
if (starter === null) {
|
||||
@@ -865,6 +874,7 @@ function App() {
|
||||
uploading: false,
|
||||
})
|
||||
resetScoring(initialScoreState, { releaseLiveRoom: false })
|
||||
setNextMatchSignal((current) => current + 1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -914,6 +924,7 @@ function App() {
|
||||
uploading: false,
|
||||
})
|
||||
resetScoring(initialScoreState, { releaseLiveRoom: false })
|
||||
setNextMatchSignal((current) => current + 1)
|
||||
} catch (error) {
|
||||
setSettlement({
|
||||
error: error instanceof Error ? error.message : '上傳戰績失敗。',
|
||||
@@ -975,6 +986,13 @@ function App() {
|
||||
>
|
||||
房間列表
|
||||
</NavLink>
|
||||
<NavLink
|
||||
className={({ isActive }) => (isActive ? 'nav-pill nav-pill-active' : 'nav-pill')}
|
||||
onClick={handleNavAttempt('/changelog')}
|
||||
to="/changelog"
|
||||
>
|
||||
更新紀錄
|
||||
</NavLink>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -1031,6 +1049,7 @@ function App() {
|
||||
hasRecordedPoint={pointLog.length > 0}
|
||||
leftTeam={leftTeam}
|
||||
liveRoomId={liveRoomId}
|
||||
nextMatchSignal={nextMatchSignal}
|
||||
rightTeam={rightTeam}
|
||||
scoreState={scoreState}
|
||||
selectedGroup={selectedGroup}
|
||||
@@ -1052,6 +1071,7 @@ function App() {
|
||||
}
|
||||
/>
|
||||
<Route path="/history" element={<HistoryPage />} />
|
||||
<Route path="/changelog" element={<ChangelogPage />} />
|
||||
<Route path="/rooms" element={<RoomListPage />} />
|
||||
<Route
|
||||
path="/rooms/:roomId"
|
||||
@@ -1159,25 +1179,6 @@ function getNextServerName(
|
||||
return getServingPlayer(rightTeam, state.rightRightCourtPlayer, state.scoreRight)?.name ?? ''
|
||||
}
|
||||
|
||||
function hasWonGame(state: ScoreState) {
|
||||
const leadingScore = Math.max(state.scoreLeft, state.scoreRight)
|
||||
const trailingScore = Math.min(state.scoreLeft, state.scoreRight)
|
||||
|
||||
if (leadingScore < state.targetScore) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (leadingScore >= 30) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (trailingScore >= state.targetScore - 1) {
|
||||
return leadingScore - trailingScore >= 2
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function formatPlayedAt(timestamp: number) {
|
||||
return new Date(timestamp * 1000).toLocaleString('zh-TW', { hour12: false })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user