golang html 模板使用指南
1. 基础模板示例
1.1 简单页面模板
<!-- 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" rel="external nofollow" >
</head>
<body>
<header>
<h1>{{.title}}</h1>
<nav>
<a href="/" rel="external nofollow" >首页</a>
<a href="/about" rel="external nofollow" >关于</a>
<a href="/contact" rel="external nofollow" >联系我们</a>
</nav>
</header>
<main>
{{template "content" .}}
</main>
<footer>
<p>© {{.year}} 我的网站</p>
</footer>
</body>
</html>// main.go
package main
import (
"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 string
year int
}{
title: "我的网站",
year: time.now().year(),
}
tmpl.execute(w, data)
})
http.listenandserve(":8080", nil)
}1.2 列表渲染
<!-- 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 string
description string
price float64
image string
instock 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 string
year int
products []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 嵌套模板
<!-- 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}}" rel="external nofollow" >{{.text}}</a>
</li>
{{end}}
</ul>
</nav>
{{if .user}}
<div class="user-menu">
<span>欢迎, {{.user.name}}</span>
<a href="/logout" rel="external nofollow" >退出</a>
</div>
{{else}}
<div class="auth-buttons">
<a href="/login" rel="external nofollow" class="btn btn-login">登录</a>
<a href="/register" rel="external nofollow" class="btn btn-register">注册</a>
</div>
{{end}}
</header>
{{end}}2.2 表单处理
<!-- 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 分页组件
<!-- templates/components/pagination.html -->
{{define "pagination"}}
<div class="pagination">
{{if gt .totalpages 1}}
{{if gt .currentpage 1}}
<a href="?page=1" rel="external nofollow" class="page-link first">«</a>
<a href="?page={{subtract .currentpage 1}}" rel="external nofollow" class="page-link prev">‹</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}}" rel="external nofollow"
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}}" rel="external nofollow" class="page-link next">›</a>
<a href="?page={{.totalpages}}" rel="external nofollow" class="page-link last">»</a>
{{end}}
{{end}}
</div>
{{end}}3. 完整应用示例
3.1 博客系统模板
<!-- 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}}" rel="external nofollow"
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}}" rel="external nofollow" >{{.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={{.}}" rel="external nofollow" class="tag">{{.}}</a>
{{end}}
</div>
</div>
</article>
{{else}}
<div class="no-posts">
暂无文章
</div>
{{end}}
</div>
{{template "pagination" .pagination}}
</div>
{{end}}type blogdata struct {
query string
currentcategory string
categories []category
posts []post
pagination pagination
}
type category struct {
name string
slug string
count int
}
type post struct {
title string
slug string
content string
author string
image string
category string
tags []string
createdat time.time
}
type pagination struct {
currentpage int
totalpages int
totalitems 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);
};
}总结
模板组织
- 使用嵌套模板实现代码复用
- 保持模板结构清晰
- 合理使用模板函数
最佳实践
- 使用语义化 html
- 保持 css 模块化
- 实现响应式设计
- 添加适当的交互效果
性能优化
- 缓存编译后的模板
- 压缩静态资源
- 使用 cdn 加速
- 实现懒加载
到此这篇关于golang html 模板使用指南的文章就介绍到这了,更多相关golang html 模板内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论