Files
badminton-match-hub/README.md
T
JianMiauandClaude Fable 5 ae8dba99b7 V2 推送 LINE 改依環境決定目標,移除卡片底部說明文字
根本原因:
V2 原本把推送目標寫死在測試對話(target: local),部署到 NAS 後也只會
私訊測試對象,無法推到正式羽球群;另外卡片底部的「搭檔不重複・實力平衡
排點」說明文字對群組成員沒有意義。

影響:
- NAS 正式環境(LINE_TARGET_MODE=prod)推送到勝皇羽球團群組
- 本機開發(local)維持推到測試對話,按鈕會標示「推送到 LINE(測試)」
- LINE 卡片不再顯示底部說明文字

修法:
前端移除寫死的 target 參數,交由 server 依 LINE_TARGET_MODE 解析目標;
按鈕文字與推送成功訊息改為依環境顯示;buildLineFlexMessageV2 移除 footer。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 11:32:41 +08:00

205 lines
7.4 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.
# 羽毛球分組配對器
這是一個使用 `React + Vite + TypeScript` 製作的網頁應用程式,用來排羽毛球場地賽程與隊伍配對。
- 首頁(`/`)是 V2:2、3 號場地各排 8 局雙打
- `/v1` 保留舊版:A / B 區三輪配對
## V2 功能說明(首頁)
- 輸入或用 `讀取出席名單` 載入出席成員(來源同 V1 的 attendance API
- 每局從全部成員中挑 8 人上場(2、3 號場地各 4 人打雙打),其他人休息
- 成員不固定場地,每局重新分配;系統會讓每個人上場局數盡量平均(落差最多 1 局)
- 搭檔組合在全部組合輪完一遍之前不會重複;對手組合也會盡量錯開
- 頁面載入時會先透過 `GET /api/skills``tg_members` 的實力表,載入完成前不能產生賽程
- 依實力權重排點:在不重複的前提下,搭檔盡量強弱互補、每局兩隊實力總和盡量接近(查不到的人權重當 1);對戰表平常只顯示名字,滑鼠停在隊伍上會浮出實力泡泡(例如 `阿志 1、蓉蓉 4(合計 5`
- 人數 4~7 人時只排 2 號場地 8 局;未滿 4 人無法排賽程
- `讀取指定日期` 可把之前上傳的 V2 賽程從資料庫回填到畫面;該日還沒賽程時會自動改載入出席名單(V1 格式的舊資料請到 `/v1` 讀取)
- 上傳沿用原本的 `badminton` 表:`personnel` 存全部出席者、`battlecombination``"0"` / `"1"` 分別存 2、3 號場地的 16 組搭檔(依序每 2 組為一局的對戰雙方)
- `推送到 LINE` 跟著 `LINE_TARGET_MODE` 走:NAS 正式環境(`prod`)推到羽球群、本機開發(`local`)推到測試對話;一樣要按兩次確認,`local` 模式時按鈕會標示「測試」
## V1 功能說明(`/v1`
- 分別輸入 A 區與 B 區名單
- 可指定要寫入資料庫的目標日期
- 每次固定產生 3 組完整配對結果
- 每一隊都由 `1 位 A 區 + 1 位 B 區` 組成
- 每一組都會把 A 區與 B 區名單完整分配完成
- 兩區人數差 2 以上時,每輪會輪流讓人多的一區成員跨區支援
- 跨區平衡後仍有缺口(總人數為奇數)時,才補入「那個」
- 產生配對後可用按鈕手動上傳資料到資料庫
- 若指定日期已經有資料,上傳前會先詢問是否覆蓋
- 可讀取指定日期的資料庫內容並回填到畫面
- 若指定日期還沒有配對資料,`讀取指定日期` 會自動改抓 Telegram bot 的出席名單
- 也可直接按 `讀取今日出席` 帶入出席名單
- 載入出席名單時會依 `tg_members.skill` 實力權重自動分區:實力強的一半放 A 區、弱的一半放 B 區(可再手動調整)
- 兩邊都沒有資料時,畫面會顯示對應的錯誤訊息
- 組隊結果產生後可一鍵主動推播到指定 LINE 對話(需按兩次確認)
- 支援換行、半形逗號、全形逗號與頓號輸入
- 會自動去除空白與重複名稱
## V1 操作流程
1. 選擇 `目標日期`
2.`讀取今日出席` 帶入 Telegram 報名名單(或手動輸入 A 區與 B 區名單)
3. 系統會依實力權重自動分好 A / B 區,需要時再手動微調
4. 按下 `產生三組配對`
5. 按下 `上傳資料` 將資料寫入 DB
6. 若需要,可按 `讀取指定日期` 載回既有資料;該日還沒配對時會自動改載入出席名單
7. 組隊結果顯示後,可按 `推送到 LINE`(按兩次確認才會送出)
## 讀取今日出席
出席名單來自 Node-RED 的 Telegram bot「建喵打羽球」:群組成員按「參加」後會寫進同一個資料庫的 `attendance` 表,按「不參加」則直接刪除,所以表裡的就是當天出席名單。
- 使用現有的 `DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_DATABASE` 連線,不需要新的環境變數
- API`GET /api/attendance/:time``time``YYYYMMDD`),回傳 `{ ok: true, data: { time, names: [...], members: [{ name, skill }] } }`;沒資料回 404
- 每個人的 `skill` 來自 `tg_members.skill`(優先用 `user_id` 對應,對不到再用 `nickname` 對應),查不到就當 1
- 載入後依 `skill` 由高到低排序,前一半(無條件進位)放 A 區、後一半放 B 區;同分時維持報名順序
- 載入時會沿用名單去重邏輯,並清空既有配對結果
`attendance` 表結構:
```sql
CREATE TABLE attendance (
poll_date INT(11) NOT NULL COMMENT '場次日期 YYYYMMDD',
user_id BIGINT NOT NULL COMMENT 'Telegram user id',
nickname VARCHAR(64) NOT NULL COMMENT '顯示名稱(就是要放進 personnel 的名字)',
tg_name VARCHAR(128) NULL COMMENT 'Telegram 顯示名',
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '報名時間',
PRIMARY KEY (poll_date, user_id)
) CHARSET utf8mb4;
```
### 實力權重
`tg_members` 表加了 `skill` 欄位(`TINYINT UNSIGNED NOT NULL DEFAULT 1`,範圍 1~10,10 最強),這個專案只會讀取、不會寫入:
```sql
UPDATE tg_members SET skill = 8 WHERE nickname = '柏威';
```
bot 另外還有 `tg_poll`(報名訊息 id)表,這個專案不會讀寫;`tg_members` 也只讀 `skill``nickname` 對應,不會修改。
## 使用方式
```bash
npm install
npm run dev
```
前端開發網址預設為:
```text
http://localhost:3500
```
推播到 LINE 時,會直接使用 Flex Message 主動送到指定對話,格式參考既有 `line-bot-ts` 羽球查詢結果樣式。
若指定日期尚未上傳資料,系統會先提示你上傳後再推播。
LINE 推播目標支援分成兩組:
- `LINE_TARGET_ID_LOCAL`: 本地測試用對話
- `LINE_TARGET_ID_PROD`: 正式環境用對話
- `LINE_TARGET_MODE`: `local``prod`
當系統偵測目前為 `local` 模式時,頁面右上角會顯示「測試環境」標籤。
正式環境 `prod` 不會顯示這個標籤。
## 資料庫欄位
- `time`: 目標日期,格式為 `YYYYMMDD`
- `personnel`: 人員清單,格式為 `[[1,"A區成員"],[0,"B區成員"]]`
- `battlecombination`: 三組隊伍搭配,格式為 `{"0":[["A","B"]],"1":[...],"2":[...]}`
## 正式建置
```bash
npm run build
```
## Docker 部署
這個專案已提供單容器部署方式,容器啟動後會同時提供:
- 網頁前端
- `/api` 後端
- MariaDB 寫入功能
### 建立映像
```bash
docker build -t badminton-match-hub .
```
### 啟動容器
```bash
docker run -d \
--name badminton-match-hub \
-p 3500:3500 \
-e PORT=3500 \
-e DB_HOST=192.168.0.15 \
-e DB_PORT=3307 \
-e DB_USER=jianmiau \
-e DB_PASSWORD=你的密碼 \
-e DB_DATABASE=badminton \
-e DB_TABLE=badminton \
badminton-match-hub
```
### NAS 上建議設定
- 容器埠使用 `3500`
- 對外埠可自訂,例如 `3500:3500`
- 環境變數請在 NAS 的 Docker 介面中填入
- 不要把本機 `.env` 直接打包進映像
部署完成後可直接透過:
```text
http://NAS_IP:3500
```
開啟系統。
### docker-compose
專案也已提供 `docker-compose.yml`
```bash
docker compose up -d --build
```
Compose 版本預設會:
- 對外使用 `3500`
- 容器內部使用 `3500`
- 讀取 `.env` 內的資料庫設定
- 預設使用 `LINE_TARGET_MODE=prod`
- 推播到 `LINE_TARGET_ID_PROD`
也就是說,在 NAS 上直接執行:
```bash
sudo docker compose up -d --build
```
就會以正式環境方式啟動。
請先確認 `.env` 至少有以下設定:
```env
LINE_CHANNEL_ACCESS_TOKEN=你的token
LINE_TARGET_ID_LOCAL=你的測試對話ID
LINE_TARGET_ID_PROD=你的正式對話ID
```
部署後可透過:
```text
http://NAS_IP:3500
```
開啟系統。