尚品汇-秒杀列表、详情、倒计时、获取下单码(五十二)

目录:

(1)秒杀列表与详情

(2)在service-activity-client模块添加接口

(3)秒杀详情页面功能介绍

(1)秒杀列表与详情

封装秒杀列表与详情接口、

封装接口

package com.atguigu.gmall.activity.service;


public interface SeckillGoodsService {

   /**
    * 返回全部列表
    * @return
    */
   List<SeckillGoods> findAll();
   
   /**
    * 根据ID获取实体
    * @param id
    * @return
    */
   SeckillGoods getSeckillGoods(Long id);
}

完成实现类

package com.atguigu.gmall.activity.service.impl;

@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 查询全部
     */
    @Override
    public List<SeckillGoods> findAll() {
        List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();
        return seckillGoodsList;
    }

    /**
     * 根据ID获取实体
     * @param id
     * @return
     */
    @Override
    public SeckillGoods getSeckillGoods(Long id) {
        return (SeckillGoods) redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(id.toString());
    }
}

完成控制器

package com.atguigu.gmall.activity.controller;

@RestController
@RequestMapping("/api/activity/seckill")
public class SeckillGoodsApiController {

    @Autowired
    private SeckillGoodsService seckillGoodsService;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private ProductFeignClient productFeignClient;


    /**
     * 返回全部列表
     *
     * @return
     */
    @GetMapping("/findAll")
    public Result findAll() {
        return Result.ok(seckillGoodsService.findAll());
    }

    /**
     * 获取实体  商品详情
     *
     * @param skuId
     * @return
     */
    @GetMapping("/getSeckillGoods/{skuId}")
    public Result getSeckillGoods(@PathVariable("skuId") Long skuId) {
        return Result.ok(seckillGoodsService.getSeckillGoods(skuId));
    }
}

(2)在service-activity-client模块添加接口

package com.atguigu.gmall.activity.client;


@FeignClient(value = "service-activity", fallback = ActivityDegradeFeignClient.class)
public interface ActivityFeignClient {

    /**
     * 返回全部列表
     *
     * @return
     */
    @GetMapping("/api/activity/seckill/findAll")
    Result findAll();

    /**
     * 获取实体  商品详情
     *
     * @param skuId
     * @return
     */
    @GetMapping("/api/activity/seckill/getSeckillGoods/{skuId}")
    Result getSeckillGoods(@PathVariable("skuId") Long skuId);
}
package com.atguigu.gmall.cart.client.impl;

@Component
public class ActivityDegradeFeignClient implements ActivityFeignClient {


    @Override
    public Result findAll() {
        return Result.fail();
    }

    @Override
    public Result getSeckillGoods(Long skuId) {
        return Result.fail();
    }
}

页面渲染

在web-all中引入远程依赖 

 

在web-all 中编写控制器

package com.atguigu.gmall.all.controller;

@Controller
public class SeckillController {

    @Autowired
    private ActivityFeignClient activityFeignClient;

    /**
     * 秒杀列表
     * @param model
     * @return
     */
    @GetMapping("seckill.html")
    public String index(Model model) {
        Result result = activityFeignClient.findAll();
        model.addAttribute("list", result.getData());
        return "seckill/index";
    }
}

列表

页面资源: \templates\seckill\index.html

<div class="goods-list" id="item">
   <ul class="seckill" id="seckill">
      <li class="seckill-item" th:each="item: ${list}">
         <div class="pic" th:@click="|detail(${item.skuId})|">
            <img th:src="${item.skuDefaultImg}" alt=''>
         </div>
         <div class="intro">
            <span th:text="${item.skuName}">手机</span>
         </div>
         <div class='price'>
            <b class='sec-price' th:text="'¥'+${item.costPrice}">¥0</b>
            <b class='ever-price' th:text="'¥'+${item.price}">¥0</b>
         </div>
         <div class='num'>
            <div th:text="'已售'+${item.num}">已售1</div>
            <div class='progress'>
               <div class='sui-progress progress-danger'>
                  <span style='width: 70%;' class='bar'></span>
               </div>
            </div>
            <div>剩余
               <b class='owned' th:text="${item.stockCount}">0</b>件</div>
         </div>
         <a class='sui-btn btn-block btn-buy' th:href="'/seckill/'+${item.skuId}+'.html'" target='_blank'>立即抢购</a>
      </li>
   </ul>
</div>

点击秒杀 

 

(3)秒杀详情页面功能介绍

说明:

  1. 立即购买,该按钮我们要加以控制,该按钮就是一个链接,页面只是控制能不能点击,一般用户可以绕过去,直接点击秒杀下单,所以我们要加以控制,在秒杀没有开始前,不能进入秒杀页面

web-all添加商品详情控制器

SeckillController

@GetMapping("seckill/{skuId}.html")
public String getItem(@PathVariable Long skuId, Model model){
    // 通过skuId 查询skuInfo
    Result result = activityFeignClient.getSeckillGoods(skuId);
    model.addAttribute("item", result.getData());
    return "seckill/item";
}

详情页面介绍

<div class="product-info">
   <div class="fl preview-wrap">
      <!--放大镜效果-->
      <div class="zoom">
         <!--默认第一个预览-->
         <div id="preview" class="spec-preview">
            <span class="jqzoom"><img th:jqimg="${item.skuDefaultImg}" th:src="${item.skuDefaultImg}" width="400" height="400"/></span>
         </div>
      </div>

   </div>
   <div class="fr itemInfo-wrap">
      <div class="sku-name">
         <h4 th:text="${item.skuName}">三星</h4>
      </div>
      <div class="news">
         <span><img src="/img/_/clock.png"/>品优秒杀</span>
         <span class="overtime">{{timeTitle}}:{{timeString}}</span>
      </div>
      <div class="summary">
         <div class="summary-wrap">

            <div class="fl title">
               <i>秒杀价</i>
            </div>
            <div class="fl price">
               <i>¥</i>
               <em th:text="${item.costPrice}">0</em>
               <span th:text="'原价:'+${item.price}">原价:0</span>
            </div>
            <div class="fr remark">
               剩余库存:<span th:text="${item.stockCount}">0</span>
            </div>
         </div>
         <div class="summary-wrap">
            <div class="fl title">
               <i>促  销</i>
            </div>
            <div class="fl fix-width">
               <i class="red-bg">加价购</i>
               <em class="t-gray">满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换购热销商品</em>
            </div>
         </div>
      </div>
      <div class="support">
         <div class="summary-wrap">
            <div class="fl title">
               <i>支  持</i>
            </div>
            <div class="fl fix-width">
               <em class="t-gray">以旧换新,闲置手机回收 4G套餐超值抢 礼品购</em>
            </div>
         </div>
         <div class="summary-wrap">
            <div class="fl title">
               <i>配 送 至</i>
            </div>
            <div class="fl fix-width">
               <em class="t-gray">满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换购热销商品</em>
            </div>
         </div>
      </div>
      <div class="clearfix choose">


         <div class="summary-wrap">
            <div class="fl title">

            </div>
            <div class="fl">
               <ul class="btn-choose unstyled">
                  <li>
                     <a href="javascript:" v-if="isBuy" @click="queue()" class="sui-btn  btn-danger addshopcar">立即抢购</a>
                     <a href="javascript:" v-if="!isBuy" class="sui-btn  btn-danger addshopcar" disabled="disabled">立即抢购</a>
                  </li>
               </ul>
            </div>
         </div>
      </div>
   </div>
</div>

点击立即抢购:

 

 

 倒计时处理

思路:页面初始化时,拿到商品秒杀开始时间和结束时间等信息,实现距离开始时间和活动倒计时。

活动未开始时,显示距离开始时间倒计时;

活动开始后,显示活动结束时间倒计时。

倒计时代码片段

init() {
// debugger
// 计算出剩余时间
var startTime = new Date(this.data.startTime).getTime();
var endTime = new Date(this.data.endTime).getTime();
var nowTime = new Date().getTime();

var secondes = 0;
// 还未开始抢购
if(startTime > nowTime) {
   this.timeTitle = '距离开始'
   secondes = Math.floor((startTime - nowTime) / 1000);
}
if(nowTime > startTime && nowTime < endTime) {
   this.isBuy = true
   this.timeTitle = '距离结束'
   secondes = Math.floor((endTime - nowTime) / 1000);
}
if(nowTime > endTime) {
   this.timeTitle = '抢购结束'
   secondes = 0;
}

const timer = setInterval(() => {
   secondes = secondes - 1
   this.timeString = this.convertTimeString(secondes)
}, 1000);
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once('hook:beforeDestroy', () => {
   clearInterval(timer);
})
},

时间转换方法

convertTimeString(allseconds) {
    if(allseconds <= 0) return '00:00:00'
    // 计算天数
    var days = Math.floor(allseconds / (60 * 60 * 24));
    // 小时
    var hours = Math.floor((allseconds - (days * 60 * 60 * 24)) / (60 * 60));
    // 分钟
    var minutes = Math.floor((allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60)) / 60);
    // 秒
    var seconds = allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60) - (minutes * 60);

    //拼接时间
    var timString = "";
    if (days > 0) {
        timString = days + "天:";
    }
    return timString += hours + ":" + minutes + ":" + seconds;
}

秒杀按钮控制

在进入秒杀功能前,我们加一个下单码,只有你获取到该下单码,才能够进入秒杀方法进行秒杀

 

 

 

获取下单码

SeckillGoodsApiController

DateUtil.dateCompare(seckillGoods.getStartTime(),curDate):后面的时间大于前面的时间返回true

 

@GetMapping("auth/getSeckillSkuIdStr/{skuId}")
public Result getSeckillSkuIdStr(@PathVariable("skuId") Long skuId, HttpServletRequest request) {
    String userId = AuthContextHolder.getUserId(request);
   
     SeckillGoods seckillGoods= (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
    //SeckillGoods seckillGoods = seckillGoodsService.getSeckillGoods(skuId);

    if (null != seckillGoods) {
        Date curTime = new Date();
        if (DateUtil.dateCompare(seckillGoods.getStartTime(), curTime) && DateUtil.dateCompare(curTime, seckillGoods.getEndTime())) {
            //可以动态生成,放在redis缓存
            String skuIdStr = MD5.encrypt(userId);
            return Result.ok(skuIdStr);
        }
    }
    return Result.fail().message("获取下单码失败");
}

说明:只有在商品秒杀时间范围内,才能获取下单码,这样我们就有效控制了用户非法秒杀,下单码我们可以根据业务自定义规则,目前我们定义为当前用户id MD5加密。 

前端页面

页面获取下单码,进入秒杀场景

queue() {
       seckill.getSeckillSkuIdStr(this.skuId).then(response => {
        var skuIdStr = response.data.data
        window.location.href = '/seckill/queue.html?skuId='+this.skuId+'&skuIdStr='+skuIdStr
    })
},

前端js完整代码如下

<script src="/js/api/seckill.js"></script>
<script th:inline="javascript">
   var item = new Vue({
      el: '#item',

      data: {
         skuId: [[${item.skuId}]],
         data: [[${item}]],
            timeTitle: '距离开始',
            timeString: '00:00:00',
            isBuy: false
      },

        created() {
            this.init()
        },

        methods: {
            init() {
            // debugger
            // 计算出剩余时间
            var startTime = new Date(this.data.startTime).getTime();
            var endTime = new Date(this.data.endTime).getTime();
            var nowTime = new Date().getTime();

            var secondes = 0;
            // 还未开始抢购
            if(startTime > nowTime) {
               this.timeTitle = '距离开始'
               secondes = Math.floor((startTime - nowTime) / 1000);
            }
            if(nowTime > startTime && nowTime < endTime) {
               this.isBuy = true
               this.timeTitle = '距离结束'
               secondes = Math.floor((endTime - nowTime) / 1000);
            }
            if(nowTime > endTime) {
               this.timeTitle = '抢购结束'
               secondes = 0;
            }

            const timer = setInterval(() => {
               secondes = secondes - 1
               this.timeString = this.convertTimeString(secondes)
            }, 1000);
            // 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
            this.$once('hook:beforeDestroy', () => {
               clearInterval(timer);
            })
            },

            queue() {
                            seckill.getSeckillSkuIdStr(this.skuId).then(response => {
                    var skuIdStr = response.data.data
                    window.location.href = '/seckill/queue.html?skuId='+this.skuId+'&skuIdStr='+skuIdStr
                })
            },

            convertTimeString(allseconds) {
                if(allseconds <= 0) return '00:00:00'
                // 计算天数
                var days = Math.floor(allseconds / (60 * 60 * 24));
                // 小时
                var hours = Math.floor((allseconds - (days * 60 * 60 * 24)) / (60 * 60));
                // 分钟
                var minutes = Math.floor((allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60)) / 60);
                // 秒
                var seconds = allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60) - (minutes * 60);

                //拼接时间
                var timString = "";
                if (days > 0) {
                    timString = days + "天:";
                }
                return timString += hours + ":" + minutes + ":" + seconds;
            }
        }
   })
</script>

编写排队跳转下单页面控制器

SeckillController
@GetMapping("seckill/queue.html")
public String queue(@RequestParam(name = "skuId") Long skuId,
                  @RequestParam(name = "skuIdStr") String skuIdStr,
                  HttpServletRequest request){
    request.setAttribute("skuId", skuId);
    request.setAttribute("skuIdStr", skuIdStr);
    return "seckill/queue";
}

页面

页面资源: \templates\seckill\queue.html

<div class="cart py-container" id="item">
    <div class="seckill_dev" v-if="show == 1">
        排队中...
    </div>
    <div class="seckill_dev" v-if="show == 2">
        {{message}}
    </div>
    <div class="seckill_dev" v-if="show == 3">
        抢购成功&nbsp;&nbsp;

        <a href="/seckill/trade.html" target="_blank">去下单</a>
    </div>
    <div class="seckill_dev" v-if="show == 4">
        抢购成功&nbsp;&nbsp;

        <a href="/myOrder.html" target="_blank">我的订单</a>
    </div>
</div>

Js部分

 

<script src="/js/api/seckill.js"></script>
<script th:inline="javascript">
    var item = new Vue({
        el: '#item',

        data: {
            skuId: [[${skuId}]],
            skuIdStr: [[${skuIdStr}]],
            data: {},
            show: 1,
            code: 211,
            message: '',
            isCheckOrder: false
        },

        mounted() {
            const timer = setInterval(() => {
                if(this.code != 211) {
                    clearInterval(timer);
                }
                this.checkOrder()
            }, 3000);
            // 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
            this.$once('hook:beforeDestroy', () => {
                clearInterval(timer);
            })
        },

        created() {
            this.saveOrder();
        },

        methods: {
            saveOrder() {
                seckill.seckillOrder(this.skuId, this.skuIdStr).then(response => {
                    debugger
                    console.log(JSON.stringify(response))
                    if(response.data.code == 200) {
                        this.isCheckOrder = true
                    } else {
                        this.show = 2
                        this.message = response.data.message
                    }

                })
            },

            checkOrder() {
                if(!this.isCheckOrder) return

                seckill.checkOrder(this.skuId).then(response => {
                     debugger
                    this.data = response.data.data
                    this.code = response.data.code
                    console.log(JSON.stringify(this.data))
                    //排队中
                    if(response.data.code == 211) {
                        this.show = 1
                    } else {
                        //秒杀成功
                        if(response.data.code == 215) {
                            this.show = 3
                            this.message = response.data.message
                        } else {
                            if(response.data.code == 218) {
                                this.show = 4
                                this.message = response.data.message
                            } else {
                                this.show = 2
                                this.message = response.data.message
                            }
                        }
                    }
                })
            }
        }
    })
</script>

说明:该页面直接通过controller返回页面,进入页面后显示排队中,然后通过异步执行秒杀下单,提交成功,页面通过轮询后台方法查询秒杀状态(代码下章完成)

点击抢购 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喵俺第一专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值