如何用Nunjucks从JSON生成多层级HTML链接列表?(11ty访问对象孙节点)
解决Eleventy+Nunjucks多层导航渲染问题
问题背景
我正在搭建作品集网站,使用以下JSON结构定义导航菜单:
{ "navTree": [ { "lvl1Item": "Graphic Design", "lvl1Link": "/graphic-design", "hasLvl2Items": true, "lvl2Items": [ { "lvl2Item": "Logos", "lvl2Link": "/logos" }, { "lvl2Item": "Digital Graphics", "lvl2Link": "/digital" } ] }, { "lvl1Item": "Illustration", "lvl1Link": "/illust", "hasLvl2Items": false, "lvl2Items": null }, { "lvl1Item": "Character Design", "lvl1Link": "/characters", "hasLvl2Items": false, "lvl2Items": null } ] }
期望渲染出的多层级HTML导航菜单如下:
<ul> <li><a href="/graphic-design" class="sidenav-lvl1">Graphic Design</a></li> <ul> <li><a href="/graphic-design/logos" class="sidenav-lvl2">Logos</a></li> <li><a href="/graphic-design/digital" class="sidenav-lvl2">Digital Graphics</a></li> </ul> <li><a href="/illust" class="sidenav-lvl1">Illustration</a></li> <li><a href="/characters" class="sidenav-lvl1">Character Design</a></li> </ul>
当前问题
编写的Nunjucks代码无法正确渲染二级菜单:
<ul> {% for lvl1Item in navTree %} <li> <a href="{{ lvl1Item.lvl1Link }}" class="sidenav-lvl1">{{ lvl1Item.lvl1Label }}</a> <ul> {% if lvl1Item.hasLvl2Items %} <li>Lvl 2 True! {# Just testing to see if it found anything. #}</li> {% for lvl2Items in lvl1Item %} <li> <a href="{{ lvl1Item.lvl1Link }}/{{ lvl1Item.lvl2Items.lvl2Link }}" class="sidenav-lvl2">{{ lvl1Item.lvl2Items.lvl2Label }}</a> </li> {% endfor %} {% endif %} </ul> </li> {% endfor %} </ul>
实际输出的HTML仅能检测到二级菜单存在,但无法显示内容:
<ul> <li> <a href="/graphic-design" class="sidenav-lvl1">Graphic Design</a> <ul> <li>Lvl 2 True! {# Just testing to see if it found anything. #}</li> </ul> </li> <li> <a href="/illust" class="sidenav-lvl1">Illustration</a> <ul></ul> </li> <li> <a href="/characters" class="sidenav-lvl1">Character Design</a> <ul></ul> </li> </ul>
问题原因与修复
代码存在两个核心错误:
- 循环目标错误:二级菜单循环时遍历的是
lvl1Item整个对象,而非其下的lvl2Items数组。 - 字段名与访问路径错误:JSON中的文本字段是
lvl2Item,但代码中误用了lvl2Label;同时访问二级属性时,应使用循环变量而非直接从lvl1Item.lvl2Items取值。
修正后的Nunjucks代码
<ul> {% for lvl1 in navTree %} <li> <a href="{{ lvl1.lvl1Link }}" class="sidenav-lvl1">{{ lvl1.lvl1Item }}</a> {% if lvl1.hasLvl2Items and lvl1.lvl2Items %} <ul> {% for lvl2 in lvl1.lvl2Items %} <li> <a href="{{ lvl1.lvl1Link }}{{ lvl2.lvl2Link }}" class="sidenav-lvl2">{{ lvl2.lvl2Item }}</a> </li> {% endfor %} </ul> {% endif %} </li> {% endfor %} </ul>
优化点说明
- 简化循环变量名(
lvl1、lvl2),提升代码可读性。 - 增加
lvl1.lvl2Items非空判断,避免null值引发的报错。 - 修正字段名:用
lvl1.lvl1Item替代错误的lvl1.lvl1Label,lvl2.lvl2Item替代lvl2.lvl2Label。 - 调整链接拼接方式为
{{ lvl1.lvl1Link }}{{ lvl2.lvl2Link }},确保路径拼接正确(如/graphic-design+/logos=/graphic-design/logos)。
更灵活的导航配置方案
若未来需要支持三级及以上菜单,可重构为递归式JSON结构,让配置更易扩展:
{ "navTree": [ { "label": "Graphic Design", "url": "/graphic-design", "children": [ { "label": "Logos", "url": "/logos" }, { "label": "Digital Graphics", "url": "/digital" } ] }, { "label": "Illustration", "url": "/illust", "children": [] }, { "label": "Character Design", "url": "/characters", "children": [] } ] }
对应的递归Nunjucks宏(可复用):
{% macro renderNav(items, parentUrl = "") %} <ul> {% for item in items %} <li> <a href="{{ parentUrl }}{{ item.url }}" class="{{ parentUrl ? 'sidenav-lvl2' : 'sidenav-lvl1' }}">{{ item.label }}</a> {% if item.children and item.children.length > 0 %} {{ renderNav(item.children, parentUrl + item.url) }} {% endif %} </li> {% endfor %} </ul> {% endmacro %} {# 调用宏生成导航 #} {{ renderNav(navTree) }}
该方案无需手动判断层级,新增层级时仅需在JSON中添加children数组即可,维护成本更低。
内容的提问来源于stack exchange,提问作者HikaTamika
相关产品推荐
相关产品推荐

