1ff149621f5e155037d2f379ec706cdf49ce9507
根本原因: V1 三輪配對制不符合實際打球流程:實際是 2、3 號場地各打 8 局雙打、 每局換搭檔輪休;且配對完全沒考慮成員實力,會出現強強同隊的失衡對戰。 影響: - 首頁改為 V2 場地排程:每局從全員挑 8 人分兩場地打雙打,其餘自動輪休, 搭檔組合在全部輪過一遍之前不重複,每人上場局數落差最多 1 局 - tg_members 新增 skill 欄位(1~10,預設 1),attendance API 一併帶出實力, 新增 GET /api/skills 供頁面開啟時載入全體實力表(載完才能產生賽程) - V2 依實力排點:搭檔強弱互補、每局兩隊實力總和盡量接近, 滑鼠停在隊伍上會浮出實力泡泡;讀取指定日期可回填已上傳的 V2 賽程 - V1 完整保留於 /v1,讀取出席名單改為依實力自動分 A / B 區 - V2 推送 LINE 固定走測試目標(LINE_TARGET_ID_LOCAL),不碰正式群組, 訊息改用綠底標題+斑馬紋對戰列的新版 Flex 卡片 - 上傳沿用 badminton 表,battlecombination 改以場地為 key 存 16 組搭檔 修法: App.tsx 拆為路由(/ 進 AppV2、/v1 進 AppV1);AppV2 排程演算法以 105 種 完美配對窮舉搭配重試 40 次,取「重複最少、總實力差最小」的一份賽程; server 新增 /api/skills、LINE V2 Flex 訊息與 target 覆寫,attendance 查詢 LEFT JOIN tg_members 取得 skill(user_id 優先、nickname 備援、預設 1)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
羽毛球分組配對器
這是一個使用 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_ID_LOCAL),不會推到正式羽球群;一樣要按兩次確認
V1 功能說明(/v1)
- 分別輸入 A 區與 B 區名單
- 可指定要寫入資料庫的目標日期
- 每次固定產生 3 組完整配對結果
- 每一隊都由
1 位 A 區 + 1 位 B 區組成 - 每一組都會把 A 區與 B 區名單完整分配完成
- 兩區人數差 2 以上時,每輪會輪流讓人多的一區成員跨區支援
- 跨區平衡後仍有缺口(總人數為奇數)時,才補入「那個」
- 產生配對後可用按鈕手動上傳資料到資料庫
- 若指定日期已經有資料,上傳前會先詢問是否覆蓋
- 可讀取指定日期的資料庫內容並回填到畫面
- 若指定日期還沒有配對資料,
讀取指定日期會自動改抓 Telegram bot 的出席名單 - 也可直接按
讀取今日出席帶入出席名單 - 載入出席名單時會依
tg_members.skill實力權重自動分區:實力強的一半放 A 區、弱的一半放 B 區(可再手動調整) - 兩邊都沒有資料時,畫面會顯示對應的錯誤訊息
- 組隊結果產生後可一鍵主動推播到指定 LINE 對話(需按兩次確認)
- 支援換行、半形逗號、全形逗號與頓號輸入
- 會自動去除空白與重複名稱
V1 操作流程
- 選擇
目標日期 - 按
讀取今日出席帶入 Telegram 報名名單(或手動輸入 A 區與 B 區名單) - 系統會依實力權重自動分好 A / B 區,需要時再手動微調
- 按下
產生三組配對 - 按下
上傳資料將資料寫入 DB - 若需要,可按
讀取指定日期載回既有資料;該日還沒配對時會自動改載入出席名單 - 組隊結果顯示後,可按
推送到 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 表結構:
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 最強),這個專案只會讀取、不會寫入:
UPDATE tg_members SET skill = 8 WHERE nickname = '柏威';
bot 另外還有 tg_poll(報名訊息 id)表,這個專案不會讀寫;tg_members 也只讀 skill 與 nickname 對應,不會修改。
使用方式
npm install
npm run dev
前端開發網址預設為:
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: 目標日期,格式為YYYYMMDDpersonnel: 人員清單,格式為[[1,"A區成員"],[0,"B區成員"]]battlecombination: 三組隊伍搭配,格式為{"0":[["A","B"]],"1":[...],"2":[...]}
正式建置
npm run build
Docker 部署
這個專案已提供單容器部署方式,容器啟動後會同時提供:
- 網頁前端
/api後端- MariaDB 寫入功能
建立映像
docker build -t badminton-match-hub .
啟動容器
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直接打包進映像
部署完成後可直接透過:
http://NAS_IP:3500
開啟系統。
docker-compose
專案也已提供 docker-compose.yml。
docker compose up -d --build
Compose 版本預設會:
- 對外使用
3500 - 容器內部使用
3500 - 讀取
.env內的資料庫設定 - 預設使用
LINE_TARGET_MODE=prod - 推播到
LINE_TARGET_ID_PROD
也就是說,在 NAS 上直接執行:
sudo docker compose up -d --build
就會以正式環境方式啟動。
請先確認 .env 至少有以下設定:
LINE_CHANNEL_ACCESS_TOKEN=你的token
LINE_TARGET_ID_LOCAL=你的測試對話ID
LINE_TARGET_ID_PROD=你的正式對話ID
部署後可透過:
http://NAS_IP:3500
開啟系統。
Description
A platform for pairing badminton teams and managing match opportunities.
http://jianmiau.tk:3500/
249 KiB
Languages
TypeScript
66.9%
JavaScript
20.6%
CSS
11.2%
HTML
0.7%
Dockerfile
0.6%