Gin中通过{{define}}和{{template}}实现公共布局:base.html定义{{template "main" .}}插槽,子模板用{{define "main"}}包裹内容,且所有模板须被r.LoadHTMLGlob("templates/*/")加载,名称必须一致,否则渲染缺失。

Gin 模板中使用 define 和 template 实现公共布局
html/template 的 define/template 机制实现公共模板复用。关键不是“加载方式”,而是“模板内容怎么组织”。
- 所有子模板必须显式
{{ define "xxx" }},不能依赖文件路径自动推导名称 - 公共布局(如
base.html)要先被加载,且其中用{{ template "main" . }}预留内容插槽 - 子模板里不能直接写 HTML 内容,必须包裹在
{{ define "main" }}里,否则渲染时找不到可插入的块 -
r.LoadHTMLGlob("templates/**/*")能加载嵌套目录,但前提是每个文件都带define,否则同名文件会冲突或静默覆盖
为什么 LoadHTMLGlob("templates/*") 加载不了子目录里的模板
LoadHTMLGlob 的通配符不递归匹配子目录,* 只匹配一级文件。比如 templates/header.html 会被加载,但 templates/admin/index.html 不会。
-
"templates/*.html"→ 只加载templates/下的 .html 文件 -
"templates/**/*.html"→ Go 1.16+ 支持双星号递归,但 Gin 内部调用template.ParseGlob时部分旧版本有兼容问题,建议避开 - 更稳妥的做法是用
filepath.WalkDir手动收集所有.html文件路径,再传给r.LoadHTMLFiles(tplFiles...) - 注意:如果两个文件都
{{ define "index.html" }},后加载的会覆盖前一个,Gin 不报错也不警告
自定义模板函数必须在 LoadHTMLFiles 或 LoadHTMLGlob 之前注册
r.SetFuncMap 设置的函数,只对后续加载的模板生效。一旦模板已编译,再设函数也无效。
- 顺序错误会导致模板里调用
{{ UnixToTime .date }}时 panic:function "UnixToTime" not defined - 函数签名必须是
func(...interface{}) string或其他template支持的类型,返回值不能是error - 若函数需访问 Gin 上下文(如获取请求头),得改用
c.HTML传参或封装进数据结构,模板函数本身拿不到*gin.Context
静态资源和模板路径容易混淆,但它们完全独立
r.Static("/static", "./static") 是 HTTP 路由映射,和模板加载路径无关。模板里引用 /static/css/app.css 能访问,是因为浏览器发请求到 /static/xxx,被 Static 中间件拦截并读取本地文件。
- 模板中路径写错(比如写成
./static/)会导致 404,因为这是服务端相对路径,浏览器不识别 - 模板文件本身放在
templates/,但r.LoadHTMLGlob("templates/**/*")中的templates/是 Go 进程启动时的相对路径,不是 URL 路径 - 开发时建议用绝对路径或
os.Executable定位模板根目录,避免因工作目录不同导致加载失败
define 名称不一致,或者忘了在子模板里用 {{ template "base.html" . }} 显式调用父模板 —— 这时候页面就只显示子模板里 define 的那块内容,公共头尾全没了,还查不出错。


















