一.前台自定义商品列表模板
当在首页分类点击进入分类商品列表页面时,可以根据后台分类中的分类模板跳转到对应的模板商品列表页面

1.管理后台商品分类模板设置如下图所示

2.代码展示

(1).商品控制器方法Category()完善

修改controllers/frontend/productController.go中的方法Category(), 判断分类模板,如果后台没有设置,则使用默认模板
// 根据商品分类获取分类下面的所有商品数据
func (con ProductController) Category(c *gin.Context) {//获取分类idcateId, _ := models.Int(c.Param("id"))//当前页page, _ := models.Int(c.Query("page"))if page == 0 {page = 1}//每一页显示的数量pageSize := 2//获取当前分类curCate := models.GoodsCate{}models.DB.Where("id = ? ", cateId).Find(&curCate)//判断当前分类是否顶级分类,如果是,则获取对应的二级分类,如果不是,则获取对应的兄弟分类subCate := []models.GoodsCate{}var tempSlice []intif curCate.Pid == 0 { // 当前分类是顶级分类,获取对应的二级分类models.DB.Where("pid = ?", cateId).Find(&subCate)//把二级分类id放到切片中for i := 0; i < len(subCate); i++ {tempSlice = append(tempSlice, subCate[i].Id)}} else { // 当前分类是二级分类,获取对应的兄弟分类models.DB.Where("pid = ?", curCate.Pid).Find(&subCate)}//把请求的分类id放入切片tempSlice = append(tempSlice, cateId)//通过上面的分类id,获取商品相关数据goodsList := []models.Goods{}where := "cate_id in ?"models.DB.Where(where, tempSlice).Where("status = ?", 1).Offset((page - 1) * pageSize).Limit(pageSize).Find(&goodsList)//获取总数量var count int64models.DB.Where(where, tempSlice).Table("goods").Count(&count)//定义请求的模板tpl := "frontend/product/list.html"//判断分类模板,如果后台没有设置,则使用默认模板if curCate.Template != "" {tpl = curCate.Template}con.Render(c, tpl, gin.H{"goodsList":   goodsList,"subCate":     subCate,"currentCate": curCate,"page":        page,"totalPages":  math.Ceil(float64(count) / float64(pageSize)),})
}

(2).模板页面案例

{{ define "frontend/product/catetest.html" }}<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>自定义模板
</body>
</html>
{{end}}
二.商品详情数据渲染,Markdown语法使用

1.后台商品相关页面

先来回顾一下后台商品相关属性功能页面

2.前端相关界面展示

3.路由代码

完善routers/frontendRouters.go前端路由
package routersimport ("goshop/controllers/frontend""github.com/gin-gonic/gin"
)//设置前端路由
func FrontendRoutersInit(r *gin.Engine) {defaultRouters := r.Group("/"){//首页defaultRouters.GET("/", frontend.IndexController{}.Index)//商品分类对应的商品列表页面defaultRouters.GET("/category:id", frontend.ProductController{}.Category)//商品详情页defaultRouters.GET("/detail", frontend.ProductController{}.Detail)//获取商品图库信息:获取当前商品对应颜色的图片信息defaultRouters.GET("/getImgList", frontend.ProductController{}.GetImgList)}
}

3.控制器,模型代码

在controllers/frontend/productController.go中增加商品详情等方法

//商品详情
func (con ProductController) Detail(c *gin.Context) {//获取商品idid, err := models.Int(c.Query("id"))//判断商品id是否符合要求if err != nil {c.Redirect(302, "/")c.Abort()}//1、获取商品信息goods := models.Goods{Id: id}models.DB.Find(&goods)//2、获取关联商品  RelationGoodsrelationGoods := []models.Goods{}//把数据库中保存的关联商品id转换成切片类型goods.RelationGoods = strings.ReplaceAll(goods.RelationGoods, ",", ",")relationIds := strings.Split(goods.RelationGoods, ",")//查询关联商品models.DB.Where("id in ?", relationIds).Select("id,title,price,goods_version").Find(&relationGoods)//3、获取关联赠品 GoodsGiftgoodsGift := []models.Goods{}goods.GoodsGift = strings.ReplaceAll(goods.GoodsGift, ",", ",")giftIds := strings.Split(goods.GoodsGift, ",")models.DB.Where("id in ?", giftIds).Select("id,title,price,goods_version").Find(&goodsGift)//4、获取关联颜色 GoodsColorgoodsColor := []models.GoodsColor{}goods.GoodsColor = strings.ReplaceAll(goods.GoodsColor, ",", ",")colorIds := strings.Split(goods.GoodsColor, ",")models.DB.Where("id in ?", colorIds).Find(&goodsColor)//5、获取关联配件 GoodsFittinggoodsFitting := []models.Goods{}goods.GoodsFitting = strings.ReplaceAll(goods.GoodsFitting, ",", ",")fittingIds := strings.Split(goods.GoodsFitting, ",")models.DB.Where("id in ?", fittingIds).Select("id,title,price,goods_version").Find(&goodsFitting)//6、获取商品关联的图片 GoodsImagegoodsImage := []models.GoodsImage{}models.DB.Where("goods_id = ?", goods.Id).Limit(6).Find(&goodsImage)//7、获取规格参数信息 GoodsAttrgoodsAttr := []models.GoodsAttr{}models.DB.Where("goods_id = ?", goods.Id).Find(&goodsAttr)//8、获取更多属性/*颜色:红色,白色,黄色 | 尺寸:41,42,43切片[{Cate:"颜色",List:[红色,白色,黄色]},{Cate:"尺寸",List:[41,42,43]}]goodsAttrStrSlice[0]    尺寸:41,42,43tempSlice[0]    尺寸tempSlice[1]    41,42,43goodsAttrStrSlice[1]    套餐:套餐1,套餐2*/// 更多属性: goodsAttrStr := "尺寸:41,42,43|套餐:套餐1,套餐2"goodsAttrStr := goods.GoodsAttr//字符串替换操作goodsAttrStr = strings.ReplaceAll(goodsAttrStr, ",", ",")goodsAttrStr = strings.ReplaceAll(goodsAttrStr, ":", ":")//实例化商品更多属性结构体var goodsItemAttrList []models.GoodsItemAttr//strings.Contains 判断字符串中有没有冒号(:)if strings.Contains(goodsAttrStr, ":") {//字符串替换操作:获取属性切片goodsAttrStrSlice := strings.Split(goodsAttrStr, "|")//创建切片的存储空间goodsItemAttrList = make([]models.GoodsItemAttr, len(goodsAttrStrSlice))for i := 0; i < len(goodsAttrStrSlice); i++ {//strings.Split(s, sep string) 把字符串s按照sep转换成切片//拆分 "尺寸:41,42,43tempSlice := strings.Split(goodsAttrStrSlice[i], ":")goodsItemAttrList[i].Cate = tempSlice[0]//拆分 41,42,43listSlice := strings.Split(tempSlice[1], ",")goodsItemAttrList[i].List = listSlice}}//定义请求的模板tpl := "frontend/product/detail.html"con.Render(c, tpl, gin.H{"goods":             goods,"relationGoods":     relationGoods,"goodsGift":         goodsGift,"goodsColor":        goodsColor,"goodsFitting":      goodsFitting,"goodsImage":        goodsImage,"goodsAttr":         goodsAttr,"goodsItemAttrList": goodsItemAttrList,})
}//获取商品对应颜色的图库信息
func (con ProductController) GetImgList(c *gin.Context) {//获取商品idgoodsId, err1 := models.Int(c.Query("goods_id"))//获取商品对应的颜色idcolorId, err2 := models.Int(c.Query("color_id"))//查询商品图库信息goodsImageList := []models.GoodsImage{}err3 := models.DB.Where("goods_id = ? AND color_id = ?", goodsId, colorId).Find(&goodsImageList).Errorif err1 != nil || err2 != nil || err3 != nil {c.JSON(http.StatusOK, gin.H{"success": false,"result":  "","message": "参数错误",})return}//判断 goodsImageList的长度 如果goodsImageList没有数据,那么我们需要返回当前商品所有的图库信息if len(goodsImageList) == 0 {models.DB.Where("goods_id = ?", goodsId).Find(&goodsImageList)}c.JSON(http.StatusOK, gin.H{"success": true,"result":  goodsImageList,"message": "获取数据成功",})
}
在models/goodsItemAttr.go中定义一个商品更多属性的结构体
package models//商品更多属性结构体type GoodsItemAttr struct {// 尺寸:41,42,43Cate string  //属性类型:  尺寸List []string  // 对应的值 [41,42,43]
}

4.main.go,tools.go

在前端渲染数据的时候,需要解析后台Markdown语法数据,这是就需要在models/tools.go中增加调用 Markdown语法的方法,这样就可以解析后台商品 规格与包装属性中的Markdown语法格式:
Markdown 是一种 轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档
当前许多网站都广泛使用 Markdown 来撰写帮助文档或是用于论坛上发表消息
更多语法参考: https://www.runoob.com/markdown/md-lists.html
在解析Markdown语法之前,需要 引入gomarkdown包,操作如下:
在tools.go中import github.com/gomarkdown/markdown,然后在main.go目录下 go mod tidy 更新就可以使用啦
models/tools.go中增加FormatAttr()方法
import ("github.com/gomarkdown/markdown"
)/*
转换markdown格式
str就是markdown语法
例如:
**我是一个三级标题**  <=> <h3>我是一个三级标题</h3>
**我是一个加粗** <=> <strong>我是一个加粗</strong>
*/
func FormatAttr(str string) string {tempSlice := strings.Split(str, "\n")var tempStr stringfor _, v := range tempSlice {md := []byte(v)output := markdown.ToHTML(md, nil, nil)tempStr += string(output)}return tempStr
}
增加main.go中的自定义模板函数FormatAttr
//自定义模板函数,必须在r.LoadHTMLGlob前面(只调用,不执行, 可以在html 中使用)
r.SetFuncMap(template.FuncMap{"UnixToTime": models.UnixToTime, //注册模板函数"Str2Html": models.Str2Html,"FormatImg": models.FormatImg,"Sub": models.Sub,"SubStr": models.SubStr,"FormatAttr": models.FormatAttr,
})

5.html代码

渲染商品详情相关数据frontend/product/detail.html
{{ define "frontend/product/detail.html" }}{{ template "frontend/public/page_header.html" .}}{{ template "frontend/public/middle_nav.html" .}}<link rel="stylesheet" href="/static/frontend/css/product.css"><div class="jieshao mt20 w"><div class="left fl"><div class="swiper-container"><div class="swiper-wrapper item_focus" id="item_focus">{{range $key,$value := .goodsImage}}<div class="swiper-slide"><img src="{{$value.ImgUrl | FormatImg}}"/></div>{{end}}</div><div class="swiper-pagination"></div><div class="swiper-button-next"></div><div class="swiper-button-prev"></div></div></div><div class="right fr"><div class="h3 ml20 mt20">{{.goods.Title}}</div><div class="jianjie mr40 ml20 mt10">{{.goods.SubTitle}}</div><div class="jiage ml20 mt10">{{.goods.Price}}元  <span class="old_price">{{.goods.MarketPrice}}元</span></div>{{$goodsId := .goods.Id}}{{$relationGoodsLen := len .relationGoods}}{{if gt $relationGoodsLen 0}}<div class="ft20 ml20 mt20">选择版本</div><div class="xzbb ml20 mt10 clearfix">{{range $key,$value := .relationGoods}}<div class="banben fl {{if eq $value.Id $goodsId}}active{{end}}"><a href="detail?id={{$value.Id}}"><span>{{$value.GoodsVersion}}</span><span>{{$value.Price}}元</span></a></div>{{end}}</div>{{end}}{{$goodsColorLen := len .goodsColor}}{{if gt $relationGoodsLen 0}}<div class="ft20 ml20 mt10">选择颜色</div><div class="xzbb ml20 mt10 clearfix" id="color_list">{{range $key,$value:=.goodsColor}}<div class="banben fl" goods_id="{{$goodsId}}" color_id="{{$value.Id}}"><span class="yuandian" style="background:{{$value.ColorValue}}"></span><span class="yanse">{{$value.ColorName}}</span></div>{{end}}</div>{{end}}{{$goodsItemAttrListLen := len .goodsItemAttrList}}{{if gt $goodsItemAttrListLen 0}}{{range $key,$value := .goodsItemAttrList}}<div class="ft20 ml20 mt20">{{$value.Cate}}</div><div class="xzbb ml20 mt10 clearfix">{{range $k,$v := $value.List}}<div class="banben fl"><span>{{$v}}</span></div>{{end}}</div>{{end}}{{end}}<div class="xqxq mt10 ml20"><div class="top1 mt10"><div class="left1 fl">{{.goods.GoodsVersion}} <span id="color_name"></span></div><div class="right1 fr">{{.goods.Price}}元</div><div class="clear"></div></div><div class="bot mt20 ft20 ftbc">总计:{{.goods.Price}}元</div></div><div class="xiadan ml20 mt10"><input class="jrgwc" type="button" name="jrgwc" value="加入购物车"/></div></div><div class="clear"></div></div><div class="container clearfix"><div class="c_left"><h2>看了又看</h2><div class="item"><a target="_blank" href="#"><img src="/static/upload/20211117/1637139107685884400.jpg"/><p class="price recommendLookPrice4183081">¥31.90</p><p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p></a></div><div class="item"><a target="_blank" href="#"><img src="/static/upload/20211117/1637139107685884400.jpg"/><p class="price recommendLookPrice4183081">¥31.90</p><p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p></a></div><div class="item"><a target="_blank" href="#"><img src="/static/upload/20211117/1637139107685884400.jpg"/><p class="price recommendLookPrice4183081">¥31.90</p><p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p></a></div></div><div class="c_right"><ul class="detail_list clearfix"><li class="">详情描述</li><li class="">规格参数</li><li class="">用户评价</li></ul><div class="detail_info"><div class="detail_info_item">{{Str2Html .goods.GoodsContent}}</div><div class="detail_info_item"><ul>{{range $key,$value:=.goodsAttr}}{{if ne $value.AttributeValue ""}}<li class="row clearfix"><div class="span5"><h2>{{$value.AttributeTitle}}</h2></div><div class="span15">{{$value.AttributeValue | FormatAttr | Str2Html}}</div></li>{{end}}{{end}}</ul></div><div class="detail_info_item"><ul class="comment_list"><li><div><img src="https://www.itying.com/themes/itying/images/stars5.gif"></div><p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p><p class="eval-order-info"> <span class="eval-time">2018-11-1814:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p></li><li><div><img src="https://www.itying.com/themes/itying/images/stars5.gif"></div><p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p><p class="eval-order-info"> <span class="eval-time">2018-11-1814:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p></li><li><div><img src="https://www.itying.com/themes/itying/images/stars5.gif"></div><p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p><p class="eval-order-info"> <span class="eval-time">2018-11-1814:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p></li><li><div><img src="https://www.itying.com/themes/itying/images/stars5.gif"></div><p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p><p class="eval-order-info"> <span class="eval-time">2018-11-1814:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p></li></ul></div></div></div></div>{{ template "frontend/public/page_footer.html" .}}</body></html>
{{end}}

6.js代码

在static/frontend/js/base.js中增加 initProductContentTab()方法-商品详情页面 tab切换(内容,规格参数等; initProductContentColor()方法- 商品详情 颜色选中功能,如果有关联的商品图片,则展示对应的商品图片信息
(function ($) {var app = {init: function () {this.initSwiper();this.initNavSlide();this.initProductContentTab();this.initProductContentColor();},initSwiper: function () {     // 轮播图切换操作new Swiper('.swiper-container', {loop: true,navigation: {nextEl: '.swiper-button-next',prevEl: '.swiper-button-prev'},pagination: {el: '.swiper-pagination',clickable: true}});},initNavSlide: function () {  // 导航鼠标移上去效果$("#nav_list>li").hover(function () {$(this).find('.children-list').show();}, function () {$(this).find('.children-list').hide();})},initProductContentTab: function () {  // 商品详情页面tab切换(内容,规格参数等tab切换)$(function () {$('.detail_info .detail_info_item:first').addClass('active');$('.detail_list li:first').addClass('active');$('.detail_list li').click(function () {var index = $(this).index();$(this).addClass('active').siblings().removeClass('active');$('.detail_info .detail_info_item').removeClass('active').eq(index).addClass('active');})})},initProductContentColor: function () {  // 商品详情颜色选中功能,如果有关联的商品图片,则展示对应的商品图片信息var _that = this;$("#color_list .banben").first().addClass("active");$("#color_name").html($("#color_list .active .yanse").html())$("#color_list .banben").click(function () {$(this).addClass("active").siblings().removeClass("active");$("#color_name").html($("#color_list .active .yanse").html())var goods_id = $(this).attr("goods_id")var color_id = $(this).attr("color_id")//获取当前商品对应颜色的图片信息$.get("/product/getImgList", {"goods_id": goods_id, "color_id": color_id}, function (response) {if (response.success == true) {var swiperStr = ""for (var i = 0; i < response.result.length; i++) {swiperStr += '<div class="swiper-slide"><img src="' + response.result[i].img_url + '"> </div>';}$("#item_focus").html(swiperStr)_that.initSwiper()}})})}}$(function () {app.init();})})($)

[上一节][golang gin框架] 25.Gin 商城项目-配置清除缓存以及前台列表页面数据渲染公共数据