Golang HTML 模板使用指南

news/2025/1/11 18:42:32/

Golang HTML 模板使用指南

1. 基础模板示例

1.1 简单页面模板

html"><!-- templates/layout.html -->
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/static/css/style.css">
</head>
<body><header><h1>{{.Title}}</h1><nav><a href="/">首页</a><a href="/about">关于</a><a href="/contact">联系我们</a></nav></header><main>{{template "content" .}}</main><footer><p>&copy; {{.Year}} 我的网站</p></footer>
</body>
</html>
// main.go
package mainimport ("html/template""net/http""time"
)func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {tmpl := template.Must(template.ParseFiles("templates/layout.html"))data := struct {Title stringYear  int}{Title: "我的网站",Year:  time.Now().Year(),}tmpl.Execute(w, data)})http.ListenAndServe(":8080", nil)
}

1.2 列表渲染

html"><!-- templates/products.html -->
{{define "content"}}
<div class="products"><h2>产品列表</h2><div class="product-grid">{{range .Products}}<div class="product-card"><img src="{{.Image}}" alt="{{.Name}}"><h3>{{.Name}}</h3><p>{{.Description}}</p><div class="price">{{formatPrice .Price}}</div>{{if .InStock}}<button class="buy-btn">购买</button>{{else}}<button class="out-of-stock" disabled>缺货</button>{{end}}</div>{{end}}</div>
</div>
{{end}}
type Product struct {Name        stringDescription stringPrice       float64Image       stringInStock     bool
}func productsHandler(w http.ResponseWriter, r *http.Request) {funcMap := template.FuncMap{"formatPrice": func(price float64) string {return fmt.Sprintf("¥%.2f", price)},}tmpl := template.New("layout.html").Funcs(funcMap)tmpl = template.Must(tmpl.ParseFiles("templates/layout.html","templates/products.html",))data := struct {Title    stringYear     intProducts []Product}{Title: "产品列表",Year:  time.Now().Year(),Products: []Product{{Name:        "商品1",Description: "这是商品1的描述",Price:       99.99,Image:      "/static/images/product1.jpg",InStock:    true,},// 更多商品...},}tmpl.Execute(w, data)
}

2. 高级模板示例

2.1 嵌套模板

html"><!-- templates/components/header.html -->
{{define "header"}}
<header class="site-header"><div class="logo"><img src="/static/images/logo.png" alt="Logo"></div><nav class="main-nav"><ul>{{range .NavItems}}<li class="{{if eq $.CurrentPage .Link}}active{{end}}"><a href="{{.Link}}">{{.Text}}</a></li>{{end}}</ul></nav>{{if .User}}<div class="user-menu"><span>欢迎, {{.User.Name}}</span><a href="/logout">退出</a></div>{{else}}<div class="auth-buttons"><a href="/login" class="btn btn-login">登录</a><a href="/register" class="btn btn-register">注册</a></div>{{end}}
</header>
{{end}}

2.2 表单处理

html"><!-- templates/form.html -->
{{define "content"}}
<div class="form-container"><h2>{{.FormTitle}}</h2>{{with .FormError}}<div class="error-message">{{.}}</div>{{end}}<form method="POST" action="{{.FormAction}}" enctype="multipart/form-data">{{range .Fields}}<div class="form-group"><label for="{{.ID}}">{{.Label}}{{if .Required}}*{{end}}</label>{{if eq .Type "text"}}<input type="text" id="{{.ID}}" name="{{.Name}}" value="{{.Value}}" {{if .Required}}required{{end}}{{with .Pattern}}pattern="{{.}}"{{end}}>{{else if eq .Type "textarea"}}<textarea id="{{.ID}}" name="{{.Name}}" {{if .Required}}required{{end}}>{{.Value}}</textarea>{{else if eq .Type "select"}}<select id="{{.ID}}" name="{{.Name}}" {{if .Required}}required{{end}}>{{range .Options}}<option value="{{.Value}}" {{if eq .Value $.Selected}}selected{{end}}>{{.Text}}</option>{{end}}</select>{{end}}{{with .Error}}<span class="field-error">{{.}}</span>{{end}}</div>{{end}}<div class="form-actions"><button type="submit" class="btn btn-primary">{{.SubmitText}}</button><button type="reset" class="btn btn-secondary">重置</button></div></form>
</div>
{{end}}

2.3 分页组件

html"><!-- templates/components/pagination.html -->
{{define "pagination"}}
<div class="pagination">{{if gt .TotalPages 1}}{{if gt .CurrentPage 1}}<a href="?page=1" class="page-link first">&laquo;</a><a href="?page={{subtract .CurrentPage 1}}" class="page-link prev">&lsaquo;</a>{{end}}{{range $i := range (sequence .TotalPages)}}{{if and (ge $i (subtract $.CurrentPage 2)) (le $i (add $.CurrentPage 2))}}<a href="?page={{add $i 1}}" class="page-link {{if eq $i (subtract $.CurrentPage 1)}}active{{end}}">{{add $i 1}}</a>{{end}}{{end}}{{if lt .CurrentPage .TotalPages}}<a href="?page={{add .CurrentPage 1}}" class="page-link next">&rsaquo;</a><a href="?page={{.TotalPages}}" class="page-link last">&raquo;</a>{{end}}{{end}}
</div>
{{end}}

3. 完整应用示例

3.1 博客系统模板

html"><!-- templates/blog/list.html -->
{{define "content"}}
<div class="blog-list"><div class="filters"><div class="search"><input type="text" placeholder="搜索文章..." value="{{.Query}}" id="searchInput"></div><div class="categories">{{range .Categories}}<a href="/blog?category={{.Slug}}" class="category {{if eq $.CurrentCategory .Slug}}active{{end}}">{{.Name}} ({{.Count}})</a>{{end}}</div></div><div class="articles">{{range .Posts}}<article class="post-card">{{if .Image}}<div class="post-image"><img src="{{.Image}}" alt="{{.Title}}"></div>{{end}}<div class="post-content"><h2><a href="/blog/{{.Slug}}">{{.Title}}</a></h2><div class="post-meta"><span class="author">{{.Author}}</span><span class="date">{{formatDate .CreatedAt "2006-01-02"}}</span><span class="category">{{.Category}}</span></div><p class="excerpt">{{truncate .Content 200}}</p><div class="tags">{{range .Tags}}<a href="/blog?tag={{.}}" class="tag">{{.}}</a>{{end}}</div></div></article>{{else}}<div class="no-posts">暂无文章</div>{{end}}</div>{{template "pagination" .Pagination}}
</div>
{{end}}
type BlogData struct {Query           stringCurrentCategory stringCategories      []CategoryPosts           []PostPagination      Pagination
}type Category struct {Name  stringSlug  stringCount int
}type Post struct {Title     stringSlug      stringContent   stringAuthor    stringImage     stringCategory  stringTags      []stringCreatedAt time.Time
}type Pagination struct {CurrentPage intTotalPages  intTotalItems  int
}func blogHandler(w http.ResponseWriter, r *http.Request) {funcMap := template.FuncMap{"formatDate": func(t time.Time, layout string) string {return t.Format(layout)},"truncate": func(s string, l int) string {if len(s) <= l {return s}return s[:l] + "..."},}tmpl := template.New("layout.html").Funcs(funcMap)tmpl = template.Must(tmpl.ParseFiles("templates/layout.html","templates/blog/list.html","templates/components/pagination.html",))data := BlogData{Query:           r.URL.Query().Get("q"),CurrentCategory: r.URL.Query().Get("category"),Categories: []Category{{Name: "技术", Slug: "tech", Count: 10},{Name: "生活", Slug: "life", Count: 5},},Posts: []Post{{Title:     "示例文章",Slug:      "example-post",Content:   "这是一篇示例文章的内容...",Author:    "作者名",Category: "tech",Tags:     []string{"Go", "Web"},CreatedAt: time.Now(),},},Pagination: Pagination{CurrentPage: 1,TotalPages:  5,TotalItems:  45,},}tmpl.Execute(w, data)
}

4. CSS 样式示例

/* static/css/style.css */
/* 基础样式 */
body {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;line-height: 1.6;margin: 0;padding: 0;color: #333;
}/* 头部样式 */
.site-header {background: #fff;box-shadow: 0 2px 4px rgba(0,0,0,0.1);padding: 1rem;
}.main-nav ul {list-style: none;display: flex;gap: 1rem;
}/* 产品卡片样式 */
.product-grid {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));gap: 2rem;padding: 2rem;
}.product-card {border: 1px solid #eee;border-radius: 8px;overflow: hidden;transition: transform 0.2s;
}.product-card:hover {transform: translateY(-5px);box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}/* 表单样式 */
.form-container {max-width: 600px;margin: 2rem auto;padding: 2rem;background: #fff;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}.form-group {margin-bottom: 1.5rem;
}/* 博客列表样式 */
.blog-list {max-width: 1200px;margin: 0 auto;padding: 2rem;
}.post-card {display: grid;grid-template-columns: 200px 1fr;gap: 1.5rem;margin-bottom: 2rem;padding: 1rem;background: #fff;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}/* 分页样式 */
.pagination {display: flex;justify-content: center;gap: 0.5rem;margin: 2rem 0;
}.page-link {padding: 0.5rem 1rem;border: 1px solid #ddd;border-radius: 4px;color: #333;text-decoration: none;
}.page-link.active {background: #007bff;color: #fff;border-color: #007bff;
}

5. JavaScript 交互示例

// static/js/main.js
document.addEventListener('DOMContentLoaded', function() {// 搜索功能const searchInput = document.getElementById('searchInput');if (searchInput) {searchInput.addEventListener('input', debounce(function(e) {const query = e.target.value;window.location.href = `/blog?q=${encodeURIComponent(query)}`;}, 500));}// 表单验证const forms = document.querySelectorAll('form');forms.forEach(form => {form.addEventListener('submit', function(e) {const requiredFields = form.querySelectorAll('[required]');let valid = true;requiredFields.forEach(field => {if (!field.value.trim()) {valid = false;field.classList.add('error');} else {field.classList.remove('error');}});if (!valid) {e.preventDefault();alert('请填写所有必填字段');}});});
});// 工具函数
function debounce(func, wait) {let timeout;return function executedFunction(...args) {const later = () => {clearTimeout(timeout);func(...args);};clearTimeout(timeout);timeout = setTimeout(later, wait);};
}

总结

  1. 模板组织

    • 使用嵌套模板实现代码复用
    • 保持模板结构清晰
    • 合理使用模板函数
  2. 最佳实践

    • 使用语义化 HTML
    • 保持 CSS 模块化
    • 实现响应式设计
    • 添加适当的交互效果
  3. 性能优化

    • 缓存编译后的模板
    • 压缩静态资源
    • 使用 CDN 加速
    • 实现懒加载

http://www.ppmy.cn/news/1562304.html

相关文章

【Pandas】pandas Series rsub

Pandas2.2 Series Binary operator functions 方法描述Series.add()用于对两个 Series 进行逐元素加法运算Series.sub()用于对两个 Series 进行逐元素减法运算Series.mul()用于对两个 Series 进行逐元素乘法运算Series.div()用于对两个 Series 进行逐元素除法运算Series.true…

MATLAB语言的语法糖

MATLAB语言的语法糖 引言 在编程语言的发展历程中&#xff0c;语法糖&#xff08;Syntactic Sugar&#xff09;被广泛提及。它指的是一种编程语言的语法特性&#xff0c;旨在使代码更易读、更易写&#xff0c;虽然这些特性并不增加语言的表达能力&#xff0c;但能使程序员的生…

Springboot Rabbitmq + 线程池技术控制指定数量task执行

定义DataSyncTaskManager&#xff0c;作为线程池任务控制器 package org.demo.scheduletest.service;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueu…

《探秘鸿蒙NEXT中的人工智能核心架构》

在当今科技飞速发展的时代&#xff0c;华为HarmonyOS NEXT的发布无疑是操作系统领域的一颗重磅炸弹&#xff0c;其将人工智能与操作系统深度融合&#xff0c;开启了智能新时代。那么&#xff0c;鸿蒙NEXT中人工智能的核心架构究竟是怎样的呢&#xff1f;让我们一同探秘。 基础…

XS5037C一款应用于专业安防摄像机的图像信号处理芯片,支持MIPI和 DVP 接口,内置高性能ISP处理器,支持3D降噪和数字宽动态

XS5037C是一款应用于专业安防摄像机的图像信号处理芯片&#xff0c;支持MIPI和 DVP 接口&#xff0c;最 大支持 5M sensor接入。内置高性能ISP处理器&#xff0c;支持3D降噪和数字宽动态。标清模拟输出支 持960H&#xff0c;高清模拟输出支持HDCCTV 720P/1080P/4M/5M。高度集成…

Filebeat es

es kibana 内网地址 127.0.0.1:9200 https://vpcep-7c16b185-4d03-475c-bf9b-c38cde8d02c0.test.huaweicloud.com:9200 账户 admin 密码 admin #端口 9200 eskibana https://127.0.0.1:5601/app/login?nextUrl%2F 账户 admin 密码 admin docker 构建容器启动 docker syste…

Java聊天小程序

拟设计一个基于 Java 技术的局域网在线聊天系统,实现客户端与服务器之间的实时通信。系统分为客户端和服务器端两类,客户端用于发送和接收消息,服务器端负责接收客户端请求并处理消息。客户端通过图形界面提供用户友好的操作界面,服务器端监听多个客户端的连接并管理消息通…

【蓝桥杯比赛-C++组-经典题目汇总】

1. 最短路 题目描述&#xff1a; 如下图所示&#xff0c;G是一个无向图&#xff0c;其中蓝色边的长度是1、橘色边的长度是2、绿色边的长度是3。 则从 A 到 S 的最短距离是多少&#xff1f; #include <iostream> #include <cstring> using namespace std; const i…