一、 模板配置

模板配置分为两种情况

1. 全部模板放在一个目录

在 templates 里放置所有模板,再在 main.go 里使用r.LoadHTMLGlob("templates/*")即可

2. 模板放在不同目录

这里以下面的结构为例子

1
2
3
4
5
6
7
8
9
10
test
go.mod
go.sum
│ main.go

└─templates
├─back
│ index.html //后端页面
└─front
index.html //前端页面

在前端页面的开头加上{{ define "front/index.html" }},并在结尾加上{{ end }}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{{ define "front/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>
</body>
</html>

{{ end }}

后端类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{{ define "back/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>
</body>
</html>

{{ end }}

这相当与给模板起一个名称,defineend 是成对出现的

回到 main.go ,加载模板的语句要变化,使用两个星号表示一层目录,并分别配置前后端路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()

// ** 表示一层目录
r.LoadHTMLGlob("templates/**/*")

// 前端路由
r.GET("/front", func(c *gin.Context) {
c.HTML(200, "front/index.html", gin.H{})
})

// 后端路由
r.GET("/back", func(c *gin.Context) {
c.HTML(200, "back/index.html", gin.H{})
})

r.Run()

}


当然,也可以选择用r.LoadHTMLFiles()来引用单个模板文件

二、模板语法

有关模板的语法很多

1、输出数据

模板语法都包含在{{` 和`}} 中间,其中{{.}} 中的点表示当前对象

当我们传入一个结构体对象时,我们可以根据.来访问结构体对于的字段

例如,在 main.go 中创建一个 UserInfo 类型

1
2
3
4
5
type UserInfo struct {
Name string
Gander string
Age int
}

在前端页面中添加对应的字段

1
2
3
<p>{{.user.Name}}</p>
<p>{{.user.Gander}}</p>
<p>{{.user.Age}}</p>

实例化一个 user ,并传递

1
2
3
4
5
6
7
8
9
10
11
12
user := UserInfo{
Name: "张三",
Gander: "男",
Age: 18,
}

// 前端路由
r.GET("/front", func(c *gin.Context) {
c.HTML(200, "front/index.html", gin.H{
"user": user,
})
})

2、解构结构体

在上面,我们使用这一段

1
2
3
<p>{{.user.Name}}</p>
<p>{{.user.Gander}}</p>
<p>{{.user.Age}}</p>

使用 with 可以解构结构体,简化这一步骤

1
2
3
4
5
{{with .user}}
<p>{{.Name}}</p>
<p>{{.Gander}}</p>
<p>{{.Age}}</p>
{{end}}

3、注释

1
{{/* a comment */}}

注释不能嵌套,并且必须紧贴分界符始止

4、变量

可以在模板中声明变量,来保存传入模板的数据或其他语句生成的结果,方法如下:

1
2
3
<h4>{{$t := .title}}</h4>

<h4>{{$t}}</h4>

5、移除空格

有时候我们在使用模板语法是时候会不可避免地引入空格或换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候就可以使用````去除模板内容右侧的所有空白符号

1
{{- .Name -}}

6、比较大小

  • eq 等于 ( == )
  • ne 不等于 ( != )
  • lt 小于 ( < )
  • le 小于等于 ( <= )
  • gt 大于 ( > )
  • ge 大于等于 ( >= )

注意,使用 eq A B来比较 A B 是否相等,而不是A eq B
比较大小常和下面的判断一起使用

7、条件判断

  • if-else-end 结构
1
2
3
4
5
{{if  gt .score 60}}
及格
{{else}}
不及格
{{end}}
  • if-else if-else-end 结构
1
2
3
4
5
6
7
{{if gt .score 90}}
优秀
{{else if gt .score 60}}
及格
{{else}}
不及格
{{end}}

8、遍历

Go的模板语法中使用 range 关键字进行遍历,有以下两种写法,其中 obj 必须是数组、切片、字典或者通道

1
2
3
{{range $key,$value := .obj}}
{{$key}}:{{$value}}
{{end}}
1
2
3
4
5
{{range $key,$value := .obj}}
{{$key}}:{{$value}}
{{else}}
没有数据
{{end}}

如果 obj 为空,则会返回"没有数据"

例:

1
2
3
4
5
6
7
8
9
10
11
12
r.GET("/front", func(c *gin.Context) {
c.HTML(200, "front/index.html", gin.H{
"user": user,
"hobby": []string{},
})
})

{{range $key,$value := .hobby}}
{{$key}}:{{$value}}
{{else}}
没有爱好
{{end}}

9、函数

函数有预定义的,还可以自行定义,但其实预定义的基本没什么用,所以一般都用自己定义的函数

  • 预定义函数
    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]的值;每个被索引的主体必须是数组、切片或者字典。
    print
    即 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,模板执行会中断并返回给调用模板执行者
    该错误;

  • 自定义函数
    例如,我们在后台有一个 UNIX 时间戳,希望在渲染模板时自动转换常用的时间格式

1
2
3
4
5
r.GET("/back", func(c *gin.Context) {
c.HTML(200, "back/index.html", gin.H{
"date":1642601992,
})
})

首先需要自己在 main.go 里先写一个实现这一功能的函数

1
2
3
4
func Unix2Time(timestamp int)string{
t:=time.Unix(int64(timestamp),0)
return t.Format("2000-01-02 03:04:05")
}

然后在加载模板上方创建引擎下方通过r.SetFuncMap()注册自定义模板函数

1
2
3
r.SetFuncMap(template.FuncMap{
"Unix2Time": Unix2Time,
})

现在,在模板中就可以调用函数了

1
{{Unix2Time .date}}

10、模板嵌套

比如,现在需要给前端和后端设计一个公共的标题
在 templates 中新建 public/page_header.html

1
2
3
4
5
{{ define "public/page_header.html" }}
<h1>
我是一个公共的标题
</h1>
{{ end }}

使用{{template "public/page_header.html" .}}引入**(注意末尾的点)**

当然,嵌套同一个模板并不表示会显示相同的内容,比如我们稍加修改,就能分别展示前端和后端的标题

三、静态文件服务

当我们渲染的 HTML 文件中引用了静态文件时(如css、js、图片等),我们需要使用r.Static配置静态 web 服务

1
2
3
4
5
6
7
8
func main() {
r := gin.Default()
r.Static("/static", "./static")
//前面的 /static 表示路由(从外部访问) 后面的./static 表示本地路径
r.LoadHTMLGlob("templates/**/*")
// ...
r.Run(":8080")
}
1
<link rel="stylesheet" href="/static/css/base.css" />

注意在模板里引用时 static 前面的斜杠不要漏