8 html 常用标签与模板
web 开发就是动态的生成响应结果
比如有时候我们需要给用户响应一个 html 格式,html 是一个超文本标记语言。
HTML 是用来描述网页的一种语言。
-
HTML 指的是超文本标记语言 (Hyper Text Markup Language)
-
HTML 不是一种编程语言,而是一种标记语言 (markup language)
-
标记语言是一套标记标签 (markup tag)
-
HTML 使用标记标签来描述网页
HTML 标签
HTML 标记标签通常被称为 HTML 标签 (HTML tag)。
-
HTML 标签是由尖括号包围的关键词,比如 <html>
-
HTML 标签通常是成对出现的,比如 和
-
标签对中的第一个标签是开始标签,第二个标签是结束标签
-
开始和结束标签也被称为开放标签和闭合标签
HTML 文档 = 网页
-
HTML 文档描述网页
-
HTML 文档包含 HTML 标签和纯文本
-
HTML 文档也被称为网页
-
Web 浏览器的作用是读取 HTML 文档,并以网页的形式显示出它们。浏览器不会显示 HTML 标签,而是使用标签来解释页面的内容:
如上图:
-
<html> 与 <html> 之间的文本描述网页
-
<body> 与 <body> 之间的文本是可见的页面内容
-
<h1> 与 <h1> 之间的文本被显示为标题
-
<p> 与 <p> 之间的文本被显示为段落
8.1 HTML 模板第一个范例
我们一般更多的时候实在 BODY 中进行编写,body 里面的话就是告诉用户我要显示的内容
第一个范例:
package main
import (
"fmt"
"net/http"
)
// 定义 html 变量
var html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>我的第一个页面</title>
</head>
<body>
我叫zz
</body>
</html>
`
func main() {
addr := ":8888"
// 动态的生成响应结果
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
fmt.Fprint(rw, html)
})
http.ListenAndServe(addr, nil)
}
浏览器访问,就会得到我的第一个页面,并且还会将 body 中的 我叫zz 的数据渲染到浏览器上
8.2 HTML 中常用标签
常用标签
-
注释
<!-- 注释内容 -->
-
标题
h1~h6
-
段落 p
-
超链接 a
-
图片
img
-
表单
form
-
input: text/password/radio/checkbox/file/date/datetime/url/submit/hidden
-
textarea
-
select/option
-
-
按钮 button
-
表格
table/thead/tbody/tr/td/th
-
列表
ol/ul/li
有序列别和无序列表 -
块
div/span
标签范例演示
package main
import (
"fmt"
"net/http"
)
// 定义 html 变量
var html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>我的第一个页面</title>
</head>
<body>
<!-- 我是注释 -->
我叫zz
<!-- 标题范例 -->
<h1> 1级标题 </h1>
<h2> 2级标题 </h2>
<h3> 3级标题 </h3>
<h4> 4级标题 </h4>
<h5> 5级标题 </h5>
<h5> 6级标题 </h6>
<!-- 段落 -->
<p> 第1段 </p>
<p> 第2段 </p>
<p> 第3段 </p>
<!-- 超链接 -->
<!-- href="http://www.baidu.com" 可以跳转百度 -->
<!-- target="_blank" 打开新的页面不会关闭旧页面-->
<a href="http://www.baidu.com" target="_blank">百度</a>
<!-- 在页面展示图片,src 直接可以写对应图片的 url -->
<img src="http://39.105.137.222:8089/wp-content/uploads/2021/07/image-20210723114444753.png" />
<!-- 按照表格的方式显示数据 -->
<table>
<thead>
<tr>
<!-- 标头 -->
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
</tr>
</thead>
<!-- 显示表格数据 -->
<tbody>
<tr>
<td>01</td>
<td>zzz</td>
<td>北京</td>
</tr>
<tr>
<td>02</td>
<td>ggg</td>
<td>重庆</td>
</tr>
</tbody>
</table>
<!-- 列表 -->
<ol>
<li>有序列表</li>
<li>洗衣</li>
<li>做饭</li>
<li>睡觉</li>
</ol>
<ul>
<li>无序列表</li>
<li>上班</li>
<li>学习</li>
<li>养老</li>
</ul>
</body>
<!-- 块标签 -->
<div>块1</div>
<div>块2</div>
<span>SPAN1</span>
<span>SPAN2</span>
<!-- 提交数据 action提交位置 method提交方式,默认为 GET -->
<form action="" method="GET">
<!-- label显示输入信息,这个输入信息名为 用户名 类型是 text, br为换行-->
<label>用户名</label><input name="用户名" type="text" /> <br/>
<!-- label显示输入信息,这个输入信息名为 用户密码 类型是 password 输入的时候是隐式的 -->
<label>用户密码</label><input name="用户密码" type="password" /><br/>
<!-- 选择标签,在 web 页面提供选择功能 -->
<label>性别</label><br/>
<!-- radio 中 name 一样的话表示为一组 -->
<input type="radio" name="sex" value="1"/><label>男</label><br/>
<input type="radio" name="sex" value="0"/><label>女</label><br/>
<!-- 多选框,也就是说可以同时选择多个属性 -->
<label>爱好</label><br/>
<!-- checkbox 中 hobby 一样的话表示为一组 -->
<input type="checkbox" name="hobby" value="1"/><label>足球</label><br/>
<input type="checkbox" name="hobby" value="2"/><label>篮球</label><br/>
<input type="checkbox" name="hobby" value="3"/><label>曲棍球</label><br/>
<!-- 下拉框 -->
<label>部门下拉框</label><br/>
<select name="department">
<option value="dev">开发</option>
<option value="test">测试</option>
<option value="ops">运维</option>
</select><br/>
<!-- 有些时候我们需要输入很大一块信息就使用下面这个标签 -->
<label>备注</label>
<textarea name="remark">我叫...</textarea><br/>
<!-- 登录按钮 类型为 submit,默认显示为提交按钮,通过 url 进行提交当我们想修改他的默认显示,通过 value-->
<input type="submit" value="Q我"/><br/>
</form>
</html>
`
func main() {
addr := ":8888"
// 动态的生成响应结果
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
fmt.Fprint(rw, html)
})
http.ListenAndServe(addr, nil)
}
浏览器渲染
接下来我们如何将 HTML 应用到我们的项目中去
8.3 将 HTML 应用到项目中
上面案例中只要修改了 html 文本我们就需要重启程序,这个时候我们就需要将 html 放入到一个文件中去,我们在请求的时候只需要在文件中拿去就可以了。
这里我们创建一个 template 目录,将我们的 html 文本内容复制到这个 index.html 文件中,然后再通过 go 将这个文件读取出来
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
addr := ":8888"
filePath := `./template/index.html`
// 动态的生成响应结果
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
// 打开 index.html 文件
f, err := os.Open(filePath)
if err != nil {
fmt.Println(err)
return
}
// 客户端获取到服务器端 body
io.Copy(rw, f)
})
http.ListenAndServe(addr, nil)
}
这样做的好处就是我们去修改这个 index.html 文件的内容也不需要重启程序,而是每次程序自动加载,这里我修改了 我叫zz1111111 的字段,浏览器中的加载
现在虽然解决了 html 文件修改后自动加载了,但是依旧没有解决动态生成页面的问题。下面我们就需要了解 template 模板技术
8.4 template 模板技术
在 go 中提供了以下两种模板技术
-
text/template
:实现数据驱动模板以生成文本输出,可以理解为一组文字按照特定格式动态嵌入另一组文字中。 -
html/template
:用于 web 开发,防止注入的功能,模板 + 数据 + 引擎(用于组装为一个字符串)
func template.New(name string) *template.Template
[10:26:46 root@go data]#go doc template.template
package template // import "html/template"
type Template struct {
// The underlying template's parse tree, updated to be HTML-safe.
Tree *parse.Tree
// Has unexported fields.
}
Template is a specialized Template from "text/template" that produces a safe
HTML document fragment.
func Must(t *Template, err error) *Template
func New(name string) *Template
func ParseFS(fs fs.FS, patterns ...string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)
func (t *Template) Clone() (*Template, error)
func (t *Template) DefinedTemplates() string
func (t *Template) Delims(left, right string) *Template
func (t *Template) Execute(wr io.Writer, data interface{}) error # 将数据获取
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
func (t *Template) Funcs(funcMap FuncMap) *Template
func (t *Template) Lookup(name string) *Template
func (t *Template) Name() string
func (t *Template) New(name string) *Template
func (t *Template) Option(opt ...string) *Template
func (t *Template) Parse(text string) (*Template, error) # 用于解析
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
func (t *Template) ParseGlob(pattern string) (*Template, error)
func (t *Template) Templates() []*Template
8.4.1 template 模板语法入门
8.4.1.1 template 基础语法
范例如下:
package main
import (
"fmt"
"os"
"text/template"
)
func main() {
// 在使用模板之前需要先解析模板
// 定义 tpl 模板,在模板中占位通过两个{} 中间一个 .
text := "我的名字加{{.}}\n"
// 解析模板:模板是一个字符串
tpl, err := template.New("test").Parse(text)
if err != nil {
fmt.Println(err)
return
}
// 通过 Execute 传递 zz ,这里的 zz 就会替代上面text 中的{{.}}
// 由于 Execute 中第二个参数是一个空接口所有能够接受任何数据类型
// 下面是基础语法演示范例
tpl.Execute(os.Stdout, "zz")
tpl.Execute(os.Stdout, 1)
tpl.Execute(os.Stdout, true)
tpl.Execute(os.Stdout, []int{1, 2, 3})
tpl.Execute(os.Stdout, map[string]string{"1": "2"})
}
[10:40:27 root@go template]#go run main.go
我的名字加zz
我的名字加1
我的名字加true
我的名字加[1 2 3]
我的名字加map[1:2]
8.4.1.2 template 流程判断语法
在 template 中也可以实现对语句的流程判断
package main
import (
"os"
"text/template"
)
func main() {
// 还能用于判断,必须通过 end 结尾
// if . 判断 . 是否为真,真就执行输出男, else 输出女
text := "性别: {{ if .}} 男 {{ else }} 女 {{ end }}\n"
tpl, _ := template.New("test").Parse(text)
// 输入男
tpl.Execute(os.Stdout, true)
// 输出女
tpl.Execute(os.Stdout, false)
// if 大于小于判断语法
// gt >
// lt <
// gte >=
// lte <=
// eq =
// neq !=
text = "年龄是否超过 18 : {{ if eq 18 .}} 等于 18 {{else }} 不等于 18 {{end}}\n"
tpl, _ = template.New("test").Parse(text)
tpl.Execute(os.Stdout, 1)
}
[10:55:30 root@go template]#go run main.go
性别: 男
性别: 女
年龄是否超过 18 : 不等于 18
8.4.1.3 template 循环
在 template 中遍历数据
package main
import (
"os"
"text/template"
)
func main() {
// 遍历使用 range , 结束使用 end
// range括号里面的 . 是每次需要遍历的元素。外部 . 是每次遍历后的结果
// 这里竖线为分输出的时候用于隔符
text := "学生列表:{{ range . }} {{.}}| {{end}}\n"
tpl, _ := template.New("test").Parse(text)
tpl.Execute(os.Stdout, []string{"aa", "bb", "cc", "dd"})
}
[11:03:19 root@go template]#go run main.go
学生列表: aa| bb| cc| dd|
8.4.2 template 复制数据结构
8.4.2.1 获取切片中的元素
package main
import (
"os"
"text/template"
)
func main() {
// . 为我们传递到模板中的数据,如切片
// index . 0 就是获取索引为 0 的元素
text := "第一个元素:{{ index . 0}}\n第二个元素:{{index . 1}}\n"
tpl, _ := template.New("test").Parse(text)
tpl.Execute(os.Stdout, []string{"1", "2"})
}
[11:08:31 root@go template]#go run main.go
第一个元素:1
第二个元素:2
8.4.2.2 获取 map 中的元素
package main
import (
"os"
"text/template"
)
func main() {
// . 为我们传递到模板中的数据,如 map
// {{ .name }} 获取 map 中 name 的值 zzz
// {{ .addr }} 获取 map 中 addr 的值 111
text := "name:{{ .name }} addr:{{ .addr }}\n"
tpl, _ := template.New("test").Parse(text)
tpl.Execute(os.Stdout, map[string]string{"name": "zzz", "addr": "111"})
// 如果传递的 map 中没有 addr 的值,输出 no value
tpl.Execute(os.Stdout, map[string]string{"name": "zzz"})
}
[11:12:15 root@go template]#go run main.go
name:zzz addr:111
name:zzz addr:<no value>
8.4.2.3 获取结构体数据
package main
import (
"os"
"text/template"
)
func main() {
// . 为我们传递到模板中的数据,如结构体
// . Name 获取结构体中 Name 属性
// . Addr 获取结构体中 Addr 属性
text := "name:{{ .Name}} addr:{{ .Addr}}\n"
tpl, _ := template.New("test").Parse(text)
// 这里传入我们的 结构体
tpl.Execute(os.Stdout, struct {
Name string
Addr string
}{"zzz", "bbb"})
}
/*
下面这种写法也是支持的,就是在结构体中多个属性的数据类型一样可以通过这种方式缩写:
tpl.Execute(os.Stdout, struct{ Name, Addr string }{"zzz", "bbb"})
*/
[11:20:20 root@go template]#go run main.go
name:zzz addr:bbb
8.4.3 从文件中解析模板
当然也可以将模板的数据写到文件中,然后通过 template.ParseFiles
方法进行文件解析
func (*template.Template).ParseFiles(filenames ...string) (*template.Template, error)
创建一个 user.html 文件
package main
import (
"os"
"text/template"
)
func main() {
// 从 user.html 文件中解析数据
tpl, _ := template.ParseFiles("user.html")
// 通过 ExecuteTemplate 执行 user.html
// 当我们有多个 模板文件的时候,需要在第二个参数的地方指定我们要执行的模板文件
tpl.ExecuteTemplate(os.Stdout, "user.html", struct{ Name, Addr string }{"zz", "bb\n"})
}
[12:34:14 root@go template]#go run main.go
我的第一个模板
name:zz addr:bb
8.4.4 模板结合web 页面使用(用户管理列表)
两层遍历,第一次遍历切片,然后再遍历 map,如下范例,我们将用户信息输出再浏览器上
1.编写程序文件
package main
import (
"log"
"net/http"
"text/template"
)
// 创建用户的结构体
type User struct {
Id int
Name string
Sex bool
Addr string
}
func main() {
// 多个用户信息使用到结构体切片
users := []*User{
{1, "aa", true, "xxxxx1"},
{2, "bb", false, "xxxxx2"},
{3, "cc", true, "xxxxx3"},
}
addr := ":8888"
// 创建一个 http 的页面
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
// 将 template/user.html 进行解析
tpl, err := template.ParseFiles("template/user.html")
if err != nil {
log.Println(err)
return
}
// 输出到 rw 也就是网页上,指定使用 user.html 文件为模板,传递数据是 users 结构体
tpl.ExecuteTemplate(rw, "user.html", users)
})
// 由于在 html 中新建页面跳转到 create 的 URI 所以我们需要在写一个 create 的处理函数
http.HandleFunc("/create/", func(rw http.ResponseWriter, r *http.Request) {
tpl, err := template.ParseFiles("template/create.html")
if err != nil {
log.Printf("[create err ]: %v", err)
return
}
// 没有传递数据第三个参数我们写 nil 即可
tpl.ExecuteTemplate(rw, "create.html", nil)
})
http.ListenAndServe(addr, nil)
}
2.创建 user.html 模板文件
<html>
<head>
<meta charset="utf-8" />
<title>用户管理</title>
</head>
<body>
<!-- 新建页面跳转到 create URI -->
<a href="/create/">新建</a>
<table>
<thead>
<tr>
<th>用户Id</th>
<th>用户名</th>
<th>性别</th>
<th>地址</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 定义模板循环遍历传递过来的 users []结构体 -->
{{ range . }}
<!-- range 每个元素就是一行,开始遍历传递过来的切片中的单个属性,以表格数据显示 -->
<tr>
<td>{{.Id}}</td>
<td>{{.Name}}</td>
<td>{{if .Sex}} 男 {{ else }}女 {{ end }}</td>
<td>{{.Addr}}</td>
<td>
<a>编辑</a>
<a>删除</a>
</td>
</tr>
{{ . }}
{{ end }}
</tbody>
</table>
</body>
</html>
3.编写 create.html 文件
由于我们实现了跳转所以需要在编写一个 create.html 文件
<html>
<head>
<meta charset="utf-8"/>
<title>新建页面</title>
</head>
<body>
<!-- 提交到 create 路径下,提交方式为 post -->
<form action="/create/" method="POST">
<label>姓名</label><input name="name" value=""/> <br/>
<label>性别</label>
<input name="sex" type="radio" value="1"/>男
<input name="sex" type="radio" value="0"/>女<br/>
<label>住址</label><textarea name="addr" value=""></textarea> <br/>
<input type="submit" value="创建" />
</form>
</body>
</html>
浏览器访问 / 路径
点击跳转之后访问的 create 路径
但是当我们输入了用户姓名和性别点击创建之后,并且请求方式为 POST,处理器函数又把 create 页面加载出来等于说我们什么事都没有干,那我们可以通过请求方式进行判断
那这个时候我们就又个问题,我们该如何判断 form 是通过 A 标签来的呢?所以这个时候我们就可以在开发的时候标识他们来自于那个标签。
这个时候我们 F12 打开该页面的 headers ,可以观察到他是一个 GET 请求,但是我们在 create.html 文件中已将 method 改为了 POST ,所以这个时候我们可以在 主程序中通过 POST 来进行判断,如果是 GET 请求的话我们加载页面,如果是 POST 请求的话我们需要添加数据,并且添加完了数据之后我们还需要跳转到用户列表页面
8.4.4.1 判断请求方法来进行处理(用户添加)
package main
import (
"log"
"net/http"
"text/template"
)
// 创建用户的结构体
type User struct {
Id int64
Name string
Sex bool
Addr string
}
func main() {
// 多个用户信息使用到结构体切片
users := []*User{
{1, "aa", true, "xxxxx1"},
{2, "bb", false, "xxxxx2"},
{3, "cc", true, "xxxxx3"},
}
addr := ":8888"
// 创建一个 http 的页面
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
// 将 template/user.html 进行解析
tpl, err := template.ParseFiles("template/user.html")
if err != nil {
log.Println(err)
return
}
// 输出到 rw 也就是网页上,指定使用 user.html 文件为模板,传递数据是 users 结构体
tpl.ExecuteTemplate(rw, "user.html", users)
})
// 由于在 html 中新建页面跳转到 create 的 URI 所以我们需要在写一个 create 的处理函数
http.HandleFunc("/create/", func(rw http.ResponseWriter, r *http.Request) {
// Method == "GET" 直接加载页面
if r.Method == "GET" {
tpl, err := template.ParseFiles("template/create.html")
if err != nil {
log.Printf("[create err ]: %v", err)
return
}
// 没有传递数据第三个参数我们写 nil 即可
tpl.ExecuteTemplate(rw, "create.html", nil)
} else {
// 否则添加数据,通过 creat.html 中的表名字段来获取值
users = append(users, &User{
// 每次 id 加1
int64(len(users) + 1),
r.FormValue("name"),
r.FormValue("sex") == "1",
r.FormValue("addr"),
})
// 现在数据已经添加了我们就需要跳转到 用户页面,重定向让后端告诉浏览器重新请求地址
// Redirect 第一个参数是 http.ResponseWriter
// Redirect 第二个参数是 *http.Request
// Redirect 第三个参数是 重定向的 URL
// Redirect 第四个参数是 状态码,302 临时重定向
http.Redirect(rw, r, "/", 302)
}
})
http.ListenAndServe(addr, nil)
}
启动程序
点击新建
跳转到 create URI,创建数据点击提交
完成之后跳转到了 / 页面,并有了 id 为 4 的用户信息
8.4.4.2 判断请求方法来进行处理(用户删除)
我们一般都是说删除哪一个数据,删除的话肯定需要给后端传递想要删除的数据。
我们可以通过用户 ID 进行删除,并且一定会有一个想要删除的函数,所以就的有一个 delete 页面
package main
import (
"log"
"net/http"
"strconv"
"text/template"
)
// 创建用户的结构体
type User struct {
Id int64
Name string
Sex bool
Addr string
}
func main() {
// 多个用户信息使用到结构体切片
users := []*User{
{1, "aa", true, "xxxxx1"},
{2, "bb", false, "xxxxx2"},
{3, "cc", true, "xxxxx3"},
}
addr := ":8888"
// 显示用户列表
// 创建一个 http 的页面
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
// 将 template/user.html 进行解析
tpl, err := template.ParseFiles("template/user.html")
if err != nil {
log.Println(err)
return
}
// 输出到 rw 也就是网页上,指定使用 user.html 文件为模板,传递数据是 users 结构体
tpl.ExecuteTemplate(rw, "user.html", users)
})
// 用户添加
// 由于在 html 中新建页面跳转到 create 的 URI 所以我们需要在写一个 create 的处理函数
http.HandleFunc("/create/", func(rw http.ResponseWriter, r *http.Request) {
// Method == "GET" 直接加载页面
if r.Method == "GET" {
tpl, err := template.ParseFiles("template/create.html")
if err != nil {
log.Printf("[create err ]: %v\n", err)
return
}
// 没有传递数据第三个参数我们写 nil 即可
tpl.ExecuteTemplate(rw, "create.html", nil)
} else {
// 否则添加数据,通过 creat.html 中的表名字段来获取值
users = append(users, &User{
// 每次 id 加1
int64(len(users) + 1),
r.FormValue("name"),
r.FormValue("sex") == "1",
r.FormValue("addr"),
})
// 现在数据已经添加了我们就需要跳转到 用户页面,重定向让后端告诉浏览器重新请求地址
// Redirect 第一个参数是 http.ResponseWriter
// Redirect 第二个参数是 *http.Request
// Redirect 第三个参数是 重定向的 URL
// Redirect 第四个参数是 状态码,302 临时重定向
http.Redirect(rw, r, "/", 302)
}
})
// 用户删除
http.HandleFunc("/delete/", func(rw http.ResponseWriter, r *http.Request) {
// 删除肯定需要先获取数据,这里是通过 id 删除
// FormValue 返回值是一个 string 所以我们需要将 string 转为一个 int64 类型
// 判断我们输入的想要删除的 id 是否和我们 user 结构体里面的 id 相等
// ParseInt 第一个参数为 string 类型,10 为 十进制,64 为 int 64 类型
if id, err := strconv.ParseInt(r.FormValue("id"), 10, 64); err == nil {
// make 一个切片长度为 0 个元素
NewUsers := make([]*User, 0)
for _, user := range users {
// 将 users 赋值给了 user 遍历判断 user.ID 和用传入的 ID 是否相同
// 相同就退出当前当前循环,进入到下一次循环赋值
if user.Id == id {
continue
}
// 将 id 判断后的 user 追加给 NewUsers 结构体切片
NewUsers = append(NewUsers, user)
}
// 最后重新赋值给 users 变量
users = NewUsers
}
// 删除之后重定向到用户列表页面
http.Redirect(rw, r, "/", 302)
})
http.ListenAndServe(addr, nil)
}
现在已经将用户删除、用户添加、用户显示、功能写完了,其实后端功能有了,但是前端还没有数据,所以开始编写前端代码
<html>
<head>
<meta charset="utf-8" />
<title>用户管理</title>
</head>
<body>
<!-- 新建页面跳转到 create URI -->
<a href="/create/">新建</a>
<table>
<thead>
<tr>
<th>用户Id</th>
<th>用户名</th>
<th>性别</th>
<th>地址</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 定义模板循环遍历传递过来的 users []结构体 -->
{{ range . }}
<!-- range 每个元素就是一行,开始遍历传递过来的切片中的单个属性,以表格数据显示 -->
<tr>
<td>{{.Id}}</td>
<td>{{.Name}}</td>
<td>{{if .Sex}} 男 {{ else }}女 {{ end }}</td>
<td>{{.Addr}}</td>
<td>
<a>编辑</a>
<!-- web 页面输入 id = 遍历当前用户的 id 执行 后端 delete 函数-->
<a href="/delete/?id={{ .Id}}">删除</a>
</td>
</tr>
{{ . }}
{{ end }}
</tbody>
</table>
</body>
</html>
浏览器访问,从图中我们看到是 3 个用户信息
这里我删除了 id 为 3 的用户信息,已被删除
点击新建,也能创建一个 test 用户