You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang Html Template如何创建导航子菜单?转Go遇渲染难题

解决Golang Html Template嵌套导航子菜单问题

1. 先将扁平菜单数据转为树形结构

从数据库取出的菜单是扁平列表,必须先把它转换成父子嵌套的树形结构,才能让模板方便遍历渲染。

定义菜单结构体

type MenuItem struct {
    ID       int
    ParentID int
    Name     string
    URL      string
    Children []*MenuItem // 存储子菜单
}

转换扁平数据为树形结构

假设你从数据库查询到的是[]MenuItem类型的扁平列表,用下面的方法构建树形结构:

func buildMenuTree(flatMenu []MenuItem) []*MenuItem {
    menuMap := make(map[int]*MenuItem)
    var rootMenu []*MenuItem

    // 先把所有菜单存入map,方便快速查找父菜单
    for i := range flatMenu {
        item := &flatMenu[i]
        menuMap[item.ID] = item
    }

    // 遍历每个菜单,将子菜单挂载到对应父菜单下
    for _, item := range menuMap {
        if item.ParentID == 0 {
            // ParentID为0的是主菜单,加入根列表
            rootMenu = append(rootMenu, item)
        } else {
            // 找到父菜单,将当前菜单加入其子列表
            if parent, exists := menuMap[item.ParentID]; exists {
                parent.Children = append(parent.Children, item)
            }
        }
    }

    return rootMenu
}

2. 编写嵌套的Html模板

Go模板支持通过define定义子模板,然后递归调用自身实现嵌套渲染。

{{define "menu"}}
    <ul>
        {{range .}}
            <li>
                <a href="{{.URL}}">{{.Name}}</a>
                {{if gt (len .Children) 0}}
                    {{template "menu" .Children}} <!-- 递归调用子模板渲染子菜单 -->
                {{end}}
            </li>
        {{end}}
    </ul>
{{end}}

<!-- 主模板 -->
<!DOCTYPE html>
<html>
<head>
    <title>导航菜单</title>
</head>
<body>
    {{template "menu" .}}
</body>
</html>

3. 完整渲染示例

假设你已经从数据库拿到扁平菜单数据,构建树形结构后渲染模板:

package main

import (
    "html/template"
    "os"
)

type MenuItem struct {
    ID       int
    ParentID int
    Name     string
    URL      string
    Children []*MenuItem
}

func buildMenuTree(flatMenu []MenuItem) []*MenuItem {
    menuMap := make(map[int]*MenuItem)
    var rootMenu []*MenuItem

    for i := range flatMenu {
        item := &flatMenu[i]
        menuMap[item.ID] = item
    }

    for _, item := range menuMap {
        if item.ParentID == 0 {
            rootMenu = append(rootMenu, item)
        } else {
            if parent, exists := menuMap[item.ParentID]; exists {
                parent.Children = append(parent.Children, item)
            }
        }
    }

    return rootMenu
}

func main() {
    // 模拟数据库查询到的扁平菜单数据
    flatMenu := []MenuItem{
        {ID: 1, ParentID: 0, Name: "首页", URL: "/"},
        {ID: 2, ParentID: 0, Name: "产品", URL: "/products"},
        {ID: 3, ParentID: 2, Name: "产品A", URL: "/products/a"},
        {ID: 4, ParentID: 2, Name: "产品B", URL: "/products/b"},
        {ID: 5, ParentID: 0, Name: "关于我们", URL: "/about"},
        {ID: 6, ParentID: 5, Name: "团队", URL: "/about/team"},
    }

    // 构建树形菜单
    menuTree := buildMenuTree(flatMenu)

    // 解析模板文件(假设模板文件名为menu.html)
    tpl := template.Must(template.ParseFiles("menu.html"))

    // 渲染模板
    err := tpl.Execute(os.Stdout, menuTree)
    if err != nil {
        panic(err)
    }
}

关键提示

  • Go模板不像PHP可以在循环中动态查询父级,必须提前把数据整理成树形结构,这是实现嵌套的核心前提。
  • 通过递归调用子模板,可以支持任意层级的菜单嵌套,不管是二级还是多级都能正常渲染。

内容的提问来源于stack exchange,提问作者Ugur Tas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 06:47:26