[golang gin框架] 2.Gin HTML模板渲染以及模板语法,自定义模板函数,静态文件服务
一.Gin HTML 模板渲染
全部模板放在一个目录里面的配置方法
首先在项目根目录新建 templates 文件夹,然后在文件夹中新建 对应的index.html
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<h1>这是一个 html 模板</h1>
<h3>{{.title}}</h3>
</body>
</html>
2.Gin 框架中使用 c.HTML 可以渲染模板,渲染模板前需要使用 LoadHTMLGlob()或者LoadHTMLFiles()方法加载模板
package main
import ( "net/http""github.com/gin-gonic/gin"
)func main() {//初始化路由router := gin.Default()//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行router.LoadHTMLGlob("templates/*")//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", gin.H{ "title": "首页", })})router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", map[string]interface{}{ "title": "前台首页"})})router.Run(":8080")
}
模板放在不同目录里面的配置方法
目录如下:

Gin 框架中如果不同目录下面有同名模板的话我们需要使用下面方法加载模板
注意:定义模板的时候需要通过 define 定义名称
//templates/admin/index.html
<!-- 相当于给模板定义一个名字 define end 成对出现-->{{ define "admin/index.html" }}
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<h1>后台模板</h1>
<h3>{{.title}}</h3>
</body>
</html>
{{ end }}
templates/default/index.html
<!-- 相当于给模板定义一个名字 define end 成对出现-->{{ define "default/index.html" }}
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<h1>前台模板</h1>
<h3>{{.title}}</h3>
</body>
</html>
{{end}}
代码如下:
package main
import ( "net/http"
"github.com/gin-gonic/gin"
)
func main() {router := gin.Default()router.LoadHTMLGlob("templates/**/*")router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", gin.H{ "title": "前台首页", })})router.GET("/admin", func(c *gin.Context) {c.HTML(http.StatusOK, "admin/index.html", gin.H{ "title": "后台首页", })})router.Run(":8080")
}
注意:如果模板在多级目录里面的话需要这样配置 r.LoadHTMLGlob("templates/**/**/*") /**
表示目录
3.Gin 模板基本语法
(1)、{{.}} 输出数据
模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象,当传入一个结构体对象时,可以根据.来访问结构体的对应字段。例如:
package mainimport ( "net/http""github.com/gin-gonic/gin"
)type UserInfo struct {Name stringGender stringAge int
}
func main() {router := gin.Default()router.LoadHTMLGlob("templates/**/*")user := UserInfo{Name: "张三",Gender: "男", Age: 18, }router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ "title": "前台首页", "user": user, })})router.Run(":8080")
}
模板 <!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "default/index.html" }}
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<h1>前台模板</h1>
<h3>{{.title}}</h3>
<h4>{{.user.Name}}</h4>
<h4>{{.user.Age}}</h4>
</body>
</html>
{{end}}
(2).注释
{{/* a comment */}}
注释,执行时会忽略,可以多行。注释不能嵌套,并且必须紧贴分界符始止
(3).变量
还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果,具体语法
如下:
<h4>{{$obj := .title}}</h4>
<h4>{{$obj}}</h4>
(4).移除空格
有时候在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲
染出来的内容可能就和想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有
空白符号, 使用-}}去除模板内容右侧的所有空白符号,例如:
{{- .Name -}}
注意:-要紧挨{{和}},同时与模板值之间需要使用空格分隔
(5).比较函数
布尔函数会将任何类型的零值视为假,其余视为真.
下面是定义为函数的二元比较运算的集合
eq | 如果 arg1 == arg2 则返回真 |
ne | 如果 arg1 != arg2 则返回真 |
lt | 如果 arg1 < arg2 则返回真 |
le | 如果 arg1 <= arg2 则返回真 |
gt | 如果 arg1 > arg2 则返回真 |
ge | 如果 arg1 >= arg2 则返回真 |
(6).条件判断
Go 模板语法中的条件判断有以下几种
{{if pipeline}} T1
{{end}}{{if pipeline}} T1
{{else}} T0
{{end}}{{if pipeline}} T1
{{else if pipeline}} T0
{{end}}{{if gt .score 60}}及格
{{else}}不及格
{{end}}{{if gt .score 90}}优秀
{{else if gt .score 60}}及格
{{else}}不及格
{{end}}
(6).range
Go 的模板语法中使用 range 关键字进行遍历,有以下两种写法,其中 pipeline 的值必须是数
组、切片、字典或者通道
{{range $key,$value := .obj}}{{$value}}
{{end}}
如果 pipeline 的值其长度为 0,不会有任何输出
{{$key,$value := .obj}}{{$value}}
{{else}}pipeline 的值其长度为 0
{{end}}
如果 pipeline 的值其长度为 0,则会执行 T0
router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ "hobby": []string{"吃饭", "睡觉", "写代码"}, })
})
{{range $key,$value := .hobby}}<p>{{$value}}</p>
{{end}}
(7).with
user := UserInfo{Name: "张三", Gender: "男", Age: 18,
}
router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ "user": user, })
})
以前要输出数据:
<h4>{{.user.Name}}</h4>
<h4>{{.user.Gender}}</h4>
<h4>{{.user.Age}}</h4>
现在要输出数据:
{{with .user}}<h4>姓名:{{.Name}}</h4><h4>性别:{{.Gender}}</h4><h4>年龄:{{.Age}}</h4>
{{end}}
相当于 var .=.user
(8).预定义函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典,一
般不在模板内定义函数,而是使用 Funcs 方法添加函数到模板里,预定义的全局函数如下:
and | 函数返回它的第一个 empty 参数或者最后一个参数; 就是说"and x y"等价于"if x then y else x";所有参数都会执行 |
or | 返回第一个非 empty 参数或者最后一个参数; 亦即"or x y"等价于"if x then x else y";所有参数都会执行 |
not | 返回它的单个参数的布尔值的否定 |
len | 返回它的参数的整数类型长度 |
index | 执行结果为第一个参数以剩下的参数为索引/键指向的值; 如"index x 1 2 3"返回 x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典 |
即 fmt.Sprint | |
printf | 即 fmt.Sprintf |
println | 即 fmt.Sprintln |
html | 返回与其参数的文本表示形式等效的转义 HTML。 这个函数在 html/template 中不可用 |
urlquery | 以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。 这个函数在 html/template 中不可用 |
js | 返回与其参数的文本表示形式等效的转义 JavaScript |
call | 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函 数的参数; 如"call .X.Y 1 2"等价于 go 语言里的 dot.X.Y(1, 2); 其中 Y 是函数类型的字段或者字典的值,或者其他类似情况; call 的第一个参数的执行结果必须是函数类型的值(和预定义函数如 print 明显不同); 该函数类型值必须有 1 到 2 个返回值,如果有 2 个则后一个必须是 error 接口类型; 如果有 2 个返回值的方法返回的 error 非 nil,模板执行会中断并返回给调用模板执行者 该错误 |
{{len .title}}
{{index .hobby 2}}
(9).自定义模板函数
package mainimport ( "fmt""html/template""net/http""time""github.com/gin-gonic/gin"
)func formatAsDate(t time.Time) string {year, month, day := t.Date()return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}func main() {router := gin.Default()//注册全局模板函数 注意顺序,注册模板函数需要在加载模板上面router.SetFuncMap(template.FuncMap{ "formatDate": formatAsDate,})//加载模板router.LoadHTMLGlob("templates/**/*")router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ "title": "前台首页", "now": time.Now(),})})router.Run(":8080")
}
{{.now | formatDate}}
//或者
{{formatDate .now }}
4.嵌套 template
(1).新建 templates/deafult/page_header.html
{{ define "default/page_header.html" }}
<h1>这是一个头部</h1>
{{end}}
(2).外部引入
注意:
1).引入的名字为 page_header.html 中定义的名字
2).引入的时候注意最后的点(.)
{{template "default/page_header.html" .}}
<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "default/index.html" }}
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
{{template "default/page_header.html" .}}
</body>
</html>
{{end}}
二.静态文件服务
当渲染的HTML 文件中引用了静态文件时,需要配置静态 web 服务r.Static("/static", "./static"),前面的/static 表示路由,后面的./static 表示路径
func main() {r := gin.Default()r.Static("/static", "./static")r.LoadHTMLGlob("templates/**/*")// ... r.Run(":8080")
}
<link rel="stylesheet" href="/static/css/base.css" />
案例目录如下:

代码如下:
main.go
package mainimport ("github.com/gin-gonic/gin""html/template""net/http""time"
)type Article struct {Title string `json:"title"`Content string `json:"
"`
}//时间戳转换成日期函数
func UnixToTime(timestamp int) string {t := time.Unix(int64(timestamp), 0)return t.Format("2006-01-02 15:04:05")
}func Println(str1 string, str2 string) string {return str1 + str2
}func main() {//初始化路由r := gin.Default()//自定义模板函数,必须在r.LoadHTMLGlob前面r.SetFuncMap(template.FuncMap{"UnixToTime":UnixToTime, //注册模板函数"Println": Println,})//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//配置路由//前台路由r.GET("/", func(c *gin.Context) {//渲染模板文件c.HTML(http.StatusOK, "default/index.html", gin.H{"title": "首页","score": 88,"hobby": []string{"吃饭", "睡觉", "打豆豆"}, // 切片"newList":[]interface{}{ // 接口&Article{Title: "新闻标题1",Content: "新闻内容1",},&Article{Title: "新闻标题2",Content: "新闻内容2",},},"testSlice":[]string{}, // 空数组/空切片"news": &Article{ // 结构体Title: "新闻标题3",Content: "新闻内容3",},"date": 1672648334,})})r.GET("/news", func(c *gin.Context) {news := &Article{Title: "新闻标题",Content: "新闻内容",}c.HTML(http.StatusOK, "default/news.html", gin.H{"title": "新闻详情","news": news,})})//后台路由r.GET("/admin", func(c *gin.Context) {//渲染模板文件c.HTML(http.StatusOK, "admin/index.html", gin.H{"title": "后台首页",})})r.GET("/admin/news", func(c *gin.Context) {news := &Article{Title: "后台新闻标题",Content: "后台新闻内容",}c.HTML(http.StatusOK, "admin/news.html", gin.H{"title": "后台新闻详情","news": news,})})r.Run() // 启动一个web服务
}
templates/admin/index.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/index.html" }}
<!DOCTYPE html>
<html lang="en">
<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>后台首页</title>
</head>
<body>
<h2>{{.title}}</h2>
</body>
</html>
{{ end }}
templates/admin/news.html
{{ define "admin/news.html" }}
<!DOCTYPE html>
<html lang="en">
<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>后台新闻详情</title>
</head>
<body>
<h2>{{.title}}</h2>
</body>
</html>
{{ end }}
templates/default/index.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "default/index.html" }}<!DOCTYPE html><html lang="en"><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"><link rel="stylesheet" href="/static/css/base.css"><title>首页</title></head><body>{{ template "public/page_header.html" .}}<h2>{{.title}}</h2><img src="/static/images/test.png"><!-- 定义变量 把后台的变量赋值给$t -->{{ $t := .title}}<!-- 显示$t --><h4> {{ $t }}</h4><!-- if判断 -->{{if gt .score 90}}<div>优秀</div><br/>{{else if gt .score 80}}<div>良好</div><br/>{{else if gt .score 70}}<div>可以</div><br/>{{ else }}<div>合格</div><br/>{{end}}<!-- 循环数据 --><ul><!-- 循环切片 -->{{range $k, $v := .hobby}}<li>{{$k}} => {{$v}}</li>{{end}}</ul><ul><!-- 循环结构体 -->{{range $k, $v := .newList}}<li>{{$k}} => {{$v.Title}} --- {{$v.Content}}</li>{{end}}</ul><ul><!-- 循环空数组/切片 判断 -->{{range $k, $v := .testSlice}}<li>{{$k}} => {{$v.Title}} --- {{$v.Content}}</li>{{else}}<li>空数据</li>{{end}}</ul><ul><!-- with 解析结构体-->{{with .news}}<li>{{.Title}} => {{.Content}}</li>{{end}}</ul><h4><!--预定义函数 -->{{.title}}的长度为{{len .title}}</h4><h4><!--自定义函数 -->{{UnixToTime .date}}{{Println .title .news.Title}}</h4><!-- 嵌套 template -->{{ template "public/page_footer.html" .}}</body></html>
{{end}}
templates/default/news.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "default/news.html" }}
<!DOCTYPE html>
<html lang="en">
<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>news</title>
</head>
<body>
{{ template "public/page_header.html" .}}<link rel="stylesheet" href="static/css/base.css">
<h2>{{.title}}</h2>
<h4>{{.news.Title}}{{.news.Content}}
</h4>
{{ template "public/page_footer.html" .}}
</body>
</html>
{{ end }}
templates/public/page_footer.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "public/page_footer.html" }}<h4>公共的底部</h4>
{{end}}
templates/public/page_header.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "public/page_header.html" }}<h1>公共的 --- {{.title}}</h1>
{{end}}
[上一节][golang gin框架] 1.Gin环境搭建,程序的热加载,路由GET,POST,PUT,DELETE