You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
shop-pc/pages/seckill/index.vue

437 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
* @Author: ch
* @Date: 2022-05-04 17:25:41
* @LastEditors: ch
* @LastEditTime: 2022-05-04 17:26:08
* @Description: file content
-->
<template>
<div class="seckill">
<div v-show="isSticky" class="seckill-header-sticky">
<TabBar
:tab-id="query.activityTimeId"
:list="tabList"
@tab-change="handleTabChange"
/>
</div>
<!-- 秒杀时间段 -->
<div
class="seckill-header"
:style="{ backgroundImage: `url(${bkgSckill})` }"
>
<div class="seckill-header-tabbar">
<TabBar
:tab-id="query.activityTimeId"
:list="tabList"
@tab-change="handleTabChange"
/>
</div>
</div>
<!-- 倒计时展示 -->
<div
v-if="tabList.length > 0"
class="seckill-bar flex flex-middle flex-center"
>
<p>{{ seckillTip }}</p>
<div class="seckill-bar-countdown flex flex-middle">
<div class="seckill-bar-countdown__time">{{ countdown.hour }}</div>
<span class="seckill-bar-countdown--mark">:</span>
<div class="seckill-bar-countdown__time">{{ countdown.minute }}</div>
<span class="seckill-bar-countdown--mark">:</span>
<div class="seckill-bar-countdown__time">{{ countdown.second }}</div>
</div>
</div>
<!-- 秒杀商品列表 -->
<div v-loading="loading">
<div class="seckill-products flex flex-wrap">
<div
v-for="item in goodsList"
:key="item.productId"
@click="onJumpGoodsDetail(item.productId)"
class="seckill-products-wrap"
>
<img
:src="item.productMainPicture"
class="seckill-products-wrap__cover"
/>
<div class="seckill-products-wrap__content">
<p class="products-wrap-content__title">{{ item.productName }}</p>
<div class="products-wrap-content__price">
<strong>¥{{ item.activityPrice }}</strong>
<del>¥{{ item.originalPrice }}</del>
</div>
<div
class="products-wrap-content__footer flex flex-middle flex-between"
>
<div class="wrap-content-footer__stock">
<el-progress :percentage="40" :show-text="false"></el-progress>
<p>仅剩{{ item.stock }}件</p>
</div>
<div class="wrap-content-footer__btn">立即抢购</div>
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="seckill-pagination flex flex-right">
<el-pagination
background
:current-page="currentPage"
:page-size="query.length"
layout="prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
<el-button class="btn-confirm"></el-button>
</div>
<DialogEnd :visible="seckillEndVisible" @close="handleClose" />
</div>
</template>
<script>
import {
ApiGetSeckillTimes,
ApiGetSeckillGoods,
ApiGetCurrentTime,
} from "@/plugins/api/seckill";
import { SECKILL_STATUS } from "@/constants";
import TabBar from "./module/TabBar.vue";
import DialogEnd from "./module/DialogEnd.vue";
const PAGE_SIZE = 16;
export default {
name: "Sckill",
components: { TabBar, DialogEnd },
data() {
return {
bkgSckill: require("~/assets/img/sckill/bkg-large.png"),
tabList: [], // 秒杀时间段
goodsList: [], // 商品列表
total: 0,
currentPage: 0,
loading: false,
query: {
pageIndex: 1,
length: PAGE_SIZE,
activityTimeId: 0,
},
ticking: false,
isSticky: false,
timeInterval: null, // 倒计时定时器
seckillTip: "", // 秒杀提示文案
seckillTime: 0, // 秒杀时间
seckillEndVisible: false, //秒杀结束弹窗
seckillStatus: SECKILL_STATUS.NOT_START, // 当前秒杀时间段状态
};
},
computed: {
countdown() {
const data = parseInt(this.seckillTime / 1e3);
const hour = Math.floor(data / 60 / 60);
const minute = Math.floor((data / 60) % 60);
const second = Math.floor(data % 60);
return {
hour: hour < 10 ? `0${hour}` : `${hour}`,
minute: minute < 10 ? `0${minute}` : `${minute}`,
second: second < 10 ? `0${second}` : `${second}`,
};
},
},
watch: {
// 切换秒杀时间段
"query.activityTimeId": {
immediate: true,
handler(val) {
if (val === 0) {
return;
}
clearImmediate(this.timeInterval);
const tabItem = this.tabList.find((item) => item.id === val);
if (tabItem) {
const { activityStartTime, activityEndTime } = tabItem;
this.setTimerInterval({
startTime: activityStartTime,
endTime: activityEndTime,
});
}
},
},
},
created() {
this.getSeckillTimes();
},
mounted() {
// 监听滚动事件
window.addEventListener("scroll", this.scrollEventMethod);
},
destroyed() {
window.removeEventListener("scroll", this.scrollEventMethod);
clearInterval(this.timeInterval);
},
methods: {
scrollEventMethod() {
const that = this;
// 节流
if (!that.ticking) {
window.requestAnimationFrame(function () {
that.ticking = false;
that.isSticky = window.scrollY > 400;
});
that.ticking = true;
}
},
async getSeckillTimes() {
const { result } = await ApiGetSeckillTimes();
if (result && result.length > 0) {
this.tabList = result;
const inProgressItem = result.find((item) => item.isInProgress);
this.query.activityTimeId =
(inProgressItem && inProgressItem.id) || result[0].id;
this.getGoodsList();
}
},
async getGoodsList() {
this.loading = true;
const { result } = await ApiGetSeckillGoods({ ...this.query });
this.loading = false;
if (result) {
this.total = result.total;
this.goodsList = result.records;
}
},
// 设置定时器
async setTimerInterval({ startTime, endTime }) {
clearInterval(this.timeInterval);
// 获取服务器当前时间
const { result } = await ApiGetCurrentTime();
const current = result || Date.now();
startTime = new Date(startTime).getTime();
endTime = new Date(endTime).getTime();
if (current > endTime) {
// 秒杀结束
this.getSeckillTimes();
this.queryDataReset();
return;
}
if (startTime < current && endTime > current) {
// 秒杀进行中
this.seckillTime = endTime - current;
this.seckillTip = "本场正在秒杀中,好物转瞬即逝,不要错过哦~距结束仅剩";
this.seckillStatus = SECKILL_STATUS.GOING;
} else {
// 秒杀未开始
this.seckillTime = startTime - current;
this.seckillTip = "本场秒杀即将开抢,距开始还剩";
this.seckillStatus = SECKILL_STATUS.NOT_START;
}
this.timeInterval = setInterval(() => {
if (this.seckillTime <= 0) {
clearInterval(this.timeInterval);
// 仅有一个进行中的秒杀时间段,倒计时完成展示结束弹窗
if (
this.tabList.length === 1 &&
this.seckillStatus === SECKILL_STATUS.GOING
) {
this.seckillEndVisible = true;
return;
}
// 秒杀开始activityTimeId重置触发watch
this.query.activityTimeId = 0;
this.getSeckillTimes();
return;
}
this.seckillTime -= 1e3;
}, 1e3);
},
// 重置分页等数据
queryDataReset() {
Object.assign(this.query, {
pageIndex: 1,
length: PAGE_SIZE,
});
},
onJumpGoodsDetail(id) {
this.$router.push(`/goods/detail/${id}`);
},
handleSizeChange(size) {
this.query.length = size;
this.getGoodsList();
},
handleCurrentChange(page) {
this.query.pageIndex = page;
this.getGoodsList();
},
handleTabChange(id) {
this.query.activityTimeId = id;
this.queryDataReset();
this.getGoodsList();
},
// 离开活动页面
handleClose() {
this.$router.push("/");
},
},
};
</script>
<style lang="scss" scoped>
.seckill {
background: #f8f8f8;
padding-bottom: 42px;
.seckill-header-sticky {
position: fixed;
left: 50%;
transform: translate(-50%);
top: 50px;
z-index: 1;
@include layout-box;
}
.seckill-header {
position: relative;
width: 100%;
height: 156px;
background-size: 100% 100%;
.seckill-header-tabbar {
@include layout-box;
position: absolute;
left: 50%;
bottom: 0;
transform: translate(-50%);
}
}
.seckill-bar {
height: 60px;
line-height: 60px;
text-align: center;
font-size: 14px;
color: #666666;
.seckill-bar-countdown {
margin-left: 25px;
font-weight: bold;
&__time {
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
font-size: 14px;
color: #ffffff;
background: #2f3430;
}
&--mark {
display: block;
margin: 0 7px;
color: #2f3430;
}
}
}
.seckill-products,
.seckill-pagination {
@include layout-box;
}
.seckill-products {
.seckill-products-wrap {
width: 24%;
background: #ffffff;
margin-bottom: 16px;
cursor: pointer;
&:not(:nth-child(4n)) {
margin-right: calc(4% / 3);
}
.seckill-products-wrap__cover {
width: 100%;
height: 288px;
}
.seckill-products-wrap__content {
padding: 20px 16px;
.products-wrap-content__title {
@include ellipsis;
color: #333333;
font-size: 16px;
margin-bottom: 10px;
}
.products-wrap-content__price {
font-size: 12px;
color: #999999;
margin-bottom: 7px;
strong {
font-size: 18px;
color: #ff512b;
margin-right: 14px;
}
}
.products-wrap-content__footer {
.wrap-content-footer__stock {
font-size: 12px;
color: #666666;
/deep/.el-progress {
width: 100px;
height: 6px;
background: #dddddd;
border-radius: 4px;
margin-bottom: 8px;
}
}
.wrap-content-footer__btn {
width: 100px;
height: 36px;
line-height: 36px;
text-align: center;
background: #ff512b;
font-size: 14px;
color: #ffffff;
}
}
}
}
}
.seckill-pagination {
margin-top: 60px;
/deep/.el-pagination {
.btn-prev,
.btn-next {
width: 32px;
height: 32px;
background: #ffffff;
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.el-pagination__jump {
color: #333333;
}
.el-pager {
.number {
width: 32px;
height: 32px;
line-height: 32px;
font-size: 14px;
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 2px;
}
.active {
color: #ffffff;
background: #ff512b;
border: none;
}
}
}
.btn-confirm {
width: 81px;
height: 32px;
border-radius: 2px;
margin-left: 14px;
}
}
}
</style>