2022-09-26 工作记录--React-js将时间戳转换成“时分秒”+“时分秒”的倒计时(含tab切换)

一、实现效果

请添加图片描述

假如后端返了两个数据:当前时间13位时间戳currentTimestamp秒抢时间13位时间戳seckillTimestamp,想实现“距开始秒抢”的倒计时,即:秒抢时间与当前时间之间的时间差以“时分秒”的形式进行倒计时,如下动图:

请添加图片描述

二、实现方式

第一步、封装方法——js将时间戳转换成“时分秒” ⭐️

【注意】:是“时分秒”,不是 “天时分秒”哦~ (二者写法是有区别滴哦)
请添加图片描述

utils.js

/** 
 1、js获取 倒计时 时分秒
 通过时间戳的方式来 
 let h =  Math.trunc(dec / 3600); // 时
 let m =  Math.trunc(dec % 3600 / 60); // 分
 let s =  Math.trunc(dec % 3600 % 60); // 秒
 2、参数分析:
 @param {Num} inputTime: 需要转换成 时分秒 的 13位时间戳
 @param {Boolean} isPop: 便于实现两种返回结果:true -> `${h}小时${m}分${s}秒` ; false -> 包含时分秒的finalDateObj对象
 */
export function timestampFormatter(inputTime, isPop = false) {
  // 最终时间结果对象
  const finalDateObj = {
    h: null, // 小时
    m: null, // 分钟
    s: null, // 秒
  }
  
  // 剩余时间总的毫秒数 除以 1000 变为总秒数(时间戳为13位 需要除以1000,为10位 则不需要)
  let dec = inputTime / 1000;
  
  if (dec <= 0) {
    dec = 0;
  }
  
  // 得到小时 格式化成前缀加零的样式
  let h = Math.trunc(dec / 3600);
  h = h < 10 ? '0' + h : h;
  // 得到分钟 格式化成前缀加零的样式
  let m = Math.trunc(dec % 3600 / 60);
  m = m < 10 ? '0' + m : m;
  // 得到秒 格式化成前缀加零的样式
  let s = Math.trunc(dec % 3600 % 60);
  s = s < 10 ? '0' + s : s;

  finalDateObj.h = h;
  finalDateObj.m = m;
  finalDateObj.s = s;

  return isPop ? `${h}小时${m}${s}` : finalDateObj;
}

第二步、实现“时分秒”的倒计时

请添加图片描述

实现思路同我的另外一篇博文,相比其代码,只是有两处区别,如下:

countDownComponent.jsx

这是含倒计时的子组件。

1、倒计时初始值

参考项目代码:

// 1、初始化state
state = { 
  	canCountDown: true, // 判断是否开启定时器
  	finalDate: '00天00小时00分00秒', // 倒计时初始值
}

现项目代码: ⭐️

// 1、初始化state
state = { 
  canCountDown: true, // 判断是否开启定时器
  h1: '0', // 倒计时初始值-时
  h2: '0', // 倒计时初始值-时
  m1: '0', // 倒计时初始值-分
  m2: '0', // 倒计时初始值-分
  s1: '0', // 倒计时初始值-秒
  s2: '0', // 倒计时初始值-秒 
}
2、获取到倒计时最终值【位于方法updateTime里】

参考项目代码:

/** 调用上面封装的方法timestampFormatter,计算到倒计时的展示结果,并state赋值,方便html里进行展示 */
const result = timestampFormatter(countDownTimestamp, true);
this.setState({
  	finalDate: result,
})

现项目代码: ⭐️

/** 调用上面封装的方法timestampFormatter,计算到倒计时的展示结果,并state赋值,方便html里进行展示 */
const _h = timestampFormatter(countDownTime).h + '';
const _m = timestampFormatter(countDownTime).m + '';
const _s = timestampFormatter(countDownTime).s + '';
this.setState({
     h1: _h.slice(0, 1),
     h2: _h.slice(-1),
     m1: _m.slice(0, 1),
     m2: _m.slice(-1),
     s1: _s.slice(0, 1),
     s2: _s.slice(-1),
})

第三步、HTML渲染 ⭐️

render(){
	const { h1, h2, m1, m2, s1, s2 } = this.state;
	return(
		<div __examplenotes="倒计时" className="countDown">
             <span __examplenotes="倒计时背景" className="countDownBg"></span>
             <span __examplenotes="时" className="commonTime hour1">{h1}</span>
             <span __examplenotes="时" className="commonTime hour2">{h2}</span>
             <span __examplenotes="分" className="commonTime minute1">{m1}</span>
             <span __examplenotes="分" className="commonTime minute2">{m2}</span>
             <span __examplenotes="秒" className="commonTime second1">{s1}</span>
             <span __examplenotes="秒" className="commonTime second2">{s2}</span>
       </div>
	 )
}

三、坑💥

如上结果动图可知:我们实现的是一个可以tab切换的倒计时项目。

  • 实现思路:把倒计时相关的部分封装成一个组件,随之渲染页面。
  • 坑💥:当tab1里的第一条数据的倒计时小于0时,tab2里的第一条数据的倒计时 自动归置为0(即使 其并非为0);当tab1里的第二条数据的倒计时小于0时,tab2里的第二条数据的倒计时 自动归置为0(即使 其并非为0);依次类推。。。
  • 分析原因:原来是因为我们在更新倒计时时间时定义了如下代码【当倒计时小于0时,定时器被关闭了】,如下图所示:
    在这里插入图片描述
  • 解决方法:我们需要在切换tab时,判断组件的定时器timer是不是null,如果是nullcountDownTimestamp大于0,调下funTimer(),重新开启定时器,代码如下:【需结合我的另外一篇博文一起看哦~】

countDownComponent.jsx

这是含倒计时的子组件。

componentDidMount() {
	// 发送事件"startTimer"
    document.addEventListener("startTimer", this.startTimer, this);
}

componentWillUnmount() {
	// 移除事件"startTimer"
    document.removeEventListener("startTimer", this.startTimer, this);
}

// 判断组件的定时器timer是不是null,如果是null且countDown大于0,调下funTimer()
startTimer = () => {
   	/** seckillTimestamp:秒杀开始时间的时间戳,currentTimestamp:当前时间的时间戳 */
    const seckillTimestamp = 1664280000000; // 秒杀开始时间【实际情况时 由后台返回,这儿写死只是做个测试】
    /** 计算倒计时的展示结果,并return值,方便html里进行展示 */
    const countDownTimestamp = seckillTimestamp * 1 - this.currentTimestamp * 1; // 计算得到 倒计时 的时间戳
    if (this.timer == null && countDownTimestamp > 0) {
        this.FunTimer();
    }
}

homePage.jsx

这是调用倒计时子组件的首页

// 标题tab切换按钮 - 0->今日秒抢, 1->明日秒抢
switchTitle = _throttle(async (index) => {
   // 调用组件CountDownComponent里的方法
   document.dispatchEvent(new CustomEvent("startTimer", {}));
}, 1000)

四、完整代码 ⭐️

父页面 homePage.jsx

'use strict';

import React from 'react';
import { observer } from 'mobx-react';
import store from '@src/store';
import { _throttle } from '@src/utils/utils';
import SeckillDetailComponent from './seckillDetailComponent/seckillDetailComponent'; // 引入秒杀详情组件
import modalStore from '@src/store/modal';
import { Toast } from '@spark/ui';
import { CHANNEL_ERROR } from '@src/utils/constants';

import './homePage.less';

@observer
class HomePage extends React.Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        store.getHomeData(); // 请求接口-首页
    }

    // 点击「奖品」按钮,进入奖品页面
    toPrizePage = _throttle(() => {
        const {
            // 首页接口是否报错
            isError,
            // 0:不在区域 1:北京2:上海3:深圳4:杭州
            orgNo,
            // 除错误码600004以外的提示信息
            errorMessage,
        } = store;
        (!isError && orgNo != 0) ? store.changePage('myPrizePage') : Toast(errorMessage ? errorMessage : CHANNEL_ERROR);
    })

    // 点击「规则」按钮,打开【活动规则弹窗】
    openRulePop = _throttle(() => {
        const {
            // 首页接口是否报错
            isError,
            // 0:不在区域 1:北京2:上海3:深圳4:杭州
            orgNo,
            // 除错误码600004以外的提示信息
            errorMessage,
        } = store;
        (!isError && orgNo != 0) ? modalStore.pushPop('PublicPopupWindow', { type: 'rule' }) : Toast(errorMessage ? errorMessage : CHANNEL_ERROR);
    })

    // 标题切换按钮 - 0->今日秒抢, 1->明日秒抢
    switchTitle = _throttle(async (index) => {
        // 设置当前展示日的索引值
        store.setSeckillIndex(index);
        // 刷新首页数据
        await store.getHomeData();
        // 调用组件SeckillDetailComponent里的方法
        document.dispatchEvent(new CustomEvent("startTimer", {}));
    }, 1000)

    render() {
        const {
            // 当前展示的秒杀数据
            seckillData,
            // 当前展示的秒杀数据的索引值 0-今日秒抢 1-明日秒抢
            seckillIndex,
        } = store;

        return (
            <div className="homePage">
                <span __examplenotes="首页背景" className="homeBackground"></span>
                <div __examplenotes="秒杀活动" className="seckill">
                    <div __examplenotes="第一场秒杀活动" className="seckillFirst">
                        <div __examplenotes="秒杀标题" className={`seckillBg ${seckillIndex == 0 ? 'todaySeckillBg' : 'tomorrowSeckillBg'}`}>
                            {
                                Array(2).fill('').map((item, index) => {
                                    return (
                                        <div __examplenotes="标题切换按钮-今日秒抢/明日秒抢" className="switchBtn" onClick={() => { this.switchTitle(index) }} key={index}></div>
                                    )
                                })
                            }
                        </div>
                        <span __examplenotes="第一场秒杀活动背景" className="underFrame"></span>
                        <div __examplenotes="第一场秒杀活动详情" className="seckillItem">
                            {seckillData?.length > 0 && seckillData[0] != undefined && <SeckillDetailComponent seckillDetail={seckillData[0]} />}
                        </div>
                    </div>
                    {seckillData?.length > 1 && seckillData[1] != undefined && <div __examplenotes="第二场秒杀活动" className="seckillSecond">
                        <span __examplenotes="第二场秒杀活动背景" className="underBorder"></span>
                        <div __examplenotes="第二场秒杀活动详情" className="seckillItem2">
                            <SeckillDetailComponent seckillDetail={seckillData[1]} />
                        </div>
                    </div>}
                </div>
                <span __examplenotes="按钮-奖品" className="prize" onClick={this.toPrizePage}></span>
                <span __examplenotes="按钮-规则" className="rule" onClick={this.openRulePop}></span>
            </div>
        );
    }
}

export default HomePage;

子组件 seckillDetailComponent.jsx

'use strict';

import React from 'react';
import { observer } from 'mobx-react';
import store from '@src/store';
import { _throttle } from '@src/utils/utils';
import { Toast } from '@spark/ui';
import { timestampFormatter } from '@src/utils/utils'; // 引入上面封装好的方法——`js`将时间戳转换成“时分秒”

import './seckillDetailComponent.less';

@observer
class SeckillDetailComponent extends React.Component {
    constructor(props) {
        super(props);
        
        // 1、初始化state
        this.state = {
            canCountDown: true, // 判断是否开启定时器
            h1: '0', // 倒计时初始值-时
            h2: '0', // 倒计时初始值-时
            m1: '0', // 倒计时初始值-分
            m2: '0', // 倒计时初始值-分
            s1: '0', // 倒计时初始值-秒
            s2: '0', // 倒计时初始值-秒    
        }
    }

    // 当前时间的时间戳【注意: currentTime一定要记得*1转换成数值型哟】—— 这里是组件,所以可以直接这样写获取到store?.currentTime
    currentTime = store?.currentTime * 1;
    // 2、初始化定时器
    timer = null;

    componentDidMount() {
        this.handleCountDownLogic();

        // 发送事件"startTimer"
        document.addEventListener("startTimer", this.startTimer, this);
    }

    componentWillUnmount() {
        // 清除定时器
        clearInterval(this.timer);

        // 移除事件"startTimer"
        document.removeEventListener("startTimer", this.startTimer, this);
    }

    // 判断组件的定时器timer是不是null,如果是null且countDown大于0,调下funTimer()
    startTimer = () => {
        /** currentTime:当前时间,startTime:秒杀开始时间 */
        const { startTime } = this.props?.seckillDetail || {};
        /** 计算倒计时的展示结果,并return值,方便html里进行展示 */
        const countDownTime = startTime * 1 - this.currentTime * 1; // 计算得到 倒计时 的时间戳
        if (this.timer == null && countDownTime > 0) {
            this.FunTimer();
        }
    }

    // 处理倒计时总逻辑
    handleCountDownLogic() {
        // 倒计时
        if (this.state.canCountDown) {
            this.setState({ canCountDown: false })
            this.FunTimer(); // 调用函数
        } else {
            this.setState({ canCountDown: true })
            clearInterval(this.timer); // 关闭定时器
        }
    }

    // 封装函数-倒计时
    FunTimer = () => {
        this.timer = setInterval(() => {
            // 想实现倒计时的代码就写在这里啦~【我单独封装了一个函数】
            this.updateTime();
        }, 1000)
    }

    // 更新时间
    updateTime = () => {
        /** currentTime:当前时间,startTime:秒杀开始时间 */
        const { startTime } = this.props?.seckillDetail || {};
        /** 手动把获取到的当前时间 隔一秒加1s*/
        this.currentTime += 1000;
        /** 计算倒计时的展示结果,并return值,方便html里进行展示 */
        const countDownTime = startTime * 1 - this.currentTime * 1; // 计算得到 倒计时 的时间戳
        const _h = timestampFormatter(countDownTime).h + '';
        const _m = timestampFormatter(countDownTime).m + '';
        const _s = timestampFormatter(countDownTime).s + '';
        this.setState({
            h1: _h.slice(0, 1),
            h2: _h.slice(-1),
            m1: _m.slice(0, 1),
            m2: _m.slice(-1),
            s1: _s.slice(0, 1),
            s2: _s.slice(-1),
        })
        // 倒计时小于0,则刷新首页数据、关闭定时器
        if (countDownTime < 0) {
            console.log('倒计时小于0');
            clearInterval(this.timer); // 关闭定时器
            this.timer = null; // 将定时器归置为null
            
            // 根据自己需求进行操作
            store.getHomeData(); // 刷新首页数据
        }
    }

    /**
     * 点击对应按钮
     * @param {Num} type 1->按钮-立即秒杀 2->按钮-已抢到 3->按钮-已抢完
     */
    clickButton = _throttle((type) => {
        // orgNo 0:不在区域 1:北京2:上海3:深圳4:杭州
        const { orgNo } = store;
        // goodsId: 商品id, name: 奖品名称
        const { goodsId, name } = this.props?.seckillDetail || {};
        switch (type) {
            case 1:
                store.doSeckill(goodsId, orgNo, name);
                break;
            case 2:
                Toast(`您已抢到${name},请前往【奖品】查看`);
                break;
            case 3:
                Toast(`本场${name}已抢完`);
                break;
        }
    })


    render() {
        // 需渲染的秒杀数据
        const { seckillDetail } = this.props;

        const {
            // 首页接口是否报错
            isError,
            // 0:不在区域 1:北京2:上海3:深圳4:杭州
            orgNo,
            // 活动是否结束 true:活动结束
            ifEnd,
        } = store;

        const { h1, h2, m1, m2, s1, s2 } = this.state;
        return (
            <div>
                {
                    !isError && !ifEnd && orgNo != 0 &&
                    <div __examplenotes="秒杀详情组件" className="seckillDetailComponent">
                        <div __examplenotes="奖品介绍" className="prizeIntroduction">
                            <span __examplenotes="奖品介绍背景" className="prizeBg"></span>
                            <div __examplenotes="奖品图片" className="prizeImage">
                                <img src={seckillDetail?.icon} alt="" />
                            </div>
                            <span __examplenotes="奖品名称" className="prizeName text-hidden-ellipsis">{seckillDetail?.name}</span>
                            <span __examplenotes="奖品剩余量" className="surplus text-hidden-ellipsis">{seckillDetail?.status != 0 && seckillDetail?.status != 3 && seckillDetail?.status != 0 && '正在疯抢 '}剩余:{seckillDetail?.stock}</span>
                            <div __examplenotes="滚动条" className="scrollbar">
                                <div __examplenotes="滚动条外壳" className="outerScrollbar">
                                    <span __examplenotes="滚动条内里" className="insideScrollbar" style={{ 'transform': `translateX(${seckillDetail?.process}%)` }}></span>
                                </div>
                            </div>
                            <div __examplenotes="按钮" className="button">
                                {seckillDetail?.status == 0 && <span __examplenotes="按钮-等待开抢" className="commonButton btn-waitToSeckill"></span>}
                                {seckillDetail?.status == 1 && <span __examplenotes="按钮-立即秒杀" className="commonButton btn-immediatelySeckill" onClick={() => { this.clickButton(1) }}></span>}
                                {seckillDetail?.status == 2 && <span __examplenotes="按钮-已抢到" className="commonButton btn-gotten" onClick={() => { this.clickButton(2) }}></span>}
                                {seckillDetail?.status == 3 && <span __examplenotes="按钮-已抢完" className="commonButton btn-robbed" onClick={() => { this.clickButton(3) }}></span>}
                            </div>
                        </div>
                        <div __examplenotes="倒计时" className="countDown">
                            <span __examplenotes="倒计时背景" className="countDownBg"></span>
                            <span __examplenotes="时" className="commonTime hour1">{h1}</span>
                            <span __examplenotes="时" className="commonTime hour2">{h2}</span>
                            <span __examplenotes="分" className="commonTime minute1">{m1}</span>
                            <span __examplenotes="分" className="commonTime minute2">{m2}</span>
                            <span __examplenotes="秒" className="commonTime second1">{s1}</span>
                            <span __examplenotes="秒" className="commonTime second2">{s2}</span>
                        </div>
                        <div __examplenotes="场次标题" className="openTitle">
                            <span __examplenotes="场次标题背景" className="titleBg"></span>
                            <span __examplenotes="场次标题内容" className="titleName text-hidden-ellipsis">{seckillDetail?.title}</span>
                        </div>
                    </div>
                }
            </div>
        );
    }
}

export default SeckillDetailComponent;

请添加图片描述

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小呀小萝卜儿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值