diff --git a/reaction_board/web/queries.py b/reaction_board/web/queries.py index 68f17cd..23937e1 100644 --- a/reaction_board/web/queries.py +++ b/reaction_board/web/queries.py @@ -699,7 +699,7 @@ class DashboardQueries: sort: str = "heat", within_days: int | None = None, year: int | None = None, - growth_days: int | str = 7, + growth_days: int | str = "auto", folder_id: int | None = None, q: str | None = None, page: int = 1, @@ -734,7 +734,7 @@ class DashboardQueries: sort: str = "heat", within_days: int | None = None, year: int | None = None, - growth_days: int | str = 7, + growth_days: int | str = "auto", folder_id: int | None = None, q: str | None = None, page: int = 1, @@ -742,24 +742,29 @@ class DashboardQueries: ) -> dict[str, Any]: if sort not in VIDEO_SORTS: raise ValueError(f"不支持的排序: {sort}") + # auto:仅增长类排序由服务端选窗(配置 home_growth_days;默认先 7 再降 1)。 + # 首页应省略 growth_days,勿自行探查。其它排序把 auto 当作 7(兼容旧默认)。 if growth_days == "auto": - preferred = self.config_store.get().analytics.home_growth_days - if preferred in (1, 7): - growth_days = int(preferred) + if sort in {"growth", "growth_rate"}: + preferred = self.config_store.get().analytics.home_growth_days + if preferred in (1, 7): + growth_days = int(preferred) + else: + trial = self._list_videos_uncached( + sort=sort, + within_days=within_days, + year=year, + growth_days=7, + folder_id=folder_id, + q=q, + page=page, + page_size=page_size, + ) + if trial.get("available") and int(trial.get("total") or 0) > 0: + return trial + growth_days = 1 else: - trial = self._list_videos_uncached( - sort=sort, - within_days=within_days, - year=year, - growth_days=7, - folder_id=folder_id, - q=q, - page=page, - page_size=page_size, - ) - if trial.get("available") and int(trial.get("total") or 0) > 0: - return trial - growth_days = 1 + growth_days = 7 growth_days = int(growth_days) if growth_days < 1 or growth_days > 365: raise ValueError("growth_days 必须在 1~365 之间") @@ -942,6 +947,8 @@ class DashboardQueries: "within_days": within_days, "year": year, "growth_days": growth_days, + # days:与 growth_days 同步;首页 auto 时为 1|7,供前端链接/展示 + "days": growth_days, "page": page, "page_size": page_size, "total": total, diff --git a/reaction_board/web/server.py b/reaction_board/web/server.py index 45dc580..ba8f3f6 100644 --- a/reaction_board/web/server.py +++ b/reaction_board/web/server.py @@ -650,11 +650,10 @@ def _year_param(query: Mapping[str, list[str]]) -> int | None: def _growth_days_param(query: Mapping[str, list[str]]) -> int | str: - """榜单传 1~365;首页可传 auto(由查询层按 home_growth_days 解析)。""" + """省略或 auto:查询层按 home_growth_days 解析(默认 7→1)。 + 榜单应显式传 1~365。""" raw = _optional_str(query, "growth_days") - if raw is None: - return 7 - if raw == "auto": + if raw is None or raw == "auto": return "auto" try: value = int(raw) diff --git a/reaction_board/web/static/views/home.js b/reaction_board/web/static/views/home.js index 714215f..05acf2a 100644 --- a/reaction_board/web/static/views/home.js +++ b/reaction_board/web/static/views/home.js @@ -47,8 +47,8 @@ export async function renderHome(root) { api("/api/overview"), api("/api/videos", { sort: "published", within_days: 14, limit: 6 }), api("/api/videos", { sort: "heat", limit: 6 }), - // auto:服务端按 analytics.home_growth_days 解析;默认先试 7 天再降到 1 天 - api("/api/videos", { sort: "growth", growth_days: "auto", limit: 6 }), + // 不带天数:后端按 home_growth_days(默认 auto:有 7 日则 7,否则 1)决定,响应带 days + api("/api/videos", { sort: "growth", limit: 6 }), ]); const fans = overview.fans; @@ -60,7 +60,8 @@ export async function renderHome(root) { const pending = overview.counts?.candidates_pending || 0; // 候选提示仅对已登录(或未启用鉴权)用户可见,访客不展示运行期信息。 const showCandidates = pending > 0 && canAccessProtected(); - const growthDays = Number(growth.growth_days) || 1; + // 以后端返回的 days(与 growth_days 同步)为准,避免前端再探查 + const growthDays = Number(growth.days ?? growth.growth_days) || 1; const growthLabel = `近 ${growthDays} 日`; const growthEmptyTitle = growth.reason || `近 ${growthDays} 日增长暂不可用`; diff --git a/tests/test_web_queries.py b/tests/test_web_queries.py index a5e1ca1..f157eb3 100644 --- a/tests/test_web_queries.py +++ b/tests/test_web_queries.py @@ -106,6 +106,8 @@ def test_growth_and_cold_start(): try: growth = q.list_videos(sort="growth", growth_days=2) assert growth["available"] is True + assert growth["days"] == 2 + assert growth["growth_days"] == 2 assert {item["aid"] for item in growth["items"]} == {101, 102} first = next(item for item in growth["items"] if item["aid"] == 101) assert first["growth"]["available"] is True @@ -570,15 +572,19 @@ def test_growth_auto_falls_back_to_one_day(): with tempfile.TemporaryDirectory() as tmp: db, q = setup(tmp) try: - auto = q.list_videos(sort="growth", growth_days="auto", page_size=6) + # 省略 growth_days 与显式 auto 等价:后端决定窗口并回传 days + auto = q.list_videos(sort="growth", page_size=6) assert auto["available"] is True assert auto["growth_days"] == 1 + assert auto["days"] == 1 assert {item["aid"] for item in auto["items"]} == {101, 102} first = next(item for item in auto["items"] if item["aid"] == 101) assert first["growth"]["delta"] == 100 # 相邻日 forced7 = q.list_videos(sort="growth", growth_days=7) assert forced7["available"] is False + assert forced7["days"] == 7 + assert forced7["growth_days"] == 7 assert "至少 8 天快照" in forced7["reason"] # 配置钉死 7 天:auto 不再降级 @@ -589,8 +595,9 @@ def test_growth_auto_falls_back_to_one_day(): target = max(path.stat().st_mtime_ns, time.time_ns()) + 1_000_000 os.utime(path, ns=(target, target)) DASHBOARD_CACHE.clear() - pinned = q.list_videos(sort="growth", growth_days="auto") + pinned = q.list_videos(sort="growth") # omit → auto → 配置钉死 7 assert pinned["growth_days"] == 7 + assert pinned["days"] == 7 assert pinned["available"] is False finally: db.close() diff --git a/tests/test_web_server.py b/tests/test_web_server.py index 5d53fd2..cfb8872 100644 --- a/tests/test_web_server.py +++ b/tests/test_web_server.py @@ -108,8 +108,11 @@ def test_readonly_routes_and_static(): status, _, payload = running.request("/api/videos") assert status == 200 - items = json.loads(payload)["data"]["items"] + heat_payload = json.loads(payload)["data"] + items = heat_payload["items"] assert items and items[0]["aid"] == 102 + # 非增长排序:省略 growth_days 时 days/growth_days 回落为 7,不做 7→1 + assert heat_payload["days"] == heat_payload["growth_days"] == 7 status, _, payload = running.json("/api/videos?year=2026") assert status == 200 @@ -120,6 +123,13 @@ def test_readonly_routes_and_static(): assert status == 200 assert payload["data"]["year"] == 2026 + # 首页形态:省略 growth_days → 后端 auto,响应带 days/growth_days + status, _, payload = running.json("/api/videos?sort=growth&limit=6") + assert status == 200 + growth = payload["data"] + assert growth["days"] == growth["growth_days"] == 1 + assert growth["available"] is True + status, _, payload = running.request("/api/ups") assert status == 200 assert json.loads(payload)["data"]["sort"] == "efficiency"