Download src/view_home.py from sidaier/fc: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/sidaier/fc/resolve/main/src/view_home.py
- Command line
-
hf download hf://spaces/sidaier/fc/src/view_home.py
-
curl -L -o view_home.py https://huggingface.co/spaces/sidaier/fc/resolve/main/src/view_home.py
10.4 kB
| """主页视图:球队战绩卡、每场战报记分牌、冲击力榜单(Hero 聚光台 + 奖牌行 + 能量条)。""" | |
| from __future__ import annotations | |
| from typing import Any, Callable, Optional | |
| from shiny import ui | |
| from . import config, theme | |
| from .view_common import _medal | |
| from .view_me import fifa_card_tag | |
| def home_view( | |
| summary: dict[str, Any], | |
| matches: list[dict[str, Any]], | |
| agg: list[dict[str, Any]], | |
| me_id: Optional[int], | |
| contributions: dict[int, dict[str, list[str]]], | |
| mvp: list[dict[str, Any]], | |
| mvp_summary: Optional[dict[int, dict[str, Any]]] = None, | |
| dmvp: Optional[list[dict[str, Any]]] = None, | |
| fifa_cards: Optional[list[dict[str, Any]]] = None, | |
| ) -> ui.Tag: | |
| stats = ui.div( | |
| theme.stat(summary["played"], "场次", accent="cream"), | |
| theme.stat( | |
| f'{summary["wins"]}-{summary["draws"]}-{summary["losses"]}', "胜-平-负" | |
| ), | |
| theme.stat( | |
| f'{summary["gf"]}-{summary["ga"]}', "进球-失球", f'净胜 {summary["gd"]:+d}', "blue" | |
| ), | |
| theme.stat(summary["points"], "积分"), | |
| theme.stat(f'{summary["win_rate"]}%', "胜率", accent="green"), | |
| class_="sde-stats", | |
| ) | |
| scorers = sorted(agg, key=lambda x: (-x["goals"], -x["assists"], -x["attended"])) | |
| assisters = sorted(agg, key=lambda x: (-x["assists"], -x["goals"])) | |
| attend = sorted(agg, key=lambda x: (-x["attended"], -x["goals"])) | |
| keepers = sorted( | |
| [r for r in agg if r.get("is_goalkeeper")], | |
| key=lambda x: (-x["saves"], -x["attended"]), | |
| ) | |
| board_cards = [ | |
| theme.card("射手榜", _impact_board( | |
| scorers, "射手王", lambda r: r["goals"], "球", "", | |
| lambda r: f'助攻 {r["assists"]} · 出场 {r["attended"]}/{r["total"]}', me_id, | |
| ), icon="⚽"), | |
| theme.card("助攻榜", _impact_board( | |
| assisters, "助攻王", lambda r: r["assists"], "助攻", "c-blue", | |
| lambda r: f'进球 {r["goals"]} · 出场 {r["attended"]}/{r["total"]}', me_id, | |
| ), icon="🎯"), | |
| theme.card("出勤榜", _impact_board( | |
| attend, "全勤铁人", lambda r: r["attended"], "场", "c-green", | |
| lambda r: f'共 {r["total"]} 场 · 进球+助攻 {r["ga"]}', me_id, | |
| ), icon="📋"), | |
| theme.card("最佳球员 MVP", _impact_board( | |
| mvp, "最佳球员", lambda r: r["mvps"], "次", "", | |
| None, me_id, | |
| ), icon="🏅", sub="每场由领队结算 · 计获得次数"), | |
| theme.card("铁血防守 MVP", _impact_board( | |
| dmvp or [], "铁血防守", lambda r: r["dmvps"], "次", "c-red", | |
| None, me_id, | |
| ), icon="🛡️", sub="每场由领队结算 · 计获得次数"), | |
| ] | |
| widths = [6, 6, 6, 6, 6] | |
| if keepers: # 有门将才显示扑救榜 | |
| board_cards.append( | |
| theme.card("扑救榜", _impact_board( | |
| keepers, "扑救之神", lambda r: r["saves"], "扑救", "c-blue", | |
| lambda r: f'出场 {r["attended"]}/{r["total"]}', me_id, | |
| ), icon="🧤", sub="仅门将") | |
| ) | |
| widths = [6, 6, 6, 6, 6, 6] | |
| boards = ui.layout_columns(*board_cards, col_widths=widths, fill=False) | |
| card_wall = None | |
| if fifa_cards: | |
| top8 = fifa_cards[:8] # all_fifa_cards 已按 OVR 降序;每场数据更新后席位自动洗牌 | |
| card_wall = theme.card( | |
| "本队 TOP 8 球星卡", | |
| ui.div( | |
| *[fifa_card_tag(c, is_me=(c["user_id"] == me_id), flip_prefix="flipw") | |
| for c in top8], | |
| class_="sde-fifa-wall", | |
| ), | |
| icon="🃏", | |
| sub="OVR 前 8 上榜 · 每场数据更新后席位洗牌 · 紫≥88 金≥74 银≥66 · ★=我 · " | |
| "点卡片翻面看战绩 · 自己的卡在「我的数据」页", | |
| ) | |
| return ui.div( | |
| theme.card("球队战绩", stats, icon="🏆"), | |
| theme.card( | |
| "每场比分", _matches_list(matches, contributions, mvp_summary or {}), | |
| icon="📅", sub="比分由领队录入;进球/助攻为队员自填汇总", | |
| ), | |
| boards, | |
| card_wall, | |
| ) | |
| # --------------------------------------------------------------- 战报记分牌 | |
| _RECENT_N = 3 # 默认展示最近场次数,其余折叠 | |
| def _matches_list( | |
| matches: list[dict[str, Any]], | |
| contributions: dict[int, dict[str, list[str]]], | |
| mvp_summary: dict[int, dict[str, Any]], | |
| ) -> ui.Tag: | |
| if not matches: | |
| return ui.div("还没有比赛记录。", class_="sde-empty") | |
| def card(m): | |
| return _scorecard(m, contributions.get(m["id"], {}), mvp_summary.get(m["id"])) | |
| recent, older = matches[:_RECENT_N], matches[_RECENT_N:] | |
| children: list[Any] = [card(m) for m in recent] | |
| if older: | |
| # <details> 原生折叠:摘要行即按钮,展开显示其余场次(零 JS) | |
| children.append( | |
| ui.tags.details( | |
| ui.tags.summary( | |
| ui.span(f"展开更早的 {len(older)} 场", class_="txt-open"), | |
| ui.span("收起早期场次", class_="txt-close"), | |
| ui.span("▾", class_="arr"), | |
| class_="sde-more-btn", | |
| ), | |
| ui.div(*[card(m) for m in older], class_="sde-mc-list"), | |
| class_="sde-mc-more", | |
| ) | |
| ) | |
| return ui.div(*children, class_="sde-mc-list") | |
| def _scorecard( | |
| m: dict[str, Any], c: dict[str, list[str]], mvp: Optional[dict[str, Any]] | |
| ) -> ui.Tag: | |
| """一场比赛的战报卡:结果色边+光晕、居中大比分、结果大徽章、分行明细。 | |
| 明细区每类一行:行首固定标签(最佳球员/铁血防守/进球/助攻)+ 该类芯片。 | |
| """ | |
| res = m["result"] | |
| def detail_row(icon: str, label: str, names: list[str], chip_cls: str): | |
| return ui.div( | |
| ui.span(ui.span(icon, class_="ic"), label, class_=f"sde-dl {chip_cls}"), | |
| ui.div( | |
| *[ui.span(n, class_=f"sde-chip {chip_cls}") for n in names], | |
| class_="sde-dr", | |
| ), | |
| class_="sde-detail-row", | |
| ) | |
| rows: list[Any] = [] | |
| if m.get("mvp_locked") and mvp: | |
| if mvp.get("names"): | |
| rows.append(detail_row("🏅", "最佳球员", mvp["names"], "mvp")) | |
| if mvp.get("dmvp_name"): | |
| rows.append(detail_row("🛡️", "铁血防守", [mvp["dmvp_name"]], "dmvp")) | |
| if c.get("goals"): | |
| rows.append(detail_row("⚽", "进球", c["goals"], "goal")) | |
| if c.get("assists"): | |
| rows.append(detail_row("🅰️", "助攻", c["assists"], "assist")) | |
| return ui.div( | |
| ui.span(res, class_=f"sde-sc-badge {res}"), | |
| ui.div( | |
| ui.span(m["date"], class_="d"), | |
| theme.ha_pill(m["home_away"]), | |
| (ui.span(m["note"], class_="note") if m["note"] else None), | |
| class_="sde-sc-meta", | |
| ), | |
| ui.div( | |
| ui.div(ui.div(config.TEAM_NAME, class_="tn"), class_="sde-sc-side us"), | |
| ui.div( | |
| ui.span(str(m["our"]), class_="n us"), | |
| ui.span(":", class_="sep"), | |
| ui.span(str(m["opp"]), class_="n them"), | |
| class_="sde-sc-score", | |
| ), | |
| ui.div(ui.div(m["opponent"], class_="tn"), class_="sde-sc-side them"), | |
| class_="sde-sc-main", | |
| ), | |
| (ui.div(*rows, class_="sde-sc-detail") if rows else None), | |
| class_=f"sde-scorecard res-{res}", | |
| ) | |
| # --------------------------------------------------------------- 冲击力榜单 | |
| def _impact_board( | |
| rows: list[dict[str, Any]], | |
| champ: str, | |
| val: Callable[[dict], int], | |
| unit: str, | |
| color: str, | |
| sub: Optional[Callable[[dict], str]], | |
| me_id: Optional[int], | |
| ) -> ui.Tag: | |
| """榜首 Hero 聚光台 + 2/3 名奖牌行 + 能量条;空数据显示占位。 | |
| 只列「该项有记录(值>0)」的球员:榜首 Hero + 2/3 名常驻,第 4 名起折叠, | |
| 展开即显示全部有记录者(不设上限),无记录的不上榜。 | |
| """ | |
| rows = [r for r in rows if val(r) > 0] or rows[:0] | |
| if not rows: | |
| return ui.div(ui.div("暂无数据 · 虚位以待", class_="sde-empty"), class_="sde-board") | |
| top, rest = rows[0], rows[1:] | |
| maxv = max(1, val(top)) | |
| def jersey(r): | |
| return ui.span(f'#{r["jersey"]}', class_="sde-jersey") if r.get("jersey") else None | |
| def me_tag(r): | |
| return ui.span("我", class_="sde-me-tag") if r["user_id"] == me_id else None | |
| hero = ui.div( | |
| ui.span("👑", class_="crown"), | |
| ui.div(champ, class_="title"), | |
| ui.div( | |
| jersey(top), ui.span(top["name"], class_="nm"), me_tag(top), | |
| ui.span( | |
| ui.span(str(val(top)), class_="n"), ui.span(unit, class_="u"), | |
| class_="big", | |
| ), | |
| class_="who", | |
| ), | |
| (ui.div(sub(top), class_="sub2") if sub else None), | |
| class_=f"sde-hero {color}", | |
| ) | |
| def brow(i, r): | |
| cls = "sde-brow" + (" r2" if i == 2 else " r3" if i == 3 else "") | |
| if color: | |
| cls += f" {color}" | |
| if r["user_id"] == me_id: | |
| cls += " is-me" | |
| w = max(5, round(val(r) / maxv * 100)) | |
| return ui.div( | |
| ui.span(_medal(i), class_="rk"), | |
| jersey(r), | |
| ui.span(r["name"], class_="nm"), | |
| me_tag(r), | |
| ui.span(str(val(r)), ui.span(unit, class_="u"), class_="val"), | |
| ui.span(class_="bar", style=f"--w:{w}%"), | |
| class_=cls, | |
| ) | |
| # 榜首 Hero + 2/3 名奖牌行常驻;第 4 名起折叠(<details>,零 JS) | |
| shown = [brow(i, r) for i, r in enumerate(rest[:2], 2)] | |
| hidden = [brow(i, r) for i, r in enumerate(rest[2:], 4)] | |
| tail: list[Any] = [] | |
| if hidden: | |
| tail.append( | |
| ui.tags.details( | |
| ui.tags.summary( | |
| ui.span(f"展开完整榜单(还有 {len(hidden)} 人)", class_="txt-open"), | |
| ui.span("收起榜单", class_="txt-close"), | |
| ui.span("▾", class_="arr"), | |
| class_="sde-more-btn", | |
| ), | |
| ui.div(*hidden, class_="sde-board-more-list"), | |
| class_="sde-board-more", | |
| ) | |
| ) | |
| return ui.div(hero, *shown, *tail, class_="sde-board") | |