模板渲染 #

一、模板渲染基础 #

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>&copy; 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>&copy; 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