From a47ad991d07eaec6b316763cba6a0b209868760a Mon Sep 17 00:00:00 2001 From: bwstudio Date: Sun, 6 Sep 2026 18:52:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(public):=20=E9=A6=96=E9=A1=B5=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E3=80=8C=E7=83=AD=E9=97=A8=E7=BD=91=E5=9D=80=E3=80=8D?= =?UTF-8?q?=E5=88=86=E7=B1=BB=EF=BC=88=E8=AE=BF=E9=97=AE=E9=87=8F=20TOP12?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 跨分类聚合:按 NavClick 总点击数倒序,取前 12 个有效 nav。 实现要点: - applications/view/public/nav.py: 新增 _top_navs(limit=12), 复用 get_nav_visit_counts() 的 60s 进程内缓存;同点击数按 id asc 稳定排序; 0 点击返回空列表(前端整段不渲染) - _render_index() 注入 top_navs 到模板 - templates/public/index.html: 在 groups 循环前渲染 .section-hot,标题「🔥 热门网址」, meta 显示「近期访问量最大的 N 个」;卡片复用 .card,新增 .card-top 类 - templates/public/base.html: - .card-top:左侧 3px 品牌色边框 + 浅色背景渐变 + hover 增强阴影 - .section-hot h2 图标用 brand-orange #ff6b35 - .cat-nav-inner a.cat-nav-hot 顶部导航热门入口用品牌色 - 抽屉:首页 top_navs 非空时插入「🔥 热门」区段,链接到 #cat-top 边界: - 没有任何点击时整段隐藏(包括抽屉和顶部 nav),不出现「0 个」空标题 - 同一 nav 不会重复(按 nav.id 维度聚合) - Nav.enable=0 的不会进 TOP12 验证: - 15 个 nav 各灌入 20..6 次点击,前 12 个按降序显示访问数 20..9 - 清空所有 NavClick 后首页 section-hot / #cat-top / .cat-drawer-hot 三处全不渲染 - 测试数据已清理 --- applications/view/public/nav.py | 29 +++++++++++++++++++++ templates/public/base.html | 42 ++++++++++++++++++++++++++++++ templates/public/index.html | 45 +++++++++++++++++++++++++++++++-- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/applications/view/public/nav.py b/applications/view/public/nav.py index 3e09f1c..e2ed7b7 100644 --- a/applications/view/public/nav.py +++ b/applications/view/public/nav.py @@ -85,9 +85,38 @@ def _render_index(): total=total, site_name='旺珂 · 导航', cat_meta=_category_meta(), + top_navs=_top_navs(12), ) +def _top_navs(limit: int = 12) -> "list[dict]": + """跨分类聚合:返回点击数 TOP N 的有效 nav 列表(按点击数降序)。 + + - 复用 get_nav_visit_counts() 的 60s 进程内缓存 + - 0 点击时返回空列表(调用方负责隐藏整个分类块) + - 同一个 nav 不可能重复出现(按 nav.id 维度) + """ + counts = get_nav_visit_counts() + if not counts: + return [] + # 取点击数最高的 N 个 nav_id(点击数相同的按 id asc 稳定排序) + top_ids = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:limit] + nav_ids = [nid for nid, _ in top_ids] + rows = ( + Nav.query + .filter(Nav.id.in_(nav_ids), Nav.enable == 1) + .all() + ) + by_id = {n.id: n.to_dict() for n in rows} + out = [] + for nid in nav_ids: + if nid in by_id: + d = by_id[nid] + d['visit_count'] = counts.get(nid, 0) + out.append(d) + return out + + def _grouped_navs() -> "OrderedDict[str, list]": """按 category 分组,组内按 sort asc 排序;分组顺序优先按 NavCategory.sort 排列""" # 先取启用的分类,按 sort 排好序 diff --git a/templates/public/base.html b/templates/public/base.html index e205724..9632571 100644 --- a/templates/public/base.html +++ b/templates/public/base.html @@ -396,6 +396,32 @@ border-color: var(--brand); } .card:active { transform: translateY(0); } + + /* ===== 热门网址卡片:与普通卡片区分 ===== */ + .section-hot { margin-top: 8px; } + .section-hot .section-header h2 i { color: #ff6b35; margin-right: 2px; } + .card-top { + border-color: rgba(22, 186, 170, 0.35); + background: linear-gradient(135deg, var(--card-bg) 0%, rgba(22, 186, 170, 0.05) 100%); + position: relative; + } + .card-top::before { + content: ''; + position: absolute; top: 0; left: 0; + width: 3px; height: 100%; + background: var(--brand); + border-radius: 10px 0 0 10px; + } + .card-top:hover { + border-color: var(--brand); + box-shadow: 0 6px 20px rgba(22, 186, 170, 0.18); + } + + .cat-nav-inner a.cat-nav-hot { + color: #ff6b35; + font-weight: 600; + } + .card .icon-wrap { width: 42px; height: 42px; background: rgba(22, 186, 170, 0.1); @@ -770,6 +796,22 @@ + {# 热门网址入口(仅首页,且首页必须传 top_navs 才渲染)#} + {% if top_navs is defined and top_navs %} +
+
热门
+ +
+ {% endif %} + {# 分类区 #} {% if cat_meta is defined %}
diff --git a/templates/public/index.html b/templates/public/index.html index e9c29b3..da27b5e 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -2,9 +2,10 @@ {# 分类目录条(sticky 顶部导航) #} {% block cat_nav %} -{% if groups %} +{% if groups or top_navs %}