3 回答

TA贡献1848条经验 获得超10个赞
只要您将模板与“内容”一起解析,您就可以使用 base.html,如下所示:
基本文件
{{define "base"}}
<!DOCTYPE html>
<html>
<body>
header...
{{template "content" .}}
footer...
</body>
</html>
{{end}}
页面1.html
{{define "content"}}
I'm page 1
{{end}}
页面2.html
{{define "content"}}
I'm page 2
{{end}}
然后ParseFiles with ("your-page.html", "base.html") 和ExecuteTemplate与你的上下文。
tmpl, err := template.New("").ParseFiles("page1.html", "base.html")
// check your err
err = tmpl.ExecuteTemplate(w, "base", yourContext)

TA贡献1850条经验 获得超11个赞
Go 1.16 引入了 embed 包,将非 .go 文件打包成二进制文件,极大地方便了 Go 程序的部署。该ParseFS函数还添加到标准库 html/template 中,它将 embed.FS 中包含的所有模板文件编译为模板树。
// templates.go
package templates
import (
"embed"
"html/template"
)
//go:embed views/*.html
var tmplFS embed.FS
type Template struct {
templates *template.Template
}
func New() *Template {
funcMap := template.FuncMap{
"inc": inc,
}
templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tmplFS, "views/*.html"))
return &Template{
templates: templates,
}
}
// main.go
t := templates.New()
t.templates是一个全局模板,包含所有匹配的views/*.html模板,所有模板都是相关的,可以相互引用,模板的名字就是文件的名字,例如article.html.
此外,我们Render为该*Template类型定义了一个方法,该方法实现Renderer了 Echo Web 框架的接口。
// templates.go
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
然后,您可以为 Echo 指定渲染器,以便在每个处理程序中生成 HTML 响应,只需将模板的名称传递给c.Render函数即可。
// main.go
func main() {
t := templates.New()
e := echo.New()
e.Renderer = t
}
// handler.go
func (h *Handler) articlePage(c echo.Context) error {
id := c.Param("id")
article, err := h.service.GetArticle(c.Request().Context(), id)
...
return c.Render(http.StatusOK, "article.html", article)
}
由于t.templates模板包含了所有解析的模板,每个模板名称都可以直接使用。
为了组装 HTML,我们需要使用模板继承。例如,为基本的 HTML 框架和<head>元素定义一个 layout.html ,并设置{{block "title"}}和{{block "content"}},其他模板继承 layout.html,并使用自己定义的块填充或覆盖布局模板的同名块。
以下是layout.html模板的内容。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{block "title" .}}{{end}}</title>
<script src="/static/main.js"></script>
</head>
<body>
<div class="main">{{block "content" .}}{{end}}</div>
</body>
</html>
其他模板可以参考(继承)layout.html,在layout.html模板中定义blocks。
例如,login.html 内容如下。
{{template "layout.html" .}}
{{define "title"}}Login{{end}}
{{define "content"}}
<form class="account-form" method="post" action="/account/login" data-controller="login">
<div div="account-form-title">Login</div>
<input type="phone" name="phone" maxlength="13" class="account-form-input" placeholder="Phone" tabindex="1">
<div class="account-form-field-submit ">
<button type="submit" class="btn btn-phone">Login</button>
</div>
</form>
{{end}}
article.html 还引用了 layout.html:
{{template "layout.html" .}}
{{define "title"}}<h1>{{.Title}}</h1>{{end}}
{{define "content"}}
<p>{{.URL}}</p>
<article>{{.Content}}</article>
{{end}}
我们希望 login.html 模板中定义的块在渲染时覆盖 layout.html 中的块,也在渲染 article.html 模板时覆盖。但事实并非如此,这取决于 Go 文本/模板实现。在我们的实现中ParseFS(tmplFS, "views/*.html"),假设先解析article.html并将其content块解析为模板名称,那么当稍后解析login.html模板并content在其中找到一个块时,text/template将覆盖该模板的模板与后面解析的内容同名,所以当所有的模板都解析完后,content我们的模板树中实际上只有一个模板命名,也就是content上次解析的模板文件中定义的。
因此,当我们执行article.html模板时,有可能该content模板不是本模板中定义的内容,而是content其他模板中定义的内容。
社区针对这个问题提出了一些解决方案。例如,不是使用全局模板,而是在每次渲染时创建一个新模板,仅包含 layout.html 和子模板的内容。但这真的很乏味。事实上,当 Go 1.6block为文本/模板引入指令 [1] 时,我们能够使用该Clone方法做我们想做的事情,只需对上面的代码进行一些更改。
// templates.go
package templates
import (
"embed"
"html/template"
"io"
"github.com/labstack/echo/v4"
)
//go:embed views/*.html
var tmplFS embed.FS
type Template struct {
templates *template.Template
}
func New() *Template {
funcMap := template.FuncMap{
"inc": inc,
}
templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tmplFS, "views/*.html"))
return &Template{
templates: templates,
}
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
tmpl := template.Must(t.templates.Clone())
tmpl = template.Must(tmpl.ParseFS(tmplFS, "views/"+name))
return tmpl.ExecuteTemplate(w, name, data)
}
可以看到Render这里只修改了函数。我们不会执行全局模板,而是将其克隆到一个新模板中,而content这个新模板中的块可能不是我们想要的,所以这里我们解析一个子模板的内容,我们最终将在此之上渲染全局模板,这样content新添加的子模板的 将覆盖以前的,可能不正确的content。我们的目标子模板引用了全局模板中的layout.html,这并不冲突,而且由于全局模板从来没有被执行过(Render每次执行我们在函数中克隆一个新的全局模板),它也是干净的。当一个模板最终被执行时,我们有一个干净的 layout.htmlcontent我们想要的内容,相当于每次执行都会生成一个新的模板,里面只包含我们需要的布局模板和子模板。思路是一样的,只不过不是在执行模板时手动生成新模板,而是在Render函数中自动完成。
当然,也可以使用{{ template }}来引用子模板中的其他布局模板,只要这些布局模板不相互覆盖,执行时只需要指定目标子模板的名称,模板引擎会自动使用其中{{ template }}定义的标签为我们寻找布局模板,这些模板都在克隆的全局模板中。
[1] https://github.com/golang/go/commit/12dfc3bee482f16263ce4673a0cce399127e2a0d

TA贡献1811条经验 获得超5个赞
据我了解,当您使用 时ParseGlob(),Gin 会解析所有匹配的文件并从中创建一个模板对象。为了做你想做的事,你需要两个不同的模板(一个用于第 1 页,另一个用于第 2 页)。
Gin 文档说这是一个已知的限制,并指出了克服它的方法:
Gin 默认只允许使用一个 html.Template。检查多模板渲染以使用 go 1.6 等功能block template。
使用多模板库,您可以编写如下内容:
render := multitemplate.NewRenderer()
render.AddFromFiles("page1", "templates/base.html", "templates/page1.html")
render.AddFromFiles("page2", "templates/base.html", "templates/page2.html")
router := gin.Default()
router.HTMLRender = render
// Later
ginContext.HTML(200, "page1", gin.H{
"title": "The Wonderful Page One",
})
这需要比我希望的更多的手动设置,但可以完成工作。
- 3 回答
- 0 关注
- 305 浏览
添加回答
举报