Gin HTML 模板渲染
# 1、全部模板放在一个目录里面的配置方法
1、我们首先在项目根目录新建 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 (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
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": "Main website"})
})
router.Run(":9090")
}
效果:
# 2、模板放在不同目录里面的配置方法
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 (
"github.com/gin-gonic/gin"
"net/http"
)
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(":9090")
}
效果:
注意:如果模板在多级目录里面的话需要这样配置
r.LoadHTMLGlob("templates/**/**/*")
,其中/**
表示目录
# 3、gin 模板基本语法
# 3.1、{ { . } }
模板语法都包含在{{
和}}
中间,其中 { { . } }
它们之间的点表示当前对象。
当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type UserInfo struct {
Name string
Gender string
Age 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(":9090")
}
模板
相当于给模板定义一个名字 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}}
# 3.2、注释
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。
{{/* a comment */}}
# 3.3、变量
我们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:
<h4>{{$obj := .title}}</h4>
<h4>{{$obj}}</h4>
# 3.4、移除空格
有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用 { {-语法去除模板内容左侧的所有空白符号, 使用-} }
去除模板内容右侧的所有空白符号。
例如:
{{- .Name -}}
注意:
-
要紧挨{{
和}}
,同时与模板值之间需要使用空格分隔。
# 3.5、比较函数
布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
- eq:如果 arg1 == arg2 则返回真
- ne:如果 arg1 != arg2 则返回真
- lt:如果 arg1 < arg2 则返回真
- le:如果 arg1 <= arg2 则返回真
- gt:如果 arg1 > arg2 则返回真
- ge:如果 arg1 >= arg2 则返回真
# 3.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}}
# 3.7、range
Go 的模板语法中使用 range 关键字进行遍历,有以下两种写法,其中 pipeline 的值必须是数组、切片、字典或者通道。
{{range $key,$value := .obj}}
{{$value}}
{{end}}
如果 pipeline 的值其长度为 0,不会有任何输出
{{$key,$value := .obj}}
{{$value}}
{{else}}
pipeline 的值其长度为 0
{{end}}
如果 pipeline 的值其长度为 0,则会执行 T0。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type UserInfo struct {
Name string
Gender string
Age 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,
"hobby": []string{"吃饭", "睡觉", "写代码"},
})
})
router.Run(":9090")
}
// index.html 添加以下内容
{{range $key,$value := .hobby}}
<p>{{$value}}</p>
{{end}}
# 3.8、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>性别:{{.user.Gender}}</h4>
<h4>年龄:{{.Age}}</h4>
{{end}}
简单理解:相当于 var .=.user
# 3.9、预定义函数 (了解)
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用 Funcs 方法添加函数到模板里。
预定义的全局函数如下:
# 3.9.1、逻辑运算
- and:返回 第一个 empty 参数 或 最后一个参数。等价于 if x then y else x。
📌 所有参数都会执行。
and x y // 等价于 if x then y else x
- or:返回 第一个非 empty 参数 或 最后一个参数。等价于 if x then x else y。
📌 所有参数都会执行。
or x y // 等价于 if x then x else y
- not:返回 参数的布尔值的否定。
not true // 返回 false
not false // 返回 true
# 3.9.2、数据处理
- len:返回 参数的长度(整数类型)。
len "hello" // 返回 5
len [1,2,3,4] // 返回 4
len {a:1, b:2} // 返回 2
- index:取 嵌套索引的值,类似
x[1][2][3]
。
📌 要求索引主体是数组、切片或字典。
index x 1 2 3 // 等价于 x[1][2][3]
# 3.9.3、格式化输出
- print:类似 fmt.Sprint,返回格式化的字符串。
print "Hello" 123 // "Hello123"
- printf:类似 fmt.Sprintf,支持格式化字符串。
printf "Hello %s" "World" // "Hello World"
- println:类似 fmt.Sprintln,返回带换行的格式化字符串。
println "Hello" "World" // "Hello World\n"
# 3.9.4、安全转义
(仅在非 html/template 下可用)
- html:返回 HTML 转义后的字符串。
html "<b>Bold</b>" // "<b>Bold</b>"
- urlquery:返回 URL 查询参数格式化后的值。
urlquery "a=b c" // "a%3Db+c"
- js:返回 JavaScript 转义后的字符串。
js `"alert('x')"` // "\"alert('x')\""
# 3.9.5、函数调用
- call:调用函数类型的值,支持 1-2 个返回值(第二个必须是 error 类型)。
- 如果返回 error 非 nil,模板执行会终止并返回错误。
call .X.Y 1 2 // 等价于 Go 代码中的 dot.X.Y(1, 2)
# 3.9.6、总结
类别 | 函数 | 作用 |
---|---|---|
逻辑运算 | and | 返回第一个 empty 参数或最后一个参数(if x then y else x) |
逻辑运算 | or | 返回第一个非 empty 参数或最后一个参数(if x then x else y) |
逻辑运算 | not | 取布尔值的否定 |
数据处理 | len | 计算参数长度 |
数据处理 | index | 取嵌套索引的值 |
格式化输出 | 类似 fmt.Sprint | |
格式化输出 | printf | 类似 fmt.Sprintf |
格式化输出 | println | 类似 fmt.Sprintln |
安全转义 | html | HTML 转义(< → <) |
安全转义 | urlquery | URL 查询参数转义 |
安全转义 | js | JavaScript 转义 |
函数调用 | call | 调用函数,支持 1-2 个返回值 |
# 3.10、自定义模板函数
router.SetFuncMap(template.FuncMap{
"formatDate": formatAsDate,
})
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
"time"
)
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 }}
效果:
# 3.11、嵌套 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}}
效果: