JianMiauandClaude Fable 5 6ae1eda7e2 功能:選隊面板新增「同步今日場次」,從 history 表撈今天資料覆蓋本機場次
摘要:
- 選隊伍面板新增「同步今日場次」按鈕,按下後把每個人的今日場次更新成 DB 的數字
- 後端新增 GET /api/history/play-counts?from=&to=,回傳區間內每人上場場數與比賽數
- 按鈕上方顯示同步結果或錯誤訊息;手機版同步按鈕獨占一列

根本原因:
- 今日場次只存在本機 localStorage,跨裝置記分時其他裝置上傳的場次本機看不到,
  自動選擇與「今日 N 場」提示就不準

影響:
- 任一裝置按同步後,今日場次會與 DB history 表一致(以 DB 為準,覆蓋本機)
- 輪空/那個等佔位字不計場次

修法:
- server.mjs 新增 play-counts 端點,依 time 區間查 history.players 累計
- api.ts 新增 loadHistoryPlayCounts;App.tsx 以本機時區當天 00:00 起算 24 小時區間並 setPlayCounts
- ScoreboardPage 加入同步狀態與按鈕,App.css 調整按鈕列為三欄、≤720px 改兩列

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 11:33:54 +08:00

羽毛球記分板

這是一個使用 Vite + React + TypeScript + Node.js 製作的羽毛球記分板專案,提供手機優先的記分介面、歷史戰績、房間觀戰、語音播報、PWA 安裝,以及 Docker / NAS 部署方式。

功能特色

  • 選隊伍頁面
    • 可依指定日期從資料庫讀取分組資料。
    • 若當天沒有資料,可手動輸入 A、B 區名單建立配對。
    • 點進記分板時會直接帶入該組對戰。
  • 記分板
    • 兩隊隊員可自由交換上下、左右位置。
    • 畫面編號固定為左上 1、右上 2、右下 3、左下 4
    • 先攻只能在開局設定一次,之後不會跟著發球權改變。
    • 點擊分數直接加分,沒有加一減一按鈕。
    • 第一分開始後,設定隊伍 會改成 上一步
    • 比賽結算 需要長按 1 秒才會觸發,避免誤觸。
    • 達標分數後有獲勝動畫與結算流程。
    • 手機上會盡量壓縮成單頁滿版,避免上下滑動。
  • 羽球規則
    • 預設 21 分制,可在設定隊伍時調整目標分數。
    • 支援 Deuce20:20 後需領先 2 分才獲勝。
    • 29:29 時第 30 分直接獲勝。
    • 發球方依羽球規則處理,0 分在右發球區。
    • 畫面以下方隊伍為我方、上方隊伍為對方。
    • 上方隊伍採鏡像顯示,所以我方 0:0 在右邊發球時,對方會在左邊接發。
  • 語音播報
    • 只在按下加分當下播報,不會因復原或其他操作重複報分。
    • 可選擇是否播報得分與發球者。
    • 同隊連續得分才會播報 換邊發球
    • 支援調整語速,最高可到 10x
    • RURU 會做大小寫無關判斷並以指定發音播報。
  • 歷史戰績
    • 可從資料庫讀取歷史列表。
    • 點開單筆可查看得分過程。
    • 每筆資料可刪除,刪除前會顯示確認提示。
  • 今日場次
    • 選隊伍面板每個人名字旁顯示 今日 N 場,每次結算幫上場的人各加一場,存在本機、跨日歸零。
    • 面板的 同步今日場次 按鈕會從 history 表撈今天(本機時區)的比賽,把每個人的今日場次覆蓋成 DB 的數字,跨裝置記分時用來補齊其他裝置上傳的場次。
  • 出席率統計
    • 歷史戰績頁的 出席率統計 按鈕會彈窗顯示每個人的出席狀況。
    • 資料取自 attendance 表(TG 報名 bot 每天每人一列),不是 history 表,所以有報名到場但沒被記分的場次也算出席;當天名單還沒存進 badminton 表也已經算得到。
    • 若 DB 沒有 attendance 表,會退回用 badminton 表的每日分組名單統計。表名可用 DB_ATTENDANCE_TABLE 覆寫。
    • 同時顯示兩種出席率:
      • 全期:以資料庫全部場次為分母。
      • 加入後:從各自第一次出席那天算起,新加入的人不會被稀釋。
    • 另外顯示最近 12 場的到場次數,可看出誰還在打球。
    • 英文名大小寫不同(RURU / RuRu / Ruru)自動視為同一人;中文錯字用 server/server.mjsATTENDANCE_ALIASES 別名表對照,合併過的寫法會顯示在該列。
    • ATTENDANCE_IGNORED_NAMES 列的佔位字(如 輪空)不列入統計。
  • 房間觀戰
    • 記分板帶入隊伍後會自動建立房間。
    • 房間列表可查看目前直播中的比賽。
    • 觀戰者只能看,不能操作記分。
    • 分數、房間狀態、比賽結算會即時同步給觀戰者。
    • 房間失效、重整、重選隊伍後也會通知觀戰者。
    • 房間列表有 重新取得列表,並帶有 5 秒冷卻。
  • PWA
    • 可安裝到 iPhone / iPad / Android 主畫面。
    • 支援 Web App 模式啟動。
    • 新版本部署後會提示重新整理或重新安裝。

本機開發

Port

  • Client: 3501
  • Server: 8788

安裝

npm install

啟動開發模式

npm run dev

啟動後:

  • 前端:http://localhost:3501
  • APIhttp://localhost:8788

檢查

npm run lint
npm run build

記分板滿版模式

  • 記分板頁面會套用 100dvh 高度。
  • 手機進入記分板時會關閉頁面捲動與 overscroll。
  • viewport 已加上 viewport-fit=cover,較能貼合 iPhone / iPad 安全區。
  • 若手機高度較矮,會再縮小字級、按鈕與分數區,盡量維持整頁顯示。

環境變數

請先建立 .env

DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_DATABASE=badminton
DB_TABLE=badminton
DB_HISTORY_TABLE=history
DB_ATTENDANCE_TABLE=attendance
PORT=8788

Docker / NAS 部署

對外服務配置:

  • 容器內 Node / API8788
  • 對外 HTTPS 網址:3501

部署指令:

sudo docker compose up -d --build

部署完成後可用:

https://你的網域或 NAS IP:3501

每次執行 sudo docker compose up -d --build 都會重新建置前後端與 PWA 靜態資產。

SSL 憑證

Docker Compose 會掛載以下目錄:

/volume1/docker/certs/

需包含:

  • cert.pem
  • chain.pem
  • privkey.pem

nginx 容器會監看這個目錄,更新憑證檔案後會自動重新載入,不需要重啟容器。

憑證到期偵測

  • 後端會讀取 cert.pem 的到期日,透過 /api/version/api/health 回報給前端。
  • 前端會把到期日存在 localStorage;當 API 出現 Failed to fetch 時,若存下來的到期日已過,會明確顯示「SSL 憑證已於某日過期」而不是通用錯誤。
  • 憑證到期前 7 天,頁面會跳出提醒更新憑證的通知。

資料表格式

history

  • id
  • time
  • dayOfWeek
  • score
  • winScore
  • type
    • 0: 雙打
    • 1: 單打
  • players
    • 依照 1 ~ 4 固定編號順序儲存玩家名單。
  • team
    • 12 一隊
    • 34 一隊
  • scoreList
    • 格式:[round, 發球者編號, 連勝數, 得分隊伍(0 或 1)]

PWA Icon

目前使用:

  • public/favicon.png
  • public/apple-touch-icon.png
  • public/pwa-192.png
  • public/pwa-512.png

Git 中文顯示

若要讓 git log / commit 顯示中文,建議設定:

git config i18n.commitEncoding utf-8
git config i18n.logOutputEncoding utf-8
git config core.quotepath false
S
Description
羽毛球記分板系統,可用於即時顯示比分、回合進度與比賽狀態。
https://jianmiau.tk:3501/
Readme
2.3 MiB
Languages
TypeScript 59.4%
CSS 24.3%
JavaScript 14.7%
Shell 1%
HTML 0.4%
Other 0.2%