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:
@@ -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()
|
||||
|
||||
|
||||
@@ -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 [],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user