Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d59c9a975 | ||
|
|
14317a44a2 |
@@ -129,8 +129,10 @@ def api_team(tid):
|
||||
@app.route("/api/players")
|
||||
def api_players():
|
||||
q = request.args.get("q", "")
|
||||
r = tools.search_players(q, limit=int(request.args.get("limit", 60)))
|
||||
return jsonify(r.get("results", []))
|
||||
position = request.args.get("position", "")
|
||||
page = max(1, int(request.args.get("page", 1)))
|
||||
size = min(60, max(1, int(request.args.get("size", 24))))
|
||||
return jsonify(tools.list_players(q, position=position, page=page, size=size))
|
||||
|
||||
|
||||
@app.route("/api/players/<int:pid>")
|
||||
@@ -147,12 +149,9 @@ def api_player(pid):
|
||||
def api_games():
|
||||
q = request.args.get("q", "")
|
||||
status = request.args.get("status", "")
|
||||
limit = int(request.args.get("limit", 30))
|
||||
r = tools.search_games(q, limit=limit)
|
||||
results = r.get("results", [])
|
||||
if status:
|
||||
results = [g for g in results if g["status"] == status]
|
||||
return jsonify(results)
|
||||
page = max(1, int(request.args.get("page", 1)))
|
||||
size = min(40, max(1, int(request.args.get("size", 12))))
|
||||
return jsonify(tools.list_games(q, status=status, page=page, size=size))
|
||||
|
||||
|
||||
@app.route("/api/games/<int:gid>")
|
||||
|
||||
+64
-15
@@ -80,8 +80,18 @@ $$(".tab").forEach((t) => t.addEventListener("click", () => {
|
||||
|
||||
function loadView(v) {
|
||||
if (v === "teams") loadTeams();
|
||||
else if (v === "players") loadPlayers("");
|
||||
else if (v === "games") loadGames("");
|
||||
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") loadNews("");
|
||||
else if (v === "persons") loadPersons("");
|
||||
else if (v === "standings") {
|
||||
@@ -363,17 +373,55 @@ async function openTeam(id) {
|
||||
}
|
||||
|
||||
/* ================= 球员 ================= */
|
||||
async function loadPlayers(q) {
|
||||
const players = await getJSON(`/api/players?q=${encodeURIComponent(q)}&limit=60`);
|
||||
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) => `
|
||||
<div class="card" onclick="openPlayer(${p.id})">
|
||||
<h3>${esc(p.name)} <span class="en">${esc(p.position)} #${p.number}</span></h3>
|
||||
<h3>${esc(p.name)} <span class="en">${esc(POS_CN[p.position] || p.position)} #${p.number}</span></h3>
|
||||
<div class="en">${esc(p.team || "")} · ${esc(p.country)}</div>
|
||||
<div class="meta">本季:<span class="big-num">${p.season.pts}</span> 分 / ${p.season.reb} 板 / ${p.season.ast} 助</div>
|
||||
</div>`).join("");
|
||||
renderPager(d, "players");
|
||||
}
|
||||
$("#btn-players").addEventListener("click", () => loadPlayers($("#search-players").value.trim()));
|
||||
|
||||
/* 通用分页控件 */
|
||||
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 = `
|
||||
<button ${cur <= 1 ? "disabled" : ""} onclick="goPage('${key}', ${cur - 1})">← 上一页</button>
|
||||
${nums.map((n) => n === "…" ? `<span>…</span>` : `<button class="${n === cur ? "active" : ""}" onclick="goPage('${key}', ${n})">${n}</button>`).join("")}
|
||||
<button ${cur >= pages ? "disabled" : ""} onclick="goPage('${key}', ${cur + 1})">下一页 →</button>
|
||||
<span>共 ${d.total} 条</span>`;
|
||||
}
|
||||
function goPage(key, p) {
|
||||
if (key === "players") { playersPage = p; loadPlayers(); }
|
||||
else if (key === "games") { gamesPage = p; loadGames(); }
|
||||
}
|
||||
|
||||
$("#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(`
|
||||
@@ -396,12 +444,11 @@ async function openPlayer(id) {
|
||||
}
|
||||
|
||||
/* ================= 比赛 ================= */
|
||||
let gameStatusFilter = "";
|
||||
async function loadGames(q) {
|
||||
const url = `/api/games?q=${encodeURIComponent(q)}&limit=40`;
|
||||
const games = await getJSON(url);
|
||||
const list = games.filter((g) => !gameStatusFilter || g.status === gameStatusFilter);
|
||||
$("#list-games").innerHTML = list.map((g) => {
|
||||
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 ? `<span class="score-big">${g.away_score}</span> : <span class="score-big">${g.home_score}</span>` : "VS";
|
||||
return `<div class="list-item" onclick="openGame(${g.id})">
|
||||
@@ -417,14 +464,16 @@ async function loadGames(q) {
|
||||
<div class="meta">🕐 ${esc(g.game_time)} · ${esc(g.venue)} · ${esc(g.broadcast)}</div>
|
||||
</div>`;
|
||||
}).join("") || '<div class="meta">没有符合条件的比赛</div>';
|
||||
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");
|
||||
gameStatusFilter = b.dataset.status;
|
||||
loadGames($("#search-games").value.trim());
|
||||
gamesStatus = b.dataset.status;
|
||||
gamesPage = 1;
|
||||
loadGames();
|
||||
}));
|
||||
$("#btn-games").addEventListener("click", () => loadGames($("#search-games").value.trim()));
|
||||
$("#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}`);
|
||||
|
||||
+12
-2
@@ -58,8 +58,17 @@
|
||||
<!-- ================= 数据浏览 ================= -->
|
||||
<section id="view-teams" class="view"><div class="grid" id="grid-teams"></div></section>
|
||||
<section id="view-players" class="view">
|
||||
<div class="toolbar"><input class="search" id="search-players" placeholder="搜索球员(中文/英文/球队)"><button class="btn" id="btn-players">搜索</button></div>
|
||||
<div class="toolbar">
|
||||
<button class="btn small active" data-pos="">全部</button>
|
||||
<button class="btn small" data-pos="PG">控卫</button>
|
||||
<button class="btn small" data-pos="SG">分卫</button>
|
||||
<button class="btn small" data-pos="SF">小前</button>
|
||||
<button class="btn small" data-pos="PF">大前</button>
|
||||
<button class="btn small" data-pos="C">中锋</button>
|
||||
<input class="search" id="search-players" placeholder="搜索球员(中文/英文/球队)"><button class="btn" id="btn-players">搜索</button>
|
||||
</div>
|
||||
<div class="grid" id="grid-players"></div>
|
||||
<div class="pager" id="pager-players"></div>
|
||||
</section>
|
||||
<section id="view-games" class="view">
|
||||
<div class="toolbar">
|
||||
@@ -69,7 +78,8 @@
|
||||
<input class="search" id="search-games" placeholder="搜索:如 湖人 最近">
|
||||
<button class="btn" id="btn-games">搜索</button>
|
||||
</div>
|
||||
<div class="list" id="list-games"></div>
|
||||
<div class="grid games-grid" id="list-games"></div>
|
||||
<div class="pager" id="pager-games"></div>
|
||||
</section>
|
||||
<section id="view-news" class="view">
|
||||
<div class="toolbar"><input class="search" id="search-news" placeholder="搜索新闻关键词"><button class="btn" id="btn-news">搜索</button></div>
|
||||
|
||||
@@ -124,6 +124,18 @@ main { flex: 1; width: 100%; max-width: 1200px; margin: 0 auto; padding: 20px 16
|
||||
.btn.small.active { color: var(--orange2); border-color: var(--orange2); background: rgba(249,115,22,.08); }
|
||||
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; }
|
||||
|
||||
/* ---------- 分页控件 ---------- */
|
||||
.pager { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 18px; color: var(--sub); font-size: 12.5px; flex-wrap: wrap; }
|
||||
.pager button { background: var(--bg2); border: 1px solid var(--line); color: var(--txt); border-radius: 999px; padding: 6px 14px; cursor: pointer; font-size: 12.5px; transition: .15s; }
|
||||
.pager button:hover:not(:disabled) { border-color: var(--orange2); color: var(--orange2); }
|
||||
.pager button.active { background: linear-gradient(135deg, #f97316, #ea580c); color: #fff; border-color: transparent; }
|
||||
.pager button:disabled { opacity: .35; cursor: default; }
|
||||
.pager span { margin: 0 4px; }
|
||||
|
||||
/* ---------- 比赛网格卡片(固定宽度,宽度足够一行多个) ---------- */
|
||||
.games-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); align-items: stretch; }
|
||||
.games-grid .list-item { display: flex; flex-direction: column; justify-content: space-between; gap: 8px; height: 100%; padding: 14px 16px; }
|
||||
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; cursor: pointer; transition: .18s; }
|
||||
.card:hover { transform: translateY(-3px); border-color: var(--orange2); box-shadow: 0 6px 20px rgba(0,0,0,.35); }
|
||||
.card h3 { font-size: 15.5px; margin-bottom: 4px; }
|
||||
|
||||
@@ -157,6 +157,26 @@ def get_player(player_id):
|
||||
return _fmt_player(p) if p else None
|
||||
|
||||
|
||||
# ================================================================== 球员列表(前台分页版)
|
||||
def list_players(q="", position="", page=1, size=24):
|
||||
"""前台球员列表:关键词 + 位置筛选 + 分页(区别于 search_players 工具版)"""
|
||||
q = expand_aliases((q or "").strip())
|
||||
conds, args = [], []
|
||||
if q:
|
||||
like = f"%{fuzzy(q)}%"
|
||||
conds.append("(p.name LIKE ? ESCAPE '\\' OR p.name_en LIKE ? ESCAPE '\\' OR t.name LIKE ? ESCAPE '\\')")
|
||||
args += [like, like, like]
|
||||
if position:
|
||||
conds.append("p.position=?")
|
||||
args.append(position)
|
||||
where = ("WHERE " + " AND ".join(conds)) if conds else ""
|
||||
total = query_one(f"SELECT COUNT(*) AS c FROM players p LEFT JOIN teams t ON p.team_id=t.id {where}", args)["c"]
|
||||
rows = query(f"""SELECT p.*, t.name AS team_name FROM players p LEFT JOIN teams t ON p.team_id=t.id
|
||||
{where} ORDER BY p.season_pts DESC LIMIT ? OFFSET ?""",
|
||||
args + [size, (page - 1) * size])
|
||||
return {"results": [_fmt_player(r) for r in rows], "total": total, "page": page, "size": size}
|
||||
|
||||
|
||||
# ================================================================== 比赛
|
||||
_TEAM_PAT = None
|
||||
|
||||
@@ -259,6 +279,18 @@ def get_game_detail(game_id):
|
||||
return detail
|
||||
|
||||
|
||||
# ================================================================== 比赛列表(前台分页版)
|
||||
def list_games(q="", status="", page=1, size=12):
|
||||
"""前台比赛列表:关键词 + 状态筛选 + 分页(数据量小,全量匹配后切片)"""
|
||||
r = search_games(q, limit=500)
|
||||
results = r.get("results", [])
|
||||
if status:
|
||||
results = [g for g in results if g["status"] == status]
|
||||
total = len(results)
|
||||
start = (page - 1) * size
|
||||
return {"results": results[start:start + size], "total": total, "page": page, "size": size}
|
||||
|
||||
|
||||
# ================================================================== 排名
|
||||
def search_standings(query_text, limit=20, season=None):
|
||||
q = (query_text or "").strip()
|
||||
|
||||
Reference in New Issue
Block a user