一 基本使用
第一步:index.html
在項目根路徑下新建templates文件夾,文件夾內寫模板文件,如index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第一個模板文件</title>
</head>
<body>
我的名字是:{{.name}}
<br>
我的年齡是:{{.age}}
</body>
</html>
第二步:渲染模板
Gin 框架中使用 c.HTML 可以渲染模板,渲染模板前需要使用 LoadHTMLGlob()或者 LoadHTMLFiles()方法加載模板
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 加載整個文件夾
//router.LoadHTMLGlob("templates/*")
// 加載單個
router.LoadHTMLFiles("templates/index.html", "templates/index2.html")
router.GET("/index", func(c *gin.Context) {
c.HTML(200,"index.html",gin.H{"name":"劉清政","age":19})
})
router.Run(":8000")
}
二 模板文件放在不同文件夾下
//Gin 框架中如果不同目錄下面有同名模板的話我們需要使用下面方法加載模板
// 一旦templates文件夾下還有文件夾,一定要按給每一個都定義名字
//注意:定義模板的時候需要通過 define 定義名稱
templates/admin/index.html
{{ define "admin/index.html" }}
html內容
{{end}}
2.1 main.go
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 注意此處的導入路徑
router.LoadHTMLGlob("templates/**/*")
router.GET("/index", func(c *gin.Context) {
// 模板名為新定義的模板名字
c.HTML(200,"admin/index.tpl",gin.H{"title":"我是后台模板"})
})
router.Run(":8000")
}
2.2 admin/index.tmpl
{{ define "admin/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>后台管理首頁</title>
</head>
<body>
<h1>{{.title}}</h1>
</body>
</html>
{{end}}
2.3 目錄結構為
2.4 注意
// 1 如果模板在多級目錄里面的話需要這樣配置 r.LoadHTMLGlob("templates/**/**/*") /** 表示目錄
// 2 LoadHTMLGlob只能加載同一層級的文件
比如說使用router.LoadHTMLFile("/templates/**/*"),就只能加載/templates/admin/或者/templates/order/下面的文件
解決辦法就是通過filepath.Walk來搜索/templates下的以.html結尾的文件,把這些html文件都加載一個數組中,然后用LoadHTMLFiles加載
var files []string
filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".html") {
files = append(files, path)
}
return nil
})
router.LoadHTMLFiles(files...)
三 模板語法
3.1 {{.}} 渲染變量
有兩個常用的傳入變量的類型。一個是struct,在模板內可以讀取該struct的字段(對外暴露的屬性)來進行渲染。還有一個是map[string]interface{},在模板內可以使用key獲取對應的value來進行渲染
3.1.1 main.go
package main
import (
"github.com/gin-gonic/gin"
"os"
"path/filepath"
"strings"
)
func main() {
router := gin.Default()
var files []string
filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".html") {
files = append(files, path)
}
return nil
})
router.LoadHTMLFiles(files...)
router.GET("/index", func(c *gin.Context) {
type Book struct {
Name string
price int
}
c.HTML(200, "order.html", gin.H{
"age": 10,
"name":"劉清政",
"hobby":[3]string{"抽煙","喝酒","燙頭"},
"wife":[]string{"劉亦菲","迪麗熱巴","古力娜扎"},
"info":map[string]interface{}{"height":180,"gender":"男"},
"book":Book{"紅樓夢",99},
})
})
router.Run(":8000")
}
3.1.2 order.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>訂單頁面</title>
</head>
<body>
<h1>渲染字符串,數字,數組,切片,maps,結構體</h1>
<p>年齡:{{.age}}</p>
<p>姓名:{{.name}}</p>
<p>愛好:{{.hobby}}</p>
<p>wife:{{.wife}}</p>
<p>信息:{{.info}}--->{{.info.gender}}</p>
<p>圖書:{{.book}}--->{{.book.Name}}</p>
</body>
</html>
3.2 注釋
{{/* a comment */}}
// 注釋,執行時會忽略。可以多行。注釋不能嵌套,並且必須緊貼分界符始止。
<p>圖書不顯示,注釋了:{{/* .book */}}</p>
3.3 聲明變量
<h1>聲明變量</h1>
<p>{{$obj := .book.Name}}</p>
<p>{{$obj}}</p>
3.4 移除空格
在{{符號的后面加上短橫線並保留一個或多個空格來去除它前面的空白(包括換行符、制表符、空格等),即{{- xxxx。
在}}的前面加上一個或多個空格以及一個短橫線-來去除它后面的空白,即xxxx -}}
<p>{{ 20 }} < {{ 40 }}---> 20 < 40</p>
<p>{{ 20 -}} < {{- 40 }}-->20<40</p>
3.5 比較函數
布爾函數會將任何類型的零值視為假,其余視為真。 下面是定義為函數的二元比較運算的集合:
eq 如果arg1 == arg2則返回真
ne 如果arg1 != arg2則返回真
lt 如果arg1 < arg2則返回真
le 如果arg1 <= arg2則返回真
gt 如果arg1 > arg2則返回真
ge 如果arg1 >= arg2則返回真
// 使用方式
<h1>比較函數</h1>
<p>{{gt 11 13}}</p>
<p>{{lt 11 13}}</p>
<p>{{eq 11 11}}</p>
3.6 條件判斷
// 方式一
{{if pipeline}} T1 {{end}}
// 方式二
{{if pipeline}} T1 {{else}} T0 {{end}}
// 方式三
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
<h1>條件判斷</h1>
<br>//案例一
{{if .show}}
看到我了
{{end}}
<br>//案例二
{{if gt .age 18}}
成年了
{{else}}
沒成年
{{end}}
<br>// 案例三
{{if gt .score 90}}
優秀
{{else if gt .score 60}}
及格
{{else}}
不及格
{{end}}
3.7 range循環
// 方式一
{{ range pipeline }} T1 {{ end }}
// 方式二
// 如果 pipeline 的長度為 0 則輸出 else 中的內容
{{ range pipeline }} T1 {{ else }} T2 {{ end }}
range可以遍歷slice、數組、map或channel。遍歷的時候,會設置.為當前正在遍歷的元素。
對於第一個表達式,當遍歷對象的值為0值時,則range直接跳過,就像if一樣。對於第二個表達式,則在遍歷到0值時執行else。
range的參數部分是pipeline,所以在迭代的過程中是可以進行賦值的。但有兩種賦值情況:
{{ range $value := pipeline }} T1 {{ end }}
{{ range $key, $value := pipeline }} T1 {{ end }}
如果range中只賦值給一個變量,則這個變量是當前正在遍歷元素的值。如果賦值給兩個變量,則第一個變量是索引值(array/slice是數值,map是key),第二個變量是當前正在遍歷元素的值
<h1>range循環</h1>
<h2>循環數組</h2>
{{range $index,$value:=.wife}}
<p>{{$index}}---{{$value}}</p>
{{end}}
<h2>循環map</h2>
{{range $key,$value:=.info}}
<p>{{$key}}---{{$value}}</p>
{{end}}
<h2>循環空-->"girls":map[string]interface{}{}</h2>
{{range $value:=.girls}}
<p>{{$value}}</p>
{{else}}
沒有女孩
{{end}}
3.8 with...end
{{ with pipeline }} T1 {{ end }}
{{ with pipeline }} T1 {{ else }} T0 {{ end }}
對於第一種格式,當pipeline不為0值的時候,將.設置為pipeline運算的值,否則跳過。
對於第二種格式,當pipeline為0值時,執行else語句塊T0,否則.設置為pipeline運算的值,並執行T1。
<h1>with ... end</h1>
<h2>不使用with</h2>
<p>{{.book.Name}}</p>
<p>{{.book.Price}}</p>
<h2>使用with</h2>
{{with .book}}
<p>{{.Name}}</p>
<p>{{.Price}}</p>
{{end}}
3.9 函數
golang的模板其實功能很有限,很多復雜的邏輯無法直接使用模板語法來表達,所以只能使用模板函數來實現。
首先,template包創建新的模板的時候,支持.Funcs方法來將自定義的函數集合導入到該模板中,后續通過該模板渲染的文件均支持直接調用這些函數。
該函數集合的定義為:
type FuncMap map[string]interface{}
key為方法的名字,value則為函數。這里函數的參數個數沒有限制,但是對於返回值有所限制。有兩種選擇,一種是只有一個返回值,還有一種是有兩個返回值,但是第二個返回值必須是error類型的。這兩種函數的區別是第二個函數在模板中被調用的時候,假設模板函數的第二個參數的返回不為空,則該渲染步驟將會被打斷並報錯
3.9.1 內置函數
var builtins = FuncMap{
// 返回第一個為空的參數或最后一個參數。可以有任意多個參數。
// "and x y"等價於"if x then y else x"
"and": and,
// 顯式調用函數。第一個參數必須是函數類型,且不是template中的函數,而是外部函數。
// 例如一個struct中的某個字段是func類型的。
// "call .X.Y 1 2"表示調用dot.X.Y(1, 2),Y必須是func類型,函數參數是1和2。
// 函數必須只能有一個或2個返回值,如果有第二個返回值,則必須為error類型。
"call": call,
// 返回與其參數的文本表示形式等效的轉義HTML。
// 這個函數在html/template中不可用。
"html": HTMLEscaper,
// 對可索引對象進行索引取值。第一個參數是索引對象,后面的參數是索引位。
// "index x 1 2 3"代表的是x[1][2][3]。
// 可索引對象包括map、slice、array。
"index": index,
// 返回與其參數的文本表示形式等效的轉義JavaScript。
"js": JSEscaper,
// 返回參數的length。
"len": length,
// 布爾取反。只能一個參數。
"not": not,
// 返回第一個不為空的參數或最后一個參數。可以有任意多個參數。
// "or x y"等價於"if x then x else y"。
"or": or,
"print": fmt.Sprint,
"printf": fmt.Sprintf,
"println": fmt.Sprintln,
// 以適合嵌入到網址查詢中的形式返回其參數的文本表示的轉義值。
// 這個函數在html/template中不可用。
"urlquery": URLQueryEscaper,
}
<h1>內置函數</h1>
<p>{{len .name}}-->字節數</p>
3.9.3 比較函數
3.5模塊學過了
3.9.2 自定義函數
// 第一步:定義一個函數
func parserTime(t int64) string {
return time.Unix(t, 0).Format("2006年1月2日 15點04分05秒")
}
//第二步:在加載模板之前執行
router := gin.Default()
router.SetFuncMap(template.FuncMap{
"parserTime": parserTime,
})
//第三步:在模板中使用-->"date": time.Now().Unix(),
<h1>自定義模板函數</h1>
<p>不使用自定義模板函數:{{.date}}</p>
<p>使用自定義模板函數:{{parserTime .date}}</p>
</body>
3.10 模板嵌套
3.10.1 define
define可以直接在待解析內容中定義一個模板
// 定義名稱為name的template
{{ define "name" }} T {{ end }}
3.10.2 template
使用template來執行模板
// 執行名為name的template
{{ template "name" }} // 不加.,不能使用當前頁面的變量渲染define定義的模板
{{ template "name" . }} // 加入點,可以使用當前頁面的變量渲染define定義的模板
案例
header.html
{{define "header.html"}}
<style>
h1{
background: pink;
color: aqua;
text-align: center;
}
</style>
<h1>我是一個頭部--{{.header}}</h1>
{{end}}
footer.html
{{define "footer.html"}}
<style>
h1 {
background: pink;
color: aqua;
text-align: center;
}
</style>
<h1>我是一個尾部--{{.footer}}</h1>
{{end}}
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第一個模板文件</title>
</head>
<body>
{{ template "header.html" .}}
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
{{ template "footer.html" .}}
</body>
</html>
main.go
router.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"header": "頭部頭部",
"footer": "尾部尾部",
})
})
3.11 模板繼承
通過block、define、template實現模板繼承。 block
{{ block "name" pipeline }} T {{ end }}
block等價於define定義一個名為name的模板,並在"有需要"的地方執行這個模板,執行時將.設置為pipeline的值。
等價於:先 {{ define "name" }} T {{ end }} 再執行 {{ template "name" pipeline }}。
3.11.1 base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.head {
height: 50px;
background-color: red;
width: 100%;
text-align: center;
}
.main {
width: 100%;
}
.main .left {
width: 30%;
height: 1000px;
float: left;
background-color:violet;
text-align: center;
}
.main .right {
width: 70%;
float: left;
text-align: center;
height: 1000px;
background-color:yellowgreen;
}
</style>
</head>
<body>
<div class="head">
<h1>頂部標題部分</h1>
</div>
<div class="main">
<div class="left">
<h1>左側側邊欄</h1>
</div>
<div class="right">
{{ block "content" . }}
<h1>默認顯示內容</h1>
{{ end }}
</div>
</div>
</body>
</html>
3.11.2 home.html
{{ template "base.html" . }}
{{ define "content" }}
<h1>{{.s}}</h1>
{{ end }}
3.11.3 goods.html
{{ template "base.html" . }}
{{ define "content" }}
<h1>{{.s}}</h1>
{{ end }}
3.11.4 main.go
router.GET("/goods", func(c *gin.Context) {
c.HTML(200, "goods.html", gin.H{
"s": "這是商品goods頁面",
})
})
router.GET("/home", func(c *gin.Context) {
c.HTML(200, "home.html", gin.H{
"s": "這是首頁,home",
})
})
3.12 修改默認標識符
Go標准庫的模板引擎使用的花括號{{和}}作為標識,而許多前端框架(如Vue和 AngularJS)也使用{{和}}作為標識符,所以當我們同時使用Go語言模板引擎和以上前端框架時就會出現沖突,這個時候我們需要修改標識符,修改前端的或者修改Go語言的。這里演示如何修改Go語言模板引擎默認的標識符:
router.Delims("[[","]]")
3.13 xss攻擊
// 1 定義函數
func safe (str string) template.HTML {
return template.HTML(str)
}
//2 注冊函數
router.SetFuncMap(template.FuncMap{
"parserTime": parserTime,
"safe": safe,
})
// 3 模板中使用
<h1>xss攻擊</h1>
<p>{{.str1}}</p>
<p>{{safe .str1 }}</p>
