Files
badminton-scoreboard/README.md
T
JianMiauandClaude Fable 5 6f38c155f0 功能:首頁改為 V2 場地排程,載入與手動產生皆為 2 場地各 8 局並依實力配對
摘要:
- 從資料庫讀取 match-hub V2 資料(battlecombination 以場地為 key)時,
  顯示「2 號場地」「3 號場地」各 8 局賽程,挑一個場地帶進記分板
- 手動分組移除 A / B 區,改為「今日出席名單」+「其他區名單」,
  演算法移植 match-hub V2:依 tg_members.skill 實力強弱互補、兩隊平衡,
  搭檔輪完一遍前不重複、每人局數落差最多 1 局;8 人以上排兩場地
- server 新增 GET /api/skills(表名可用 DB_MEMBERS_TABLE 覆寫)
- 選隊面板預設隊伍照局分組並顯示局數標題;修正帶入現有對戰時
  第 2 隊不顯示「已放入」且點擊無法取消的問題(配對比對改為不分順序)
- 舊 V1 三輪資料(personnel 含 B 區)仍照「第 N 組」顯示,自動判別

根本原因:
- 實際打球流程是 2、3 號場地各打 8 局、每局換搭檔輪休,
  舊的三輪配對制不符流程,也完全沒考慮實力;
  選隊面板的隊伍比對寫死正序,帶入現有對戰時右隊兩人反序就對不上

影響:
- 首頁載入與手動產生的結果格式一致(N 號場地 × 8 局),操作流程不變
- 舊版 A / B 區的 localStorage 內容自動合併進今日出席名單

修法:
- match.ts 移除跨區平衡三輪邏輯,移植 buildSchedule/pickBestMatching 等演算法,
  新增 buildCourtGroups 輸出場地格式;RoundGroup 增加 label
- convertDbRecordToGroups 依 personnel 是否含 B 區自動分流 V1 / V2
- ScoreboardPage 的 getPresetTeamSelectionSlot / removePresetTeamFromDraft
  改為兩人同隊即成立、不看順序

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 12:19:53 +08:00

199 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 羽毛球記分板
這是一個使用 `Vite + React + TypeScript + Node.js` 製作的羽毛球記分板專案,提供手機優先的記分介面、歷史戰績、房間觀戰、語音播報、PWA 安裝,以及 Docker / NAS 部署方式。
## 功能特色
- 選隊伍頁面
- 可依指定日期從資料庫讀取分組資料。
- match-hub V2 場地排程資料(`battlecombination` 以場地為 key、每場地 8 局 × 2 隊)會顯示成「2 號場地」「3 號場地」各 8 局賽程,挑一個場地帶進記分板;選隊面板的預設隊伍也會照局分組。
- 舊的 V1 三輪資料(personnel 含 B 區)仍照「第 N 組」顯示,兩種格式自動判別。
- 若當天沒有資料,可手動輸入「今日出席名單」排賽程:演算法與 match-hub V2 相同,依 `tg_members.skill` 實力(`GET /api/skills`,表名可用 `DB_MEMBERS_TABLE` 覆寫)做強弱互補與兩隊平衡,搭檔在全部組合輪過一遍前不重複、每人局數落差最多 1 局;8 人以上排 2、3 號兩個場地,未滿 8 人只排 2 號場地。
- 點進記分板時會直接帶入該組對戰。
- 記分板
- 兩隊隊員可自由交換上下、左右位置。
- 畫面編號固定為左上 `1`、右上 `2`、右下 `3`、左下 `4`
- 先攻只能在開局設定一次,之後不會跟著發球權改變。
- 點擊分數直接加分,沒有加一減一按鈕。
- 第一分開始後,`設定隊伍` 會改成 `上一步`
- `比賽結算` 需要長按 `1` 秒才會觸發,避免誤觸。
- 達標分數後有獲勝動畫與結算流程。
- 手機上會盡量壓縮成單頁滿版,避免上下滑動。
- 羽球規則
- 預設 `21` 分制,可在設定隊伍時調整目標分數。
- 支援 Deuce`20: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.mjs``ATTENDANCE_ALIASES` 別名表對照,合併過的寫法會顯示在該列。
- `ATTENDANCE_IGNORED_NAMES` 列的佔位字(如 `輪空`)不列入統計。
- 房間觀戰
- 記分板帶入隊伍後會自動建立房間。
- 房間列表可查看目前直播中的比賽。
- 觀戰者只能看,不能操作記分。
- 分數、房間狀態、比賽結算會即時同步給觀戰者。
- 房間失效、重整、重選隊伍後也會通知觀戰者。
- 房間列表有 `重新取得列表`,並帶有 `5` 秒冷卻。
- PWA
- 可安裝到 iPhone / iPad / Android 主畫面。
- 支援 Web App 模式啟動。
- 新版本部署後會提示重新整理或重新安裝。
## 本機開發
### Port
- Client: `3501`
- Server: `8788`
### 安裝
```bash
npm install
```
### 啟動開發模式
```bash
npm run dev
```
啟動後:
- 前端:`http://localhost:3501`
- API`http://localhost:8788`
### 檢查
```bash
npm run lint
npm run build
```
## 記分板滿版模式
- 記分板頁面會套用 `100dvh` 高度。
- 手機進入記分板時會關閉頁面捲動與 overscroll。
- `viewport` 已加上 `viewport-fit=cover`,較能貼合 iPhone / iPad 安全區。
- 若手機高度較矮,會再縮小字級、按鈕與分數區,盡量維持整頁顯示。
## 環境變數
請先建立 `.env`
```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 / API`8788`
- 對外 HTTPS 網址:`3501`
部署指令:
```bash
sudo docker compose up -d --build
```
部署完成後可用:
```text
https://你的網域或 NAS IP:3501
```
每次執行 `sudo docker compose up -d --build` 都會重新建置前後端與 PWA 靜態資產。
## SSL 憑證
Docker Compose 會掛載以下目錄:
```text
/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`
- `1``2` 一隊
- `3``4` 一隊
- `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 顯示中文,建議設定:
```bash
git config i18n.commitEncoding utf-8
git config i18n.logOutputEncoding utf-8
git config core.quotepath false
```