From 0a3b0756440c76e93764d8c4251b4ec81fd9afda Mon Sep 17 00:00:00 2001 From: hz4th_coder Date: Mon, 17 Aug 2026 23:16:05 +0800 Subject: [PATCH] =?UTF-8?q?v1.2.5=20=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E7=82=B9=E5=87=BB=E8=A1=A8=E5=A4=B4=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=EF=BC=9A=E4=BB=BB=E6=84=8F=E5=AD=97=E6=AE=B5=E5=8D=87?= =?UTF-8?q?=E5=BA=8F/=E9=99=8D=E5=BA=8F=E5=88=87=E6=8D=A2(=E2=96=B2?= =?UTF-8?q?=E2=96=BC=E6=8C=87=E7=A4=BA)=EF=BC=8C=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=99=BD=E5=90=8D=E5=8D=95=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E9=98=B2=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin.py | 13 +++++++++++-- static/admin.html | 3 +++ static/admin.js | 23 +++++++++++++++++++---- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/admin.py b/admin.py index b23806c..79e22e4 100644 --- a/admin.py +++ b/admin.py @@ -191,7 +191,15 @@ def list_rows(table): page = max(1, int(request.args.get("page", 1))) size = min(100, max(1, int(request.args.get("size", 20)))) q = (request.args.get("q") or "").strip() + # 排序:字段白名单校验(防注入),默认 id 降序 cols = _columns(table) + valid_fields = {c["name"] for c in cols} + sort = request.args.get("sort", "") or "id" + if sort not in valid_fields: + sort = "id" + order = (request.args.get("order", "") or "desc").lower() + if order not in ("asc", "desc"): + order = "desc" where, args = "", [] if q: fields = SEARCHABLE.get(table) or () @@ -200,10 +208,11 @@ def list_rows(table): where = "WHERE " + " OR ".join(f"{f} LIKE ? ESCAPE '\\'" for f in fields) args = [like] * len(fields) total = query_one(f"SELECT COUNT(*) AS c FROM {table} {where}", args)["c"] - rows = query(f"SELECT * FROM {table} {where} ORDER BY id DESC LIMIT ? OFFSET ?", + rows = query(f"SELECT * FROM {table} {where} ORDER BY {sort} {order.upper()}, id {order.upper()} LIMIT ? OFFSET ?", args + [size, (page - 1) * size]) return jsonify({"table": table, "cn": TABLES[table], "columns": cols, - "total": total, "page": page, "size": size, "rows": rows}) + "total": total, "page": page, "size": size, "rows": rows, + "sort": sort, "order": order}) def get_row(table, rid): diff --git a/static/admin.html b/static/admin.html index f7267cf..dbd8219 100644 --- a/static/admin.html +++ b/static/admin.html @@ -46,6 +46,9 @@ body { background:var(--bg); color:var(--txt); font-family:"PingFang SC","Micros table { width:100%; border-collapse:collapse; } th, td { padding:9px 11px; text-align:left; font-size:13px; border-bottom:1px solid var(--line); white-space:nowrap; max-width:260px; overflow:hidden; text-overflow:ellipsis; } th { background:var(--bg2); color:var(--sub); font-weight:600; font-size:12px; position:sticky; top:0; } +th.sortable { cursor:pointer; user-select:none; transition:.15s; } +th.sortable:hover { color:var(--orange2); } +th.sort-active { color:var(--orange2); } tr:hover td { background:rgba(249,115,22,.05); } td.num { text-align:center; } .row-ops { display:flex; gap:6px; } diff --git a/static/admin.js b/static/admin.js index 07ff6bb..e773c12 100644 --- a/static/admin.js +++ b/static/admin.js @@ -4,7 +4,7 @@ const $$ = (s) => [...document.querySelectorAll(s)]; const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); let TOKEN = localStorage.getItem("nba_admin_token") || ""; -let CUR = { table: "", page: 1, q: "" }; +let CUR = { table: "", page: 1, q: "", sort: "id", order: "desc" }; let LOOKUPS = {}; // teams/players/leagues/sports → {id: name} /* 字段中文名 */ @@ -79,7 +79,7 @@ function switchPage(p) { $("#page-config").classList.toggle("hidden", p !== "config"); if (p === "stats") loadStats(); else if (p === "config") loadConfig(); - else { CUR = { table: p, page: 1, q: "" }; $("#tbl-search").value = ""; loadTable(); } + else { CUR = { table: p, page: 1, q: "", sort: "id", order: "desc" }; $("#tbl-search").value = ""; loadTable(); } } /* ================= 仪表盘 ================= */ @@ -108,11 +108,15 @@ async function loadLookups() { async function loadTable() { $("#table-title").textContent = TITLE_CN[CUR.table] || CUR.table; - const d = await api(`/api/admin/${CUR.table}?page=${CUR.page}&size=20&q=${encodeURIComponent(CUR.q)}`); + const d = await api(`/api/admin/${CUR.table}?page=${CUR.page}&size=20&q=${encodeURIComponent(CUR.q)}&sort=${encodeURIComponent(CUR.sort)}&order=${CUR.order}`); const cols = d.columns.filter((c) => c.name !== "created_at"); const rows = d.rows; if (!Object.keys(LOOKUPS).length) await loadLookups().catch(() => {}); - const thead = `${cols.map((c) => `${esc(FIELD_CN[CUR.table]?.[c.name] || c.name)}`).join("")}操作`; + const thead = `${cols.map((c) => { + const active = CUR.sort === c.name; + const arrow = active ? (CUR.order === "asc" ? " ▲" : " ▼") : ""; + return `${esc(FIELD_CN[CUR.table]?.[c.name] || c.name)}${arrow}`; + }).join("")}操作`; const tbody = rows.map((r) => { const tds = cols.map((c) => { let v = r[c.name]; @@ -141,6 +145,17 @@ $("#tbl-search-btn").addEventListener("click", () => { CUR.q = $("#tbl-search"). $("#tbl-search").addEventListener("keydown", (e) => { if (e.key === "Enter") $("#tbl-search-btn").click(); }); $("#tbl-add").addEventListener("click", () => openEdit(CUR.table, null)); +/* 表头点击排序:首次点击升序,再点切降序,循环 */ +$("#tbl-wrap").addEventListener("click", (e) => { + const th = e.target.closest("th[data-sort]"); + if (!th) return; + const f = th.dataset.sort; + if (CUR.sort === f) CUR.order = CUR.order === "asc" ? "desc" : "asc"; + else { CUR.sort = f; CUR.order = "asc"; } + CUR.page = 1; + loadTable(); +}); + /* ================= 编辑弹窗 ================= */ let EDIT = { table: "", id: null };