3 回答

TA貢獻1848條經(jīng)驗 獲得超10個贊
只要您將模板與“內(nèi)容”一起解析,您就可以使用 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條經(jīng)驗 獲得超11個贊
Go 1.16 引入了 embed 包,將非 .go 文件打包成二進制文件,極大地方便了 Go 程序的部署。該ParseFS函數(shù)還添加到標準庫 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類型定義了一個方法,該方法實現(xiàn)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函數(shù)即可。
// 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模板的內(nèi)容。
<!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 內(nèi)容如下。
{{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 文本/模板實現(xiàn)。在我們的實現(xiàn)中ParseFS(tmplFS, "views/*.html"),假設先解析article.html并將其content塊解析為模板名稱,那么當稍后解析login.html模板并content在其中找到一個塊時,text/template將覆蓋該模板的模板與后面解析的內(nèi)容同名,所以當所有的模板都解析完后,content我們的模板樹中實際上只有一個模板命名,也就是content上次解析的模板文件中定義的。
因此,當我們執(zhí)行article.html模板時,有可能該content模板不是本模板中定義的內(nèi)容,而是content其他模板中定義的內(nèi)容。
社區(qū)針對這個問題提出了一些解決方案。例如,不是使用全局模板,而是在每次渲染時創(chuàng)建一個新模板,僅包含 layout.html 和子模板的內(nèi)容。但這真的很乏味。事實上,當 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這里只修改了函數(shù)。我們不會執(zhí)行全局模板,而是將其克隆到一個新模板中,而content這個新模板中的塊可能不是我們想要的,所以這里我們解析一個子模板的內(nèi)容,我們最終將在此之上渲染全局模板,這樣content新添加的子模板的 將覆蓋以前的,可能不正確的content。我們的目標子模板引用了全局模板中的layout.html,這并不沖突,而且由于全局模板從來沒有被執(zhí)行過(Render每次執(zhí)行我們在函數(shù)中克隆一個新的全局模板),它也是干凈的。當一個模板最終被執(zhí)行時,我們有一個干凈的 layout.htmlcontent我們想要的內(nèi)容,相當于每次執(zhí)行都會生成一個新的模板,里面只包含我們需要的布局模板和子模板。思路是一樣的,只不過不是在執(zhí)行模板時手動生成新模板,而是在Render函數(shù)中自動完成。
當然,也可以使用{{ template }}來引用子模板中的其他布局模板,只要這些布局模板不相互覆蓋,執(zhí)行時只需要指定目標子模板的名稱,模板引擎會自動使用其中{{ template }}定義的標簽為我們尋找布局模板,這些模板都在克隆的全局模板中。
[1] https://github.com/golang/go/commit/12dfc3bee482f16263ce4673a0cce399127e2a0d

TA貢獻1811條經(jīng)驗 獲得超5個贊
據(jù)我了解,當您使用 時ParseGlob(),Gin 會解析所有匹配的文件并從中創(chuàng)建一個模板對象。為了做你想做的事,你需要兩個不同的模板(一個用于第 1 頁,另一個用于第 2 頁)。
Gin 文檔說這是一個已知的限制,并指出了克服它的方法:
Gin 默認只允許使用一個 html.Template。檢查多模板渲染以使用 go 1.6 等功能block template。
使用多模板庫,您可以編寫如下內(nèi)容:
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 關注
- 332 瀏覽
添加回答
舉報