JianMiauandClaude Fable 5 d53ebbd3a8 功能:出席率統計改以 attendance 表(TG 報名名單)為資料來源
摘要:
- 出席統計改讀 attendance 表(每天每人一列),依 poll_date 分組後餵給原本的統計函式
- 新增 DB_ATTENDANCE_TABLE 環境變數,預設 attendance;沒有此表時退回 badminton 分組名單
- 前端說明文字與 README 更新來源說明

根本原因:
- 原本用 badminton 表的每日分組名單,只有名單存進 DB 的日子才有資料,
  當天報名還沒存名單就統計不到,名字寫法也要靠別名表修補

影響:
- 統計天數 121 → 126 天,含當天報名尚未分組的場次
- 名字寫法在 attendance 表已統一,別名合併(卡森→景涵 等)仍生效

修法:
- server.mjs 新增 loadAttendanceDays(),buildAttendanceStats() 改吃 [{ time, names }]
- .env.example / docker-compose.yml / README 補上 DB_ATTENDANCE_TABLE

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 13:50:59 +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 會做大小寫無關判斷並以指定發音播報。
  • 歷史戰績
    • 可從資料庫讀取歷史列表。
    • 點開單筆可查看得分過程。
    • 每筆資料可刪除,刪除前會顯示確認提示。
  • 出席率統計
    • 歷史戰績頁的 出席率統計 按鈕會彈窗顯示每個人的出席狀況。
    • 資料取自 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%