模板渲染 #
一、模板渲染基础 #
1.1 渲染方法 #
go
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "index.html", map[string]interface{}{
"title": "首页",
"name": "张三",
})
})
1.2 数据传递 #
go
type PageData struct {
Title string
User *User
Message string
}
e.GET("/", func(c echo.Context) error {
data := PageData{
Title: "首页",
User: &User{ID: 1, Name: "张三"},
Message: "Welcome!",
}
return c.Render(http.StatusOK, "index.html", data)
})
二、Go模板语法 #
2.1 变量输出 #
html
<p>{{.Title}}</p>
<p>{{.User.Name}}</p>
<p>{{.User.Email}}</p>
2.2 条件判断 #
html
{{if .User}}
<p>Welcome, {{.User.Name}}!</p>
{{else}}
<p>Please login</p>
{{end}}
{{if eq .Status "active"}}
<span class="badge">Active</span>
{{else if eq .Status "inactive"}}
<span class="badge">Inactive</span>
{{else}}
<span class="badge">Unknown</span>
{{end}}
2.3 循环遍历 #
html
<ul>
{{range .Users}}
<li>{{.Name}} - {{.Email}}</li>
{{end}}
</ul>
<table>
{{range $index, $user := .Users}}
<tr>
<td>{{$index}}</td>
<td>{{$user.Name}}</td>
<td>{{$user.Email}}</td>
</tr>
{{end}}
</table>
2.4 模板包含 #
html
{{template "header.html" .}}
{{template "header.html" .}}
<main>
<h1>{{.Title}}</h1>
<p>{{.Content}}</p>
</main>
{{template "footer.html" .}}
2.5 定义模板 #
html
{{define "header.html"}}
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
{{end}}
2.6 管道操作 #
html
<p>{{.Name | upper}}</p>
<p>{{.Content | truncate 100}}</p>
<p>{{.Email | lower}}</p>
三、布局模板 #
3.1 基础布局 #
views/layouts/base.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
<link rel="stylesheet" href="/static/css/style.css">
{{block "head" .}}{{end}}
</head>
<body>
{{template "header" .}}
<main>
{{block "content" .}}{{end}}
</main>
{{template "footer" .}}
<script src="/static/js/app.js"></script>
{{block "scripts" .}}{{end}}
</body>
</html>
3.2 页面模板 #
views/pages/index.html
html
{{define "head"}}
<link rel="stylesheet" href="/static/css/home.css">
{{end}}
{{define "content"}}
<h1>Welcome to Echo!</h1>
<p>{{.Message}}</p>
<ul>
{{range .Users}}
<li>{{.Name}}</li>
{{end}}
</ul>
{{end}}
{{define "scripts"}}
<script src="/static/js/home.js"></script>
{{end}}
3.3 配置布局 #
go
func main() {
e := echo.New()
t := &Template{
templates: template.Must(template.ParseFiles(
"views/layouts/base.html",
"views/partials/header.html",
"views/partials/footer.html",
"views/pages/index.html",
"views/pages/about.html",
)),
}
e.Renderer = t
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "base.html", map[string]interface{}{
"Title": "首页",
"Message": "Welcome!",
"Users": []User{
{Name: "张三"},
{Name: "李四"},
},
})
})
e.Start(":8080")
}
四、模板函数 #
4.1 内置函数 #
| 函数 | 说明 |
|---|---|
| and | 逻辑与 |
| or | 逻辑或 |
| not | 逻辑非 |
| eq | 等于 |
| ne | 不等于 |
| lt | 小于 |
| le | 小于等于 |
| gt | 大于 |
| ge | 大于等于 |
| len | 长度 |
| index | 索引访问 |
4.2 自定义函数 #
go
func main() {
e := echo.New()
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
"trim": strings.TrimSpace,
"safe": func(s string) template.HTML {
return template.HTML(s)
},
"truncate": func(s string, n int) string {
if len(s) > n {
return s[:n] + "..."
}
return s
},
"date": func(t time.Time) string {
return t.Format("2006-01-02")
},
"datetime": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
},
"add": func(a, b int) int {
return a + b
},
"sub": func(a, b int) int {
return a - b
},
"mul": func(a, b int) int {
return a * b
},
"div": func(a, b int) int {
return a / b
},
}
t := &Template{
templates: template.Must(template.New("").Funcs(funcMap).ParseGlob("views/**/*.html")),
}
e.Renderer = t
e.Start(":8080")
}
4.3 使用自定义函数 #
html
<p>{{.Name | upper}}</p>
<p>{{.Description | truncate 100}}</p>
<p>{{.CreatedAt | date}}</p>
<p>{{.CreatedAt | datetime}}</p>
<p>Sum: {{add 1 2}}</p>
<p>{{safe "<strong>Bold text</strong>"}}</p>
五、模板继承 #
5.1 定义基础模板 #
views/layouts/base.html
html
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
{{block "head" .}}{{end}}
</head>
<body>
{{block "header" .}}
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
{{end}}
{{block "content" .}}{{end}}
{{block "footer" .}}
<footer>
<p>© 2024 My App</p>
</footer>
{{end}}
</body>
</html>
{{end}}
5.2 继承模板 #
views/pages/index.html
html
{{template "base" .}}
{{define "head"}}
<link rel="stylesheet" href="/css/home.css">
{{end}}
{{define "content"}}
<h1>Home Page</h1>
<p>{{.Message}}</p>
{{end}}
views/pages/about.html
html
{{template "base" .}}
{{define "head"}}
<link rel="stylesheet" href="/css/about.css">
{{end}}
{{define "content"}}
<h1>About Page</h1>
<p>{{.Content}}</p>
{{end}}
5.3 渲染继承模板 #
go
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "index.html", map[string]interface{}{
"Title": "首页",
"Message": "Welcome!",
})
})
e.GET("/about", func(c echo.Context) error {
return c.Render(http.StatusOK, "about.html", map[string]interface{}{
"Title": "关于",
"Content": "About content...",
})
})
六、部分模板 #
6.1 定义部分模板 #
views/partials/header.html
html
{{define "header"}}
<header class="header">
<div class="container">
<nav class="nav">
<a href="/" class="logo">My App</a>
<ul class="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
{{if .User}}
<div class="user-menu">
<span>{{.User.Name}}</span>
<a href="/logout">Logout</a>
</div>
{{else}}
<div class="auth-links">
<a href="/login">Login</a>
<a href="/register">Register</a>
</div>
{{end}}
</nav>
</div>
</header>
{{end}}
views/partials/footer.html
html
{{define "footer"}}
<footer class="footer">
<div class="container">
<p>© 2024 My App. All rights reserved.</p>
</div>
</footer>
{{end}}
6.2 使用部分模板 #
html
{{template "header" .}}
<main class="main">
{{block "content" .}}{{end}}
</main>
{{template "footer" .}}
七、表单渲染 #
7.1 表单模板 #
html
<form action="/login" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="{{.Username}}">
{{if .Errors.username}}
<span class="error">{{.Errors.username}}</span>
{{end}}
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password">
{{if .Errors.password}}
<span class="error">{{.Errors.password}}</span>
{{end}}
</div>
<button type="submit">Login</button>
</form>
7.2 CSRF保护 #
go
e.Use(middleware.CSRF())
e.GET("/login", func(c echo.Context) error {
csrf := c.Get("csrf").(string)
return c.Render(http.StatusOK, "login.html", map[string]interface{}{
"CSRF": csrf,
})
})
html
<form action="/login" method="POST">
<input type="hidden" name="_csrf" value="{{.CSRF}}">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Login</button>
</form>
八、完整示例 #
go
package main
import (
"html/template"
"io"
"net/http"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
type User struct {
ID int
Name string
Email string
}
type PageData struct {
Title string
User *User
Users []User
Message string
Errors map[string]string
CSRF string
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CSRF())
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
"safe": func(s string) template.HTML {
return template.HTML(s)
},
"truncate": func(s string, n int) string {
if len(s) > n {
return s[:n] + "..."
}
return s
},
"date": func(t time.Time) string {
return t.Format("2006-01-02")
},
"datetime": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
},
"now": time.Now,
}
t := &Template{
templates: template.Must(template.New("").Funcs(funcMap).ParseGlob("views/**/*.html")),
}
e.Renderer = t
e.Static("/static", "static")
e.GET("/", home)
e.GET("/about", about)
e.GET("/users", listUsers)
e.GET("/login", loginForm)
e.POST("/login", login)
e.Logger.Fatal(e.Start(":8080"))
}
func home(c echo.Context) error {
return c.Render(http.StatusOK, "pages/home.html", PageData{
Title: "首页",
Message: "Welcome to Echo!",
Users: []User{
{ID: 1, Name: "张三", Email: "zhangsan@example.com"},
{ID: 2, Name: "李四", Email: "lisi@example.com"},
},
})
}
func about(c echo.Context) error {
return c.Render(http.StatusOK, "pages/about.html", PageData{
Title: "关于我们",
Message: "About page content",
})
}
func listUsers(c echo.Context) error {
return c.Render(http.StatusOK, "pages/users.html", PageData{
Title: "用户列表",
Users: []User{
{ID: 1, Name: "张三", Email: "zhangsan@example.com"},
{ID: 2, Name: "李四", Email: "lisi@example.com"},
{ID: 3, Name: "王五", Email: "wangwu@example.com"},
},
})
}
func loginForm(c echo.Context) error {
return c.Render(http.StatusOK, "pages/login.html", PageData{
Title: "登录",
CSRF: c.Get("csrf").(string),
})
}
func login(c echo.Context) error {
username := c.FormValue("username")
password := c.FormValue("password")
errors := make(map[string]string)
if username == "" {
errors["username"] = "用户名不能为空"
}
if password == "" {
errors["password"] = "密码不能为空"
}
if len(errors) > 0 {
return c.Render(http.StatusOK, "pages/login.html", PageData{
Title: "登录",
Errors: errors,
CSRF: c.Get("csrf").(string),
})
}
return c.Redirect(http.StatusFound, "/")
}
九、总结 #
模板渲染要点:
| 要点 | 说明 |
|---|---|
| Render方法 | 渲染模板 |
| 变量输出 | {{.Var}} |
| 条件判断 | {{if}}{{else}}{{end}} |
| 循环遍历 | {{range}}{{end}} |
| 模板包含 | {{template “name” .}} |
| 模板继承 | {{block “name” .}}{{end}} |
| 自定义函数 | FuncMap注册 |
准备好学习静态文件服务了吗?让我们进入下一章!
最后更新:2026-03-28