1.先来看一个问题
购物车数据保持到哪里?
1、购物车数据保存在本地 (cookie或者 redis缓存中),下面统一保存到cookie中,保存到redis中和cookie中逻辑步骤其实都是一样的
2、购物车数据保存到服务器(mysql) ( 必须登录)
3、没有登录,购物车数据 保存到本地 , 登录成功后购物车数据 保存到服务器 (用的最多)
增加购物车的实现逻辑:
1、获取增加购物车的数据 (把哪一个商品加入到购物车)
2、判断购物车有没有数据 (cookie或者redis缓存中是否存在当前要购买的商品)
3、如果购物车没有任何数据,直接把当前数据写入cookie
4、如果购物车有数据
(1).判断购物车有没有当前数据
有当前数据让当前数据的数量加1,然后写入到cookie
(2).如果没有当前数据直接写入cookie
2.加入购物车界面展示
以下操作都是在用户没有登录的情况下,加入购物车的操作
商品详情页面
加入购物车成功页面
购物车页面
3.代码展示
加入购物车操作说明:
1.进入商品详情页面,选择商品对应属性,点击'加入购物车'
2.加入购物车成功后,跳转到购物车成功提示页面,展示对应的商品信息
3.点击'购物车',进入到购物车页面,渲染购物车中的相关商品信息
4.在购物车页面进行相关操作
(1).全选/反选
(2).单个商品改变选中状态
(3).增加/减少商品个数
(4).删除购物车中商品
Cookie结构体
定义结构体,增加 写入数据的方法, 获取数据的方法
package models
//cookie结构体
import (
"encoding/json"
"github.com/gin-gonic/gin"
)
//定义结构体 缓存结构体 私有
type ginCookie struct{}
//写入数据的方法
func (cookie ginCookie) Set(c *gin.Context, key string, value interface{}) {
bytes, _ := json.Marshal(value)
c.SetCookie(key, string(bytes), 3600, "/", "127.0.0.1", false, true)
}
//获取数据的方法
func (cookie ginCookie) Get(c *gin.Context, key string, obj interface{}) bool {
valueStr, err1 := c.Cookie(key)
if err1 == nil && valueStr != "" && valueStr != "[]" {
err2 := json.Unmarshal([]byte(valueStr), obj)
return err2 == nil
}
return false
}
//实例化结构体
var Cookie = &ginCookie{}
购物车模型代码
在models/cart.go中增加购物车结构体,里面是 购物车相关结构体属性,以及 判断购物车里面有没有当前数据的方法
package models
//购物车结构体
type Cart struct {
Id int //商品id
Title string //商品标题
Price float64 //价格
GoodsVersion string // 版本
Uid int //用户id
Num int // 数量
GoodsGift string // 赠品
GoodsFitting string // 商品配件
GoodsColor string // 颜色
GoodsImg string //图片
GoodsAttr string // 其他属性
Checked bool //商品是否选中
}
//判断购物车里面有没有当前数据
func HasCartData(cartList []Cart, currentData Cart) bool {
for i := 0; i < len(cartList); i++ {
if cartList[i].Id == currentData.Id && cartList[i].GoodsColor == currentData.GoodsColor && cartList[i].GoodsAttr == currentData.GoodsAttr {
return true
}
}
return false
}
控制器代码
在controllers/frontend/cart.go中增加购物车相关方法:
1. 获取购物车相关列表数据
2. 添加购物车
3. 购物车增加成功跳转页面
4. 增加购物车中商品数量
5. 减少购物车中商品数量
6. 全选/反选
7. 改变一个商品数据的选中状态
8. 删除购物车
package frontend
import (
"fmt"
"github.com/gin-gonic/gin"
"goshop/models"
"net/http"
)
type CartController struct {
BaseController
}
//获取购物车数据
func (con CartController) Get(c *gin.Context) {
//获取购物车数据 显示购物车数据
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
//定义一个总价
var allPrice float64
for i := 0; i < len(cartList); i++ { //计算总价: 价格*数量
if cartList[i].Checked {
allPrice += cartList[i].Price * float64(cartList[i].Num)
}
}
var tpl = "frontend/cart/cart.html"
con.Render(c, tpl, gin.H{
"cartList": cartList,
"allPrice": allPrice,
})
}
//添加购物车
func (con CartController) AddCart(c *gin.Context) {
/*
购物车数据保持到哪里?
1、购物车数据保存在本地 (cookie)
2、购物车数据保存到服务器(mysql) (必须登录)
3、没有登录 购物车数据保存到本地 , 登录成功后购物车数据保存到服务器 (用的最多)
增加购物车的实现逻辑:
1、获取增加购物车的数据 (把哪一个商品加入到购物车)
2、判断购物车有没有数据 (cookie)
3、如果购物车没有任何数据 直接把当前数据写入cookie
4、如果购物车有数据
1、判断购物车有没有当前数据
有当前数据让当前数据的数量加1,然后写入到cookie
2、如果没有当前数据直接写入cookie
*/
// 1、获取增加购物车的数据,放在结构体里面 (把一个商品加入到购物车)
colorId, _ := models.Int(c.Query("color_id"))
goodsId, err := models.Int(c.Query("goods_id"))
if err != nil {
c.Redirect(http.StatusFound, "/")
}
//实例化商品结构体
goods := models.Goods{}
//实例化商品颜色结构体
goodsColor := models.GoodsColor{}
//获取商品信息
models.DB.Where("id = ?", goodsId).Find(&goods)
//获取商品颜色信息
models.DB.Where("id = ?", colorId).Find(&goodsColor)
currentData := models.Cart{
Id: goodsId,
Title: goods.Title,
Price: goods.Price,
GoodsVersion: goods.GoodsVersion,
Num: 1,
GoodsColor: goodsColor.ColorName,
GoodsImg: goods.GoodsImg,
GoodsGift: goods.GoodsGift, /*赠品*/
GoodsAttr: "", //根据自己的需求拓展
Checked: true, /*默认选中*/
}
// 2、判断购物车有没有数据(cookie)
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
fmt.Println(cartList)
if len(cartList) > 0 { // 说明有数据
//4、购物车有数据:判断购物车有没有当前数据
if models.HasCartData(cartList, currentData) { //有当前商品数据,数量加1
for i := 0; i < len(cartList); i++ {
if cartList[i].Id == currentData.Id && cartList[i].GoodsColor == currentData.GoodsColor && cartList[i].GoodsAttr == currentData.GoodsAttr {
cartList[i].Num = cartList[i].Num + 1
}
}
} else { // 购物车中没有当前商品, 则加入购物车
cartList = append(cartList, currentData)
}
//更新购物车
models.Cookie.Set(c, "cartList", cartList)
} else {
// 3、如果购物车没有任何数据 直接把当前数据写入cookie
cartList = append(cartList, currentData)
fmt.Println(cartList)
models.Cookie.Set(c, "cartList", cartList)
}
//添加购物车成功后,跳转到添加成功页面
c.Redirect(http.StatusFound, "/cart/successTip?goods_id=" + models.String(goodsId))
}
//购物车增加成功跳转页面
func (con CartController) AddCartSuccess(c *gin.Context) {
goodsId, err := models.Int(c.Query("goods_id"))
if err != nil {
c.Redirect(http.StatusFound, "/")
}
//实例化商品
goods := models.Goods{}
models.DB.Where("id = ?", goodsId).Find(&goods)
//跳转到添加购物车成功的模板页面
var tpl = "frontend/cart/addcart_success.html"
con.Render(c, tpl, gin.H{
"goods": goods,
})
}
//增加购物车数量
func (con CartController) IncCart(c *gin.Context) {
//1、获取客户端穿过来的数据
goodsId, err := models.Int(c.Query("goods_id"))
goodsColor := c.Query("goods_color")
GoodsAttr := ""
//定义返回的数据
//总的选中商品价格
var allPrice float64
//当前商品总价
var currentPrice float64
var num int
var response gin.H
//2、判断数据是否合法
if err != nil {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
} else {
//获取购物车数据
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
if len(cartList) > 0 {
for i := 0; i < len(cartList); i++ { //循环购物车,找到要增加的商品,给其数量+1
if cartList[i].Id == goodsId && cartList[i].GoodsColor == goodsColor && cartList[i].GoodsAttr == GoodsAttr {
cartList[i].Num = cartList[i].Num + 1
currentPrice = float64(cartList[i].Num) * cartList[i].Price
num = cartList[i].Num
}
//判断该商品是否选中,并计算总价格
if cartList[i].Checked {
allPrice += cartList[i].Price * float64(cartList[i].Num)
}
}
//重新写入数据
models.Cookie.Set(c, "cartList", cartList)
response = gin.H{
"success": true,
"message": "更新数据成功",
"allPrice": allPrice,
"num": num,
"currentPrice": currentPrice,
}
} else {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
}
}
c.JSON(http.StatusOK, response)
}
//减少购物车数量
func (con CartController) DecCart(c *gin.Context) {
//1、获取客户端穿过来的数据
goodsId, err := models.Int(c.Query("goods_id"))
goodsColor := c.Query("goods_color")
GoodsAttr := ""
//定义返回的数据
//总价
var allPrice float64
//当前商品的价格小计
var currentPrice float64
var num int
var response gin.H
//2、判断数据是否合法
if err != nil {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
} else {
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
if len(cartList) > 0 {
for i := 0; i < len(cartList); i++ {
if cartList[i].Id == goodsId && cartList[i].GoodsColor == goodsColor && cartList[i].GoodsAttr == GoodsAttr {
if cartList[i].Num > 1 {
cartList[i].Num = cartList[i].Num - 1
}
currentPrice = float64(cartList[i].Num) * cartList[i].Price
num = cartList[i].Num
}
if cartList[i].Checked {
allPrice += cartList[i].Price * float64(cartList[i].Num)
}
}
//重新写入数据
models.Cookie.Set(c, "cartList", cartList)
response = gin.H{
"success": true,
"message": "更新数据成功",
"allPrice": allPrice,
"num": num,
"currentPrice": currentPrice,
}
} else {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
}
}
c.JSON(http.StatusOK, response)
}
//改变一个商品数据的选中状态
func (con CartController) ChangeOneCart(c *gin.Context) {
//1、获取客户端传过来的数据
goodsId, err := models.Int(c.Query("goods_id"))
goodsColor := c.Query("goods_color")
GoodsAttr := ""
//定义返回的数据
var allPrice float64
var response gin.H
//2、判断数据是否合法
if err != nil {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
} else {
//获取购物车中的商品数据
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
if len(cartList) > 0 {
for i := 0; i < len(cartList); i++ { // 修改其选中状态
if cartList[i].Id == goodsId && cartList[i].GoodsColor == goodsColor && cartList[i].GoodsAttr == GoodsAttr {
cartList[i].Checked = !cartList[i].Checked
}
if cartList[i].Checked { // 计算购物车商品总价格
allPrice += cartList[i].Price * float64(cartList[i].Num)
}
}
//重新写入数据
models.Cookie.Set(c, "cartList", cartList)
response = gin.H{
"success": true,
"message": "更新数据成功",
"allPrice": allPrice,
}
} else {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
}
}
c.JSON(http.StatusOK, response)
}
//全选反选
func (con CartController) ChangeAllCart(c *gin.Context) {
//获取flag标签: 1 全选, 0 反选
flag, _ := models.Int(c.Query("flag"))
//定义返回的数据
var allPrice float64
var response gin.H
//获取购物车数据
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
if len(cartList) > 0 { // 判断购物车是否为空
for i := 0; i < len(cartList); i++ {
if flag == 1 { // 全选
cartList[i].Checked = true
} else { // 反选
cartList[i].Checked = false
}
if cartList[i].Checked { //计算总价格
allPrice += cartList[i].Price * float64(cartList[i].Num)
}
}
//重新写入数据
models.Cookie.Set(c, "cartList", cartList)
response = gin.H{
"success": true,
"message": "更新数据成功",
"allPrice": allPrice,
}
} else {
response = gin.H{
"success": false,
"message": "传入参数错误",
}
}
c.JSON(http.StatusOK, response)
}
//删除购物车数据
func (con CartController) DelCart(c *gin.Context) {
//获取请求的商品id以及颜色
goodsId, _ := models.Int(c.Query("goods_id"))
goodsColor := c.Query("goods_color")
GoodsAttr := ""
//获取购物车数据
cartList := []models.Cart{}
models.Cookie.Get(c, "cartList", &cartList)
for i := 0; i < len(cartList); i++ {
//找到要删除的商品
if cartList[i].Id == goodsId && cartList[i].GoodsColor == goodsColor && cartList[i].GoodsAttr == GoodsAttr {
//cartList[:i]: 目标商品之前的商品数据, cartList[(i+1):]:目标商品之后的商品数据
//重构购物车数据
cartList = append(cartList[:i], cartList[(i+1):]...)
}
}
//保存新的购物车数据到cookie
models.Cookie.Set(c, "cartList", cartList)
//重定向跳转
c.Redirect(http.StatusFound, "/cart")
}
增加购物车相关路由
//获取购物车数据
defaultRouters.GET("/cart", frontend.CartController{}.Get)
//增加购物车数据
defaultRouters.GET("/cart/addCart", frontend.CartController{}.AddCart)
//购物车增加成功跳转页面
defaultRouters.GET("/cart/successTip", frontend.CartController{}.AddCartSuccess)
//减少购物车中商品数量
defaultRouters.GET("/cart/decCart", frontend.CartController{}.DecCart)
//增加购物车中商品数量
defaultRouters.GET("/cart/incCart", frontend.CartController{}.IncCart)
//改变一个商品数据的选中状态
defaultRouters.GET("/cart/changeOneCart", frontend.CartController{}.ChangeOneCart)
//全选反选
defaultRouters.GET("/cart/changeAllCart", frontend.CartController{}.ChangeAllCart)
//删除购物车商品数据
defaultRouters.GET("/cart/delCart", frontend.CartController{}.DelCart)
商品详情页加入购物车html以及js
增加 "加入购物车" 点击按钮,以及加入购物车 click事件
<div class="xiadan ml20 mt10">
<input class="jrgwc" type="button" id="addCart" name="jrgwc" value="加入购物车"/>
</div>
<script>
$(function () {
//添加购物车数据
$('#addCart').click(function () {
var goods_id = $('#title').attr('goods_id'); //获取商品id
var color_id = $('#color_list .active').attr('color_id'); //获取商品颜色
location.href = "/cart/addCart?goods_id=" + goods_id + '&color_id=' + color_id;
})
})
</script>
加入购物车成功的html页面
展示商品加入购物车 成功的页面,展示对应 商品相关信息,以及 "进入购物车"按钮等
{{ define "frontend/cart/addcart_success.html" }}
{{ template "frontend/public/page_header.html" .}}
{{ template "frontend/public/middle_nav.html" .}}
<style>
.buy-succ-box {
margin-bottom: 26px;
padding: 40px 0 40px 0;
height: 68px;
border-bottom: 1px solid #e0e0e0;
}
.buy-succ-box .goods-content {
float: left;
}
.buy-succ-box .actions {
float: right;
}
.buy-succ-box .goods-img {
float: left;
width: 64px;
height: 64px;
}
.buy-succ-box .goods-info {
float: left;
margin-left: 20px;
}
.buy-succ-box .goods-info h3 {
margin: 0;
color: #424242;
font-size: 24px;
font-weight: normal;
margin-top: 3px;
}
.buy-succ-box .goods-info .name, .buy-succ-box .goods-info .price {
margin-right: 15px;
font-size: 14px;
color: #757575;
}
.buy-succ-box .actions .btn {
width: 180px;
margin-left: 12px;
margin-top: 5px;
}
.btn-line-gray {
border-color: #b0b0b0;
background: #fff;
color: #757575;
}
.btn {
display: inline-block;
*zoom: 1;
*display: inline;
width: 158px;
height: 38px;
padding: 0;
margin: 0;
border: 1px solid #b0b0b0;
font-size: 14px;
line-height: 38px;
text-align: center;
color: #b0b0b0;
cursor: pointer;
-webkit-transition: all .4s;
transition: all .4s;
}
.btn-primary {
background: #ff6700;
border-color: #ff6700;
color: #fff;
}
</style>
<div class="container w">
<div class="buy-succ-box clearfix">
<div class="goods-content" id="J_goodsBox">
<div class="goods-img"><img src="{{.goods.GoodsImg | FormatImg}}" width="64" height="64"></div>
<div class="goods-info"><h3>已成功加入购物车!</h3> <span
class="name">{{.goods.Title}} -- {{.goods.GoodsVersion}}</span></div>
</div>
<div class="actions J_actBox">
<a href="/detail?id={{.goods.Id}}" class="btn btn-line-gray J_goBack">返回上一级</a>
<a href="/cart" class="btn btn-primary">去购物车结算</a>
</div>
</div>
</div>
{{ template "frontend/public/page_footer.html" .}}
<!-- end danpin -->
</body>
</html>
{{end}}
购物车列表页面
展示购物车里面的 商品相关数据列表,改变对应商品的 选中状态,商品数量 增加/减少,以及 删除,计算 商品小计, 总的选中商品价格, 全选/反选等功能
{{ define "frontend/cart/cart.html" }}
{{ template "frontend/public/page_header.html" .}}
<link rel="stylesheet" type="text/css" href="/static/frontend/css/cart.css">
<script src="/static/frontend/js/cart.js"></script>
<!-- start banner_x -->
<div class="banner_x center">
<a href="/">
<div class="logo fl"></div>
</a>
<div class="wdgwc fl ml40">我的购物车</div>
<div class="wxts fl ml20">温馨提示:产品是否购买成功,以最终下单为准哦,请尽快结算</div>
<div class="dlzc fr">
<ul>
<li><a href="./login.html" target="_blank">登录</a></li>
<li>|</li>
<li><a href="./register.html" target="_blank">注册</a></li>
</ul>
</div>
<div class="clear"></div>
</div>
<div class="xiantiao"></div>
<div class="gwcxqbj">
<div class="gwcxd center">
<table class="table">
<tr class="th">
<th>
<input type="checkbox" id="checkAll"/>
全选
</th>
<th>
商品名称
</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
{{range $key,$value := .cartList}}
<tr class="cart_list">
<td>
<input type="checkbox" goods_id="{{$value.Id}}"
goods_color="{{$value.GoodsColor}}" {{if eq $value.Checked true}} checked {{end}} />
</td>
<td>
<div class="col_pic">
<img src="{{$value.GoodsImg | FormatImg}}"/>
</div>
<div class="col_title">
{{$value.Title}} -- {{$value.GoodsColor}} {{$value.GoodsVersion}}
</div>
</td>
<td class="price">
{{$value.Price}}元
</td>
<td>
<div class="cart_number">
<div class="input_left decCart" goods_id="{{$value.Id}}"
goods_color="{{$value.GoodsColor}}">-
</div>
<div class="input_center">
<input id="num" name="num" readonly="readonly" type="text" value="{{$value.Num}}"/>
</div>
<div class="input_right incCart" goods_id="{{$value.Id}}"
goods_color="{{$value.GoodsColor}}">+
</div>
</div>
</td>
<td class="totalPrice">
{{ Mul $value.Price $value.Num}}元
</td>
<td>
<span><a href="/cart/delCart?goods_id={{$value.Id}}&goods_color={{$value.GoodsColor}}"
class="delete"> 删除</a></span>
</td>
</tr>
{{end}}
</table>
</div>
<div class="jiesuandan mt20 center">
<div class="tishi fl ml20">
<ul>
<li><a href="./liebiao.html">继续购物</a></li>
<li>|</li>
<li>共<span>2</span>件商品,已选择<span>1</span>件</li>
<div class="clear"></div>
</ul>
</div>
<div class="jiesuan fr">
<div class="jiesuanjiage fl">合计(不含运费):<span id="allPrice">{{.allPrice}}元</span></div>
<div class="jsanniu fr"><input class="jsan" type="submit" name="jiesuan" value="去结算"/></div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
</div>
<!-- footer -->
{{ template "frontend/public/page_footer.html" .}}
</body>
</html>
{{end}}
购物车页面js
实现 删除确认, 改变购物车数据, 改变一个商品数据的选中状态, 判断全选是否选择等效果以及功能
(function ($) {
var app = {
init: function () {
this.changeCartNum();
this.deleteConfirm();
this.initCheckBox();
this.isCheckedAll();
},
deleteConfirm: function () { //删除确认
$('.delete').click(function () {
var flag = confirm('您确定要删除吗?');
return flag;
})
},
changeCartNum() { // 改变购物车数据
$('.decCart').click(function () { //减少数量
//获取商品id,商品颜色
var goods_id = $(this).attr("goods_id")
var goods_color = $(this).attr("goods_color")
var _that = this;
//请求url
$.get('/cart/decCart?goods_id=' + goods_id + '&goods_color=' + goods_color, function (response) {
if (response.success) { //改变成功
//更新总商品价格
$("#allPrice").html(response.allPrice + "元")
//更新对应商品数量
$(_that).siblings(".input_center").find("input").val(response.num)
//更新对应商品价格小计
$(_that).parent().parent().siblings(".totalPrice").html(response.currentPrice + "元")
}
})
});
$('.incCart').click(function () { // 增加数量
var goods_id = $(this).attr("goods_id")
var goods_color = $(this).attr("goods_color")
var _that = this;
$.get('/cart/incCart?goods_id=' + goods_id + '&goods_color=' + goods_color, function (response) {
console.log(response)
if (response.success) {
$("#allPrice").html(response.allPrice + "元")
$(_that).siblings(".input_center").find("input").val(response.num)
$(_that).parent().parent().siblings(".totalPrice").html(response.currentPrice + "元")
}
})
});
},
initCheckBox(){ //改变一个商品数据的选中状态
//全选按钮点击
$("#checkAll").click(function() {
if (this.checked) {
$(":checkbox").prop("checked", true);
//让cookie中商品的checked属性都等于true
$.get('/cart/changeAllCart?flag=1',function(response){
if(response.success){
$("#allPrice").html(response.allPrice+"元")
}
})
}else {
$(":checkbox").prop("checked", false);
//让cookie中商品的checked属性都等于false
$.get('/cart/changeAllCart?flag=0',function(response){
if(response.success){
$("#allPrice").html(response.allPrice+"元")
}
})
}
});
//点击单个选择框按钮的时候触发
var _that=this;
$(".cart_list :checkbox").click(function() {
_that.isCheckedAll();
var goods_id=$(this).attr("goods_id")
var goods_color=$(this).attr("goods_color")
$.get('/cart/changeOneCart?goods_id='+goods_id+'&goods_color='+goods_color,function(response){
if(response.success){
$("#allPrice").html(response.allPrice+"元")
}
})
}); //注意:this指向
},
isCheckedAll(){ //判断全选是否选择
var allNum = $(".cart_list :checkbox").size();//checkbox总个数
var checkedNum = 0;
$(".cart_list :checkbox").each(function () {
if($(this).prop("checked")==true){
checkedNum++;
}
});
if(allNum==checkedNum){//全选
$("#checkAll").prop("checked",true);
}else{//不全选
$("#checkAll").prop("checked",false);
}
},
}
$(function () {
app.init();
})
})($)
[上一节][golang gin框架] 26.Gin 商城项目-前台自定义商品列表模板, 商品详情数据渲染,Markdown语法使用