在 Web 项目里,导航栏是当前位置的重要指示。使用 Jinja2 渲染模板时,如果导航项不能随页面切换高亮,用户很容易迷失。借助服务端已有的请求信息,我们可以在模板层直接标记当前页对应的菜单,不需要额外前端脚本参与初次渲染。

一、基础实现:在模板中直接判断路由
最简单的方式是把当前请求的端点或路径传到模板中,利用 Jinja2 的 if 语句给对应链接加类。Flask 框架中,request.endpoint 能返回视图函数名,用它比对最直观。
下面示例中,我们假设导航有三个页面:首页、文章列表、关于。在基模板里写判断逻辑,命中时追加 active 类,CSS 再定义高亮颜色。这种写法清晰,但导航项多时会显得重复。
<nav>
<ul>
<li class="{% if request.endpoint == 'index' %}active{% endif %}">
<a href="/">首页</a>
</li>
<li class="{% if request.endpoint == 'post_list' %}active{% endif %}">
<a href="/posts">文章</a>
</li>
<li class="{% if request.endpoint == 'about' %}active{% endif %}">
<a href="/about">关于</a>
</li>
</ul>
</nav>
这种方案的优点是零依赖、易调试。缺点是当导航结构复杂、包含多级菜单时,模板会变得冗长,且如果改了端点名就要同步改模板。
为了避免端点名硬编码,也可以用 request.path 做前缀匹配,适合有子路由的场景,例如 /posts 和 /posts/123 都高亮文章菜单。
<li class="{% if request.path.startswith('/posts') %}active{% endif %}">
<a href="/posts">文章</a>
</li>
二、使用 Jinja2 宏封装导航项
当导航在多个页面复用,且希望逻辑统一,可以定义宏来生成导航项。宏接收目标端点与显示文本,内部完成高亮判断,调用处只需要传参。
我们把宏写在 macros.html 里,用 request.endpoint 比较。这样新增菜单只改调用处,不用复制判断代码,也方便后期替换比对规则。
{% macro nav_item(endpoint, text, href) %}
<li class="{% if request.endpoint == endpoint %}active{% endif %}">
<a href="{{ href }}">{{ text }}</a>
</li>
{% endmacro %}
在基模板中导入并使用该宏,代码可读性明显提升。宏也支持扩展,比如增加图标参数或外链判断。
{% from "macros.html" import nav_item %}
<nav>
<ul>
{{ nav_item('index', '首页', '/') }}
{{ nav_item('post_list', '文章', '/posts') }}
{{ nav_item('about', '关于', '/about') }}
</ul>
</nav>
宏方案让模板更干净,但需要注意宏文件加载路径。若使用 Flask 的蓝图,建议把宏放在全局可搜到的模板目录,否则会报模板找不到。
另外,宏内仍可结合 request.path 做模糊匹配,只要把判断条件改为字符串方法调用,就能兼容子页面高亮父级菜单的需求。
三、结合 Flask 上下文处理器传递状态
如果项目里很多模板都要知道当前导航标识,可以在 Flask 中用上下文处理器统一注入变量,减少模板里的 request 直接依赖。
下方代码在应用层定义 inject_nav,计算当前激活的段名,模板只需读 active_nav 变量。这样即便以后从端点改为权限标识,也只改一处。
from flask import Flask, request
app = Flask(__name__)
@app.context_processor
def inject_nav():
path = request.path
if path.startswith('/posts'):
active = 'posts'
elif path == '/about':
active = 'about'
else:
active = 'home'
return dict(active_nav=active)
模板中据此加类,语义更明确,也方便非后端同事理解。配合宏使用效果最好。
{% macro nav_item(key, text, href) %}
<li class="{% if active_nav == key %}active{% endif %}">
<a href="{{ href }}">{{ text }}</a>
</li>
{% endmacro %}
该方式把路由规则收敛到 Python 代码,利于单元测试。不过要注意上下文处理器在每个请求都执行,逻辑应保持轻量,避免查询数据库等重操作。
四、样式与常见冲突排查
高亮依赖 CSS 类生效。通常写 nav li.active a { color: #fff; background: #337ab7; } 即可。但有时不生效,多是优先级被更具体的选择器覆盖。
建议用浏览器审查元素确认 active 类是否渲染到 li 上,再看计算样式里颜色属性来源。若用了 UI 框架,可能要写 nav > ul > li.active > a 提升权重。
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 菜单无高亮 | 端点名写错或路径不匹配 | 打印 request.endpoint 核对 |
| 全部菜单高亮 | 判断条件恒真 | 检查比较符与变量作用域 |
| 样式不生效 | CSS 优先级不足 | 增加选择器层级或 !important 临时验证 |
整体来看,Jinja2 动态高亮并不需要复杂扩展,核心是利用请求信息在服务端决定类名。小型项目用直接判断,中大型用宏加上下文处理器,既清晰又好维护。