/* NBA球迷大全 前端逻辑(原生 JS,无构建) */ const $ = (s) => document.querySelector(s); const $$ = (s) => [...document.querySelectorAll(s)]; const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); const chatHistory = []; let chatBusy = false; /* 简洁版当前时间 HH:MM */ function nowTime() { const d = new Date(); return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; } /* 轻提示 */ function toast(msg) { let t = document.getElementById("toast"); if (!t) { t = document.createElement("div"); t.id = "toast"; document.body.appendChild(t); } t.textContent = msg; t.classList.add("show"); clearTimeout(t._timer); t._timer = setTimeout(() => t.classList.remove("show"), 1800); } /* 复制文本(Clipboard API + 降级) */ async function copyText(text) { try { await navigator.clipboard.writeText(text); } catch (e) { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); ta.remove(); } toast("✅ 已复制"); } /* ================= Markdown 渲染(marked 本地库,先转义防 XSS) ================= */ function md(text) { if (window.marked) { marked.setOptions({ breaks: true, gfm: true }); return marked.parse(esc(text)); } return esc(text).replace(/\n/g, "
"); } /* 实体高亮:先用占位符替换实体区间(避免 markdown 转换后偏移错位),渲染后再恢复为可点击标签 */ function renderMdWithEntities(reply, entities) { let text = reply || ""; const phs = []; if (entities && entities.length) { const sorted = [...entities].sort((a, b) => b.start - a.start); for (const sp of sorted) { if (sp.start < 0 || sp.end > text.length || sp.start >= sp.end) continue; const ph = `\u27E6E${phs.length}\u27E7`; phs.push(sp); text = text.slice(0, sp.start) + ph + text.slice(sp.end); } } let html = md(text); phs.forEach((sp, i) => { const ph = `\u27E6E${i}\u27E7`; if (html.includes(ph)) { const tag = `${esc(sp.name)}`; html = html.split(ph).join(tag); } }); return html; } /* ================= 标签页切换 ================= */ $$(".tab").forEach((t) => t.addEventListener("click", () => { $$(".tab").forEach((x) => x.classList.remove("active")); $$(".view").forEach((x) => x.classList.remove("active")); t.classList.add("active"); $("#view-" + t.dataset.view).classList.add("active"); loadView(t.dataset.view); })); function loadView(v) { if (v === "teams") loadTeams(); else if (v === "players") { playersPage = 1; playersQ = ""; playersPos = ""; $("#search-players").value = ""; $$("#view-players .btn.small").forEach((x) => x.classList.toggle("active", x.dataset.pos === "")); loadPlayers(); } else if (v === "games") { gamesPage = 1; gamesQ = ""; gamesStatus = ""; $("#search-games").value = ""; $$("#view-games .btn.small").forEach((x) => x.classList.toggle("active", x.dataset.status === "")); loadGames(); } else if (v === "news") { newsPage = 1; newsQ = ""; newsKind = ""; newsTag = ""; $("#search-news").value = ""; $$("#view-news .btn.small").forEach((x) => x.classList.toggle("active", !x.dataset.kind && !x.dataset.tag)); loadNews(); } else if (v === "persons") { personsPage = 1; personsQ = ""; personsRole = ""; $("#search-persons").value = ""; $$("#view-persons .btn.small").forEach((x) => x.classList.toggle("active", x.dataset.role === "")); loadPersons(); } else if (v === "standings") { if ($("#season-select").options.length) { loadStandings(); loadPlayoffs(); } else loadSeasons(); } } /* ================= 对话 ================= */ function addMsg(role, html, opts = {}) { const div = document.createElement("div"); div.className = `msg ${role}`; if (opts.idx !== undefined) div.dataset.idx = opts.idx; const actions = role === "bot" ? `
${nowTime()}
` : `
${nowTime()}
`; div.innerHTML = `
${role === "user" ? "🧑" : "🤖"}
${html}
${actions}
`; $("#chat-list").appendChild(div); $("#chat-list").scrollTop = $("#chat-list").scrollHeight; return div; } function showTyping() { const div = document.createElement("div"); div.className = "msg bot"; div.innerHTML = `
🤖
`; $("#chat-list").appendChild(div); $("#chat-list").scrollTop = $("#chat-list").scrollHeight; return div; } /* 实体快速查看卡片 HTML */ function cardHtml(c) { if (c.type === "team") return `
🏟️
${esc(c.name)}${esc(c.city || "")} · 总冠军×${c.champion_count ?? "?"}
`; if (c.type === "player") return `
${esc(c.name)}${esc(c.team || "自由球员")}${c.season ? " · " + (c.season.pts ?? "?") + "分" : ""}
`; if (c.type === "person") return `
👤
${esc(c.name)}${esc(c.role_cn || "")}
`; if (c.type === "game") { const score = c.status === "finished" ? `${c.away_score ?? "?"} : ${c.home_score ?? "?"}` : "VS"; return `
🏀
${esc(c.away_team || "")} ${score} ${esc(c.home_team || "")}${esc(c.round_name || "")} · ${esc((c.game_time || "").slice(0, 16))}
`; } return ""; } /* 组装回答气泡 HTML(参考资讯 + markdown实体 + 卡片 + 来源) */ function buildBotHtml(d) { let html = ""; // 需求4:参考资讯(新闻/百科链接),默认折叠,位于回答块上方 if (d.news_refs && d.news_refs.length) { html += `
📰 参考资讯(${d.news_refs.length})
`; } // 需求3:markdown 渲染 + 需求5:实体特殊标记 html += `
${renderMdWithEntities(d.reply, d.entities)}
`; // 需求5:快速查看入口卡片 if (d.cards && d.cards.length) { html += `
${d.cards.map(cardHtml).join("")}
`; } if (d.sources && d.sources.length) { html += "
" + d.sources.map((s) => `来源:${esc(s.tool)}`).join("") + "
"; } return html; } async function sendChat(text) { if (chatBusy) return; chatBusy = true; $("#send-btn").disabled = true; addMsg("user", esc(text)); const typing = showTyping(); try { const r = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text, history: chatHistory.slice(-8) }) }); const d = await r.json(); typing.remove(); if (d.error) { addMsg("bot", `⚠️ ${esc(d.error)}`); return; } chatHistory.push({ user: text, assistant: d.reply }); const idx = chatHistory.length - 1; addMsg("bot", buildBotHtml(d), { idx }); if (chatHistory.length > 20) { const removed = chatHistory.length - 20; chatHistory.splice(0, removed); document.querySelectorAll("#chat-list .msg[data-idx]").forEach((m) => { const i = parseInt(m.dataset.idx); if (i < removed) m.remove(); else m.dataset.idx = i - removed; }); } // 大模型预测下一轮快捷问题(异步刷新底部 chips) const mark = chatHistory.length; refreshChips(mark); } catch (e) { typing.remove(); addMsg("bot", "⚠️ 网络异常,请稍后再试。"); } finally { chatBusy = false; $("#send-btn").disabled = false; } } $("#send-btn").addEventListener("click", () => { const v = $("#chat-input").value.trim(); if (v) { $("#chat-input").value = ""; autoResizeInput(); sendChat(v); } }); /* 输入框多行自适应:超一行自动增高,最多 4 行(约90px);Enter 发送,Shift+Enter 换行 */ const chatInput = $("#chat-input"); const INPUT_MAX_H = 90; function autoResizeInput() { chatInput.style.height = "auto"; chatInput.style.height = Math.min(chatInput.scrollHeight, INPUT_MAX_H) + "px"; } chatInput.addEventListener("input", autoResizeInput); chatInput.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); $("#send-btn").click(); } }); /* 复制当前回答(纯文本) */ function copyMsg(btn) { const bubble = btn.closest(".msg").querySelector(".bubble"); copyText(bubble.innerText.trim()); } /* 重新生成:以该轮之前的上下文重新提问,替换本条回答,截断后续对话 */ async function regenerate(btn) { const msgEl = btn.closest(".msg"); const idx = parseInt(msgEl.dataset.idx); if (isNaN(idx) || chatBusy) return; if (idx >= chatHistory.length) return; // 截断:删除该条之后的对话(上下文已变) chatHistory.splice(idx + 1); document.querySelectorAll("#chat-list .msg[data-idx]").forEach((m) => { if (parseInt(m.dataset.idx) > idx) m.remove(); }); const userText = chatHistory[idx].user; const body = msgEl.querySelector(".msg-body"); const bubble = msgEl.querySelector(".bubble"); bubble.innerHTML = ``; msgEl.querySelector(".msg-actions")?.remove(); chatBusy = true; try { const r = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: userText, history: chatHistory.slice(0, idx).map((h) => ({ user: h.user, assistant: h.assistant })) }) }); const d = await r.json(); if (d.error) { bubble.innerHTML = `⚠️ ${esc(d.error)}`; return; } bubble.innerHTML = buildBotHtml(d); const actions = document.createElement("div"); actions.className = "msg-actions"; actions.innerHTML = `${nowTime()}`; body.appendChild(actions); chatHistory[idx] = { user: userText, assistant: d.reply }; refreshChips(chatHistory.length); } catch (e) { bubble.innerHTML = "⚠️ 网络异常,请稍后再试。"; } finally { chatBusy = false; } } /* 分享:弹窗展示对话全文 + 一键复制 */ function shareChat() { if (!chatHistory.length) { toast("还没有对话内容"); return; } const text = chatHistory.map((h) => `🧑 ${h.user}\n🤖 ${h.assistant}`).join("\n\n"); openModal(`

🔗 分享对话

共 ${chatHistory.length} 轮 · 复制后粘贴到任意聊天或文档
`); } function copyShare() { const ta = document.querySelector(".share-box"); if (ta) copyText(ta.value); } /* 清空对话从头开始 */ function clearChat() { if (!chatHistory.length) { toast("对话已经是空的"); return; } if (!confirm("确定清空当前对话吗?将从头开始。")) return; chatHistory.length = 0; const list = $("#chat-list"); const welcome = document.getElementById("welcome-msg"); const w = welcome ? welcome.outerHTML : ""; list.innerHTML = w; loadBoot(); toast("🗑️ 对话已清空"); } $("#share-btn").addEventListener("click", shareChat); $("#clear-btn").addEventListener("click", clearChat); /* 快捷问题:点击 → 自动填入输入框并自动提交(需求2) */ function askQuick(q) { $("#chat-input").value = q; autoResizeInput(); sendChat(q); } /* 底部快捷问题:对话进行中由大模型预测用户可能追问的问题(异步刷新) */ async function refreshChips(mark) { try { const r = await fetch("/api/suggest", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ history: chatHistory.slice(-6) }) }); const d = await r.json(); if (!d.suggestions || !d.suggestions.length) return; if (mark !== chatHistory.length) return; // 期间用户又发了新消息 → 丢弃过期预测 $("#chips").innerHTML = d.suggestions.map((q) => ``).join(""); $$("#chips button").forEach((b) => b.addEventListener("click", () => askQuick(b.textContent))); } catch (e) {} } /* 聊天区事件委托:快捷语句 / 操作按钮(复制·重新生成) / 实体标记 / 新闻链接 / 卡片 */ $("#chat-list").addEventListener("click", (e) => { const act = e.target.closest(".act-btn"); if (act) { if (act.title.includes("复制")) copyMsg(act); else if (act.title.includes("重新生成")) regenerate(act); return; } const q = e.target.closest(".quick-q"); if (q) { e.preventDefault(); askQuick(q.textContent); return; } const ent = e.target.closest(".entity, .ecard"); if (ent && ent.dataset.type) { openEntity(ent.dataset.type, parseInt(ent.dataset.id)); return; } const news = e.target.closest("a[data-news]"); if (news) { e.preventDefault(); openNews(parseInt(news.dataset.news)); return; } }); function openEntity(type, id) { try { if (type === "team") openTeam(id); else if (type === "player") openPlayer(id); else if (type === "person") openPerson(id); else if (type === "game") openGame(id); } catch (e) { openModal(`

⚠️ 打开失败

该${type}记录可能已被删除(ID=${id})。

`); } } /* 启动信息:开场白 + 快捷问题(从配置加载,管理后台可编辑) */ async function loadBoot() { try { const b = await (await fetch("/api/boot")).json(); if (b.welcome_text) $("#welcome-text").innerHTML = md(b.welcome_text); const qs = b.suggestions || []; const hint = (b.welcome_hint || "试试:") + " "; $("#welcome-hint").innerHTML = hint + qs.map((q) => `${esc(q)}`).join(" / "); $("#chips").innerHTML = qs.map((q) => ``).join(""); $$("#chips button").forEach((b2) => b2.addEventListener("click", () => askQuick(b2.textContent))); if (b.site_name) document.title = b.site_name; if (b.footer_text) $("#footer-text").textContent = b.footer_text; } catch (e) {} } loadBoot(); /* ================= 通用请求 ================= */ async function getJSON(url) { const r = await fetch(url); if (!r.ok) throw new Error(r.status); return r.json(); } /* ================= 球队 ================= */ async function loadTeams() { const teams = await getJSON("/api/teams?limit=50"); $("#grid-teams").innerHTML = teams.map((t) => `

${esc(t.name)} ${esc(t.name_en)}

🏙️ ${esc(t.city)} · ${esc(t.arena)}
🏆 总冠军 ×${t.champion_count} · 建队 ${t.founded} 年
🧑‍🏫 主帅:${esc(t.head_coach)}
`).join(""); } async function openTeam(id) { const d = await getJSON(`/api/teams/${id}`); const t = d.team; const roster = d.roster.map((p) => `
${esc(p.name)} ${esc(p.position)} #${p.number} — 场均 ${p.season.pts}分
`).join(""); const games = d.recent_games.map((g) => `
${esc(g.game_time.slice(0, 10))} ${esc(g.away_team)} ${g.away_score ?? "?"} : ${g.home_score ?? "?"} ${esc(g.home_team)}(${g.status === "finished" ? "已结束" : "未开始"})
`).join(""); openModal(`

${esc(t.name)} ${esc(t.name_en)}

${esc(t.city)} · ${esc(t.arena)} · 建队 ${t.founded} · 总冠军 ×${t.champion_count}

${esc(t.intro)}

🧑‍🏫 主教练
${esc(t.head_coach)}
⭐ 主要球员(本赛季场均)
${roster || "暂无"}
📅 近期比赛
${games || "暂无"}
${DL("team", t.id)}
`); } /* ================= 球员 ================= */ let playersPage = 1, playersQ = "", playersPos = ""; const POS_CN = { PG: "控卫", SG: "分卫", SF: "小前", PF: "大前", C: "中锋" }; async function loadPlayers() { const d = await getJSON(`/api/players?q=${encodeURIComponent(playersQ)}&position=${playersPos}&page=${playersPage}&size=24`); const players = d.results || []; $("#grid-players").innerHTML = players.map((p) => `

${esc(p.name)} ${esc(POS_CN[p.position] || p.position)} #${p.number}

${esc(p.team || "")} · ${esc(p.country)}
本季:${p.season.pts} 分 / ${p.season.reb} 板 / ${p.season.ast} 助
`).join(""); renderPager(d, "players"); } /* 通用分页控件 */ function renderPager(d, key) { const pages = Math.max(1, Math.ceil(d.total / d.size)); const cur = d.page; const el = document.getElementById(`pager-${key}`); if (!el) return; const nums = []; for (let i = 1; i <= pages; i++) { if (pages > 9 && i !== 1 && i !== pages && Math.abs(i - cur) > 2) { if (nums[nums.length - 1] !== "…") nums.push("…"); continue; } nums.push(i); } el.innerHTML = ` ${nums.map((n) => n === "…" ? `` : ``).join("")} 共 ${d.total} 条`; } function goPage(key, p) { if (key === "players") { playersPage = p; loadPlayers(); } else if (key === "games") { gamesPage = p; loadGames(); } else if (key === "news") { newsPage = p; loadNews(); } else if (key === "persons") { personsPage = p; loadPersons(); } } $("#btn-players").addEventListener("click", () => { playersQ = $("#search-players").value.trim(); playersPage = 1; loadPlayers(); }); $("#search-players").addEventListener("keydown", (e) => { if (e.key === "Enter") $("#btn-players").click(); }); $$("#view-players .btn.small").forEach((b) => b.addEventListener("click", () => { $$("#view-players .btn.small").forEach((x) => x.classList.remove("active")); b.classList.add("active"); playersPos = b.dataset.pos; playersPage = 1; loadPlayers(); })); async function openPlayer(id) { const p = await getJSON(`/api/players/${id}`); openModal(`

${esc(p.name)} ${esc(p.name_en)}

${esc(p.team || "自由球员")} · ${esc(p.position)} · #${p.number} · ${esc(p.country)}
身高${p.height_cm} cm
体重${p.weight_kg} kg
选秀${esc(p.draft)}
年薪$${(p.salary_m / 100).toFixed(2)} 亿
本季场均${p.season.pts}分 ${p.season.reb}板 ${p.season.ast}助
本季防守${p.season.stl}断 ${p.season.blk}帽
生涯场均${p.career.pts}分 ${p.career.reb}板 ${p.career.ast}助
生涯场次${p.career.games} 场
🏅 荣誉
${esc(p.awards || "暂无")}
📝 简介
${esc(p.bio || "暂无")}
${DL("player", p.id)}
`); } /* ================= 比赛 ================= */ let gamesPage = 1, gamesQ = "", gamesStatus = ""; async function loadGames() { const d = await getJSON(`/api/games?q=${encodeURIComponent(gamesQ)}&status=${gamesStatus}&page=${gamesPage}&size=12`); const games = d.results || []; $("#list-games").innerHTML = games.map((g) => { const finished = g.status === "finished"; const score = finished ? `${g.away_score} : ${g.home_score}` : "VS"; return `
${esc(g.round_name)} ${finished ? "已结束" : g.status === "scheduled" ? "未开始" : "进行中"}
${esc(g.away_team)} ${score} ${esc(g.home_team)}
🕐 ${esc(g.game_time)} · ${esc(g.venue)} · ${esc(g.broadcast)}
`; }).join("") || '
没有符合条件的比赛
'; renderPager(d, "games"); } $$("#view-games .btn.small").forEach((b) => b.addEventListener("click", () => { $$("#view-games .btn.small").forEach((x) => x.classList.remove("active")); b.classList.add("active"); gamesStatus = b.dataset.status; gamesPage = 1; loadGames(); })); $("#btn-games").addEventListener("click", () => { gamesQ = $("#search-games").value.trim(); gamesPage = 1; loadGames(); }); $("#search-games").addEventListener("keydown", (e) => { if (e.key === "Enter") $("#btn-games").click(); }); async function openGame(id) { const g = await getJSON(`/api/games/${id}`); const finished = g.status === "finished"; const head = finished ? `
${esc(g.away_team)} ${g.away_score} : ${g.home_score} ${esc(g.home_team)}
` : `
${esc(g.away_team)} VS ${esc(g.home_team)}
`; const stats = (g.box_score || []).map((s) => ` ${esc(s.team_name)}${esc(s.player_name)} ${s.points}${s.rebounds}${s.assists} ${s.steals}${s.blocks}${s.minutes}`).join(""); openModal(`

${esc(g.round_name)}

${head}
🕐 ${esc(g.game_time)} · ${esc(g.venue)} · ${esc(g.broadcast)}
${stats ? `
📊 球员技术统计
${stats}
球队球员得分篮板助攻抢断盖帽分钟
` : ""}
${DL("game", g.id)}
`); } /* ================= 新闻 ================= */ let newsPage = 1, newsQ = "", newsKind = "", newsTag = ""; async function loadNews() { const d = await getJSON(`/api/news?q=${encodeURIComponent(newsQ)}&kind=${newsKind}&tag=${encodeURIComponent(newsTag)}&page=${newsPage}&size=10`); const news = d.results || []; $("#list-news").innerHTML = news.map((n) => `
${n.kind === "wiki" ? "百科" : "新闻"}

${esc(n.title)}

🕐 ${esc(n.publish_time)} · ${esc(n.source)}${n.author ? " · " + esc(n.author) : ""}
${esc(n.summary || n.content || "")}…
`).join("") || '
没有符合条件的新闻
'; renderPager(d, "news"); } $("#btn-news").addEventListener("click", () => { newsQ = $("#search-news").value.trim(); newsPage = 1; loadNews(); }); $("#search-news").addEventListener("keydown", (e) => { if (e.key === "Enter") $("#btn-news").click(); }); $$("#view-news .btn.small").forEach((b) => b.addEventListener("click", () => { $$("#view-news .btn.small").forEach((x) => x.classList.remove("active")); b.classList.add("active"); newsKind = b.dataset.kind; newsTag = b.dataset.tag; newsPage = 1; loadNews(); })); async function openNews(id) { const n = await getJSON(`/api/news/${id}`); openModal(`

${esc(n.title)}

🕐 ${esc(n.publish_time)} · ${esc(n.source)} · ${esc(n.author || "")} · ${n.kind === "wiki" ? "百科词条" : "新闻"}
${esc(n.content).replace(/\n/g, "
")}
${DL("news", n.id)}
`); } /* ================= 人物 ================= */ let personsPage = 1, personsQ = "", personsRole = ""; async function loadPersons() { const d = await getJSON(`/api/persons?q=${encodeURIComponent(personsQ)}&role=${personsRole}&page=${personsPage}&size=12`); const persons = d.results || []; $("#grid-persons").innerHTML = persons.map((p) => `

${esc(p.name)} ${esc(p.name_en || "")}

${esc(p.role_cn)}${esc(p.title || "")}${p.team ? " · " + esc(p.team) : ""}
${esc((p.bio || "").slice(0, 60))}…
`).join(""); renderPager(d, "persons"); } $$("#view-persons .btn.small").forEach((b) => b.addEventListener("click", () => { $$("#view-persons .btn.small").forEach((x) => x.classList.remove("active")); b.classList.add("active"); personsRole = b.dataset.role; personsPage = 1; loadPersons(); })); $("#btn-persons").addEventListener("click", () => { personsQ = $("#search-persons").value.trim(); personsPage = 1; loadPersons(); }); $("#search-persons").addEventListener("keydown", (e) => { if (e.key === "Enter") $("#btn-persons").click(); }); async function openPerson(id) { const p = await getJSON(`/api/persons/${id}`); openModal(`

${esc(p.name)} ${esc(p.name_en || "")}

${esc(p.role_cn)} · ${esc(p.title || "")}${p.team ? " · " + esc(p.team) : ""}
📝 简介
${esc(p.bio || "暂无")}
🏅 成就
${esc(p.achievements || "暂无")}
${DL("person", p.id)}
`); } /* ================= 排名 ================= */ let standingsConf = ""; let standingsSeason = ""; let bracketData = null; async function loadSeasons() { try { const ss = await getJSON("/api/seasons"); if (ss.length) { standingsSeason = ss[0]; // 默认最新赛季 $("#season-select").innerHTML = ss.map((s) => ``).join(""); loadStandings(); loadPlayoffs(); } } catch (e) {} } $("#season-select").addEventListener("change", (e) => { standingsSeason = e.target.value; loadStandings(); loadPlayoffs(); }); async function loadStandings() { const rows = await getJSON(`/api/standings?conf=${encodeURIComponent(standingsConf)}&season=${encodeURIComponent(standingsSeason || "")}`); const groups = {}; rows.forEach((r) => { (groups[r.conference] = groups[r.conference] || []).push(r); }); $("#wrap-standings").innerHTML = Object.entries(groups).map(([conf, list]) => `

${conf}赛区

${list.map((r) => ``).join("")}
排名球队胜率战绩
${r.rank}${esc(r.team)} ${r.wins}${r.losses} ${r.win_pct}%${r.wins}-${r.losses}
`).join(""); } $$("#view-standings .btn.small").forEach((b) => b.addEventListener("click", () => { $$("#view-standings .btn.small").forEach((x) => x.classList.remove("active")); b.classList.add("active"); standingsConf = b.dataset.conf; loadStandings(); })); /* ================= 季后赛对阵图 ================= */ async function loadPlayoffs() { try { bracketData = await getJSON(`/api/playoffs?season=${encodeURIComponent(standingsSeason || "")}`); renderBracket(); } catch (e) { $("#wrap-bracket").innerHTML = `
对阵图加载失败:${esc(e.message)}
`; } } function matchHtml(m) { if (!m) return ""; const gid = (m.games || [])[0]; const row = (side) => `
${side.advance ? "🏆 " : ""}${esc(side.team)}${side.wins}
`; return `
${row(m.home)}${row(m.away)}
`; } function renderBracket() { const r = (bracketData?.rounds) || {}; const first = [...(r.first?.["东部"] || []), ...(r.first?.["西部"] || [])]; const semi = [...(r.semi?.["东部"] || []), ...(r.semi?.["西部"] || [])]; const conf = [r.conf?.["东部"], r.conf?.["西部"]].filter(Boolean); const final = r.final; const col = (title, items) => `
${title}
${items.map(matchHtml).join("")}
`; $("#wrap-bracket").innerHTML = `

🏆 季后赛对阵图

${esc(bracketData?.season || standingsSeason)} 赛季
${col("首轮", first)} ${col("半决赛", semi)} ${col("分区决赛", conf)} ${col("总决赛", final ? [final] : [])}
💡 点击对阵可查看比赛详情 · 胜者显示 🏆
`; } /* ================= 弹窗 ================= */ function openModal(html) { $("#modal-body").innerHTML = html; $("#modal").classList.remove("hidden"); } /* 详情页链接(弹窗底部) */ const DL = (type, id) => `查看完整详情 →`; $("#modal-close").addEventListener("click", () => $("#modal").classList.add("hidden")); $("#modal").addEventListener("click", (e) => { if (e.target.id === "modal") $("#modal").classList.add("hidden"); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") $("#modal").classList.add("hidden"); });