功能:首頁改為 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>
This commit is contained in:
@@ -12,6 +12,8 @@ const matchTableName = process.env.DB_TABLE ?? 'badminton'
|
||||
const historyTableName = process.env.DB_HISTORY_TABLE ?? 'history'
|
||||
// 出席統計的來源:TG 報名 bot 寫入的 attendance 表(每天每人一列)。
|
||||
const attendanceTableName = process.env.DB_ATTENDANCE_TABLE ?? 'attendance'
|
||||
// 成員實力表(match-hub 維護的 tg_members.skill)。
|
||||
const membersTableName = process.env.DB_MEMBERS_TABLE ?? 'tg_members'
|
||||
const appVersion = process.env.APP_VERSION ?? `${Date.now()}`
|
||||
const appStartedAt = new Date().toISOString()
|
||||
const LIVE_ROOM_STALE_MS = 30_000
|
||||
@@ -380,6 +382,43 @@ app.get('/api/match-results/:time', async (request, response) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 全體成員實力表(1~10,預設 1),給首頁手動排賽程時做實力配對用。
|
||||
app.get('/api/skills', async (_request, response) => {
|
||||
if (!pool) {
|
||||
response.status(500).json({
|
||||
ok: false,
|
||||
message: `DB 尚未設定完成,缺少 ${missingEnv.join(', ')}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const [rows] = await pool.execute(
|
||||
`SELECT nickname, skill FROM \`${membersTableName}\` WHERE nickname IS NOT NULL`,
|
||||
)
|
||||
const skills = {}
|
||||
|
||||
rows.forEach((row) => {
|
||||
const name = String(row.nickname ?? '').trim()
|
||||
|
||||
if (name) {
|
||||
skills[name] = clampSkill(row.skill)
|
||||
}
|
||||
})
|
||||
|
||||
response.json({
|
||||
ok: true,
|
||||
data: { skills },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('skills load error:', error)
|
||||
response.status(500).json({
|
||||
ok: false,
|
||||
message: error instanceof Error ? error.message : '實力資料讀取失敗。',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/attendance', async (_request, response) => {
|
||||
if (!pool) {
|
||||
response.status(500).json({
|
||||
@@ -895,6 +934,17 @@ function broadcastRoomList() {
|
||||
})
|
||||
}
|
||||
|
||||
// 實力限制在 1~10 的整數,缺值或壞值一律當 1。
|
||||
function clampSkill(value) {
|
||||
const skill = Number(value)
|
||||
|
||||
if (!Number.isFinite(skill)) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return Math.min(10, Math.max(1, Math.round(skill)))
|
||||
}
|
||||
|
||||
function getAttendanceName(rawName) {
|
||||
const name = String(rawName ?? '').trim()
|
||||
return ATTENDANCE_ALIASES[name] ?? name
|
||||
|
||||
Reference in New Issue
Block a user