feat(public): 首页分类移至左侧侧栏 + 大屏自适应布局

- 分类导航由顶部横条改为左侧 sticky 侧栏(≥1025px),含快速导航/我常访问/热门/书签分类/站点页面
- 首页左右去留白:.container 与 .topbar-inner 改为 max-width:100%,布局铺满视口
- 卡片网格按屏宽自适应(minmax(var(--card-min),1fr)),1920 固定 6 列
- 移除顶部 .actions 菜单(与侧栏站点页面重复),主题切换改由侧栏按钮循环切换
- 新增 get_nav_categories() 统一锚点(侧栏/顶部/抽屉/section id 四者同源),修复空分类错位;后台分类增删改/启停后失效前台缓存
This commit is contained in:
2026-09-07 11:11:22 +08:00
parent 1a33648b9a
commit 4d6325c70e
5 changed files with 406 additions and 71 deletions
@@ -15,3 +15,12 @@ def init_template_directives(app):
def csrf_input():
return f'<input type="hidden" name="csrf_token" value="{generate_csrf()}">'
@app.template_global()
def nav_categories():
"""前台左侧侧栏 / 移动端抽屉共用的分类列表(带 anchor 锚点)。
以函数形式提供而非 context_processor,后台模板不调用就不会查库。
"""
from applications.view.public.nav import get_nav_categories
return get_nav_categories()
+66
View File
@@ -11,6 +11,7 @@ URL 路径(挂在根路径下,便于匿名访问):
- 使用 url_prefix='/site' 避免与后台 / 路由冲突(后台 / 是登录后的工作台)。
"""
from collections import OrderedDict
import time
from flask import Blueprint, render_template, request, jsonify
@@ -22,6 +23,70 @@ MINE_MIN_ITEMS = 2
bp = Blueprint('public_nav', __name__, url_prefix='/site')
# 分类列表缓存(左侧侧栏 / 移动端抽屉共用,避免每个请求都查库)
_NAV_CATS_CACHE = {'ts': 0.0, 'data': []}
_NAV_CATS_TTL = 30.0
def get_nav_categories() -> "list[dict]":
"""返回前台导航用的分类列表(按后台 sort 排序,带稳定锚点)。
返回形如::
[{'name': '开发工具', 'icon': 'layui-icon-list',
'description': '', 'anchor': 'cat-1'}, ...]
锚点 ``anchor`` 基于「启用分类」的排序索引生成(从 1 开始),与首页
各 ``<section>`` 的 id 严格一致——即使某个分类下暂时没有导航(空分组
被过滤)也不会错位。
带 30s 进程内缓存:后台新增/调整分类后最多 30s 生效。
"""
now = time.time()
cached = _NAV_CATS_CACHE.get('data') or []
if cached and (now - _NAV_CATS_CACHE.get('ts', 0.0)) < _NAV_CATS_TTL:
return cached
try:
cats = (
NavCategory.query
.filter(NavCategory.enable == 1)
.order_by(NavCategory.sort.desc(), NavCategory.id.asc())
.all()
)
names = [c.name for c in cats]
meta = {c.name: (c.icon, c.description) for c in cats}
# 兜底:只在 Nav 表里出现、未登记到 NavCategory 的历史分类,
# 追加到末尾(与 _grouped_navs 的兜底顺序一致:按分类名升序),
# 保证侧栏锚点与首页 section 一一对应。
used = Nav.query.filter(Nav.enable == 1).with_entities(Nav.category).distinct().all()
names.extend(sorted(n for (n,) in used if n and n not in meta))
data = []
for idx, name in enumerate(names):
icon, desc = meta.get(name, (None, None))
data.append({
'name': name,
'icon': icon or 'layui-icon-list',
'description': desc or '',
'anchor': 'cat-%d' % (idx + 1),
})
except Exception: # 数据库尚未初始化(如迁移前)时不影响页面渲染
data = []
_NAV_CATS_CACHE['ts'] = now
_NAV_CATS_CACHE['data'] = data
return data
def get_cat_anchor_map() -> "dict[str, str]":
"""分类名 -> 锚点 id 的映射,供首页渲染 section id 使用。"""
return {c['name']: c['anchor'] for c in get_nav_categories()}
def clear_nav_categories_cache():
"""后台改动分类后调用:立即失效缓存。"""
_NAV_CATS_CACHE['ts'] = 0.0
_NAV_CATS_CACHE['data'] = []
@bp.get('/')
def index():
@@ -89,6 +154,7 @@ def _render_index():
total=total,
site_name='旺珂 · 导航',
cat_meta=_category_meta(),
cat_anchors=get_cat_anchor_map(),
top_navs=_top_navs(12),
my_navs=mine if len(mine) >= MINE_MIN_ITEMS else [],
)