You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AlpineJS倒计时异常:显示负值而非剩余时长问题求助

AlpineJS电商发货倒计时负数值问题修复

问题描述

基于AlpineJS开发的电商发货倒计时功能,需求为:

  • 读取后台设置的发货时间,展示距离该时间的剩余时长
  • 当后台设置发货时间为16:00、当前时间为15:00时,显示59m 59s
  • 时间到达16:00后,自动切换为下一个发货日的倒计时(如23h 59m 59s)继续运行

功能初期可用,但运行约1小时后,会显示自16:00起流逝的秒数负值,而非正常剩余时长。原代码如下:

window.shippingAlert = (
    days,
    time,
    offdays,
) => ({
    days: [],
    offdays: [],
    offDates: [],
    hour: '',
    minute: '',
    nextShipmentUnix: null,
    timeLeftString: "",
    init() {
        this.days = days.replace(7, 0).split(',').map((x) => parseInt(x)).sort(); // replace 7 (sunday) with 0 to match JS dates.

        let offDaysArray = offdays.split(',');

        this.offDates = [];

        for (let i = 0; i < offDaysArray.length; i++) {
            let dateParts = offDaysArray[i].split("-");
            let year = parseInt(dateParts[0]);
            let month = parseInt(dateParts[1]) - 1;
            let day = parseInt(dateParts[2]);
            this.offDates.push(new Date(year, month, day));
          }

        console.log(this.offDates)
                  
        let timeParts = time.split(':');
        this.hour = parseInt(timeParts[0]);
        this.minute = parseInt(timeParts[1] || 0);

        this.calculateNextShipment();
        this.calculateTimeLeft();

        window.setInterval(() => this.calculateTimeLeft(), 1000);
    },
    calculateNextShipment() {
        let currentDate = new Date();
        let currentDay = currentDate.getDay();
        let currentHour = currentDate.getHours();
        let currentMinute = currentDate.getMinutes();
      
        let nextDay = currentDay;
      
        // If time is passed, move to next days
        if (nextDay > this.hour || (currentHour === this.hour && currentMinute >= this.minute)) {
          nextDay = nextDay === 6 ? 0 : (nextDay + 1);
        }
      
        // If no shipments occur on the day, move on until the next available day
        if (!this.days.includes(nextDay)) {
          nextDay = this.days.filter((day) => day > nextDay)?.[0] || this.days[0];
        }
      
        // If there are specific dates where shipments do not occur
        let nextDate = new Date();
        let isOffDate = true;
        while (isOffDate) {
          nextDate.setDate(nextDate.getDate() + (nextDay >= currentDay ? (nextDay - currentDay) : (7 - currentDay + nextDay)));
          nextDate.setHours(this.hour);
          nextDate.setMinutes(this.minute);
          nextDate.setSeconds(0);
      
          isOffDate = this.offDates.some((offDate) => offDate.getDay() === nextDate.getDay());
          if (isOffDate) {
            nextDay = this.days.filter((day) => day > nextDay)?.[0] || this.days[0];
          }
        }
      
        console.log(nextDate);
      
        this.nextShipmentUnix = Math.floor(nextDate.getTime() / 1000);
      },
      calculateTimeLeft() {
        let currentTimeUnix = Math.floor(new Date().getTime() / 1000);
        let secondsLeft = this.nextShipmentUnix - currentTimeUnix;
      
        if (secondsLeft <= 0) {
          this.calculateNextShipment();
          currentTimeUnix = Math.floor(new Date().getTime() / 1000); // Update the current time
          secondsLeft = this.nextShipmentUnix - currentTimeUnix;
        }
      
        let parts = [];
      
        let days = Math.floor(secondsLeft / 86400);
      
        if (days > 0) {
          secondsLeft = secondsLeft - (days * 86400);
          parts.push(days + 'd');
        }
      
        let hours = Math.floor(secondsLeft / 3600);
      
        if (hours > 0) {
          secondsLeft = secondsLeft - (hours * 3600);
          parts.push(hours + 't');
        }
      
        let minutes = Math.floor(secondsLeft / 60);
      
        if (minutes > 0) {
          secondsLeft = secondsLeft - (minutes * 60);
          parts.push(minutes + 'm');
        }
      
        parts.push(secondsLeft + 's');
      
        this.timeLeftString = parts.join(' ');
      },
})

问题根源

  1. 时间判断逻辑完全错误:原代码用nextDay(星期几,0-6)> this.hour(小时数,如16)判断时间是否已过,类型不匹配导致逻辑失效,计算出的nextShipmentUnix指向错误时间点。
  2. 节假日判断逻辑错误:用offDate.getDay() === nextDate.getDay()判断节假日,仅比较星期几而非完整日期,错误跳过所有与节假日星期几相同的正常发货日。
  3. 剩余秒数检查不严谨:仅用if判断一次剩余秒数,极端情况下重新计算发货时间后仍可能出现负值。

修复后的完整代码

window.shippingAlert = (
    days,
    time,
    offdays,
) => ({
    days: [],
    offDates: [],
    hour: '',
    minute: '',
    nextShipmentUnix: null,
    timeLeftString: "",
    init() {
        // 替换周日标识7为0,匹配JS星期格式并排序
        this.days = days.replace(7, 0).split(',').map(x => parseInt(x)).sort();

        // 解析节假日为Date对象(过滤空值)
        this.offDates = offdays.split(',').filter(dateStr => dateStr.trim()).map(dateStr => {
            const [year, month, day] = dateStr.split("-").map(Number);
            return new Date(year, month - 1, day);
        });

        // 解析发货时间
        const timeParts = time.split(':');
        this.hour = parseInt(timeParts[0]);
        this.minute = parseInt(timeParts[1] || 0);

        this.calculateNextShipment();
        this.calculateTimeLeft();

        window.setInterval(() => this.calculateTimeLeft(), 1000);
    },
    calculateNextShipment() {
        const currentDate = new Date();
        const currentDay = currentDate.getDay();
        const currentHour = currentDate.getHours();
        const currentMinute = currentDate.getMinutes();

        let nextDay = currentDay;
        let deltaDays = 0;

        // 判断当日发货时间是否已过,若是则切换到下一天
        if (currentHour > this.hour || (currentHour === this.hour && currentMinute >= this.minute)) {
            nextDay = nextDay === 6 ? 0 : nextDay + 1;
            deltaDays = 1;
        }

        // 找到下一个可用的发货星期几
        if (!this.days.includes(nextDay)) {
            const nextAvailableDay = this.days.find(day => day > nextDay);
            nextDay = nextAvailableDay !== undefined ? nextAvailableDay : this.days[0];
            // 计算到目标星期几的天数(含跨周情况)
            deltaDays = nextDay >= currentDay ? (nextDay - currentDay) : (7 - currentDay + nextDay);
            // 若当日已过发货时间,天数需叠加
            if (currentHour > this.hour || (currentHour === this.hour && currentMinute >= this.minute)) {
                deltaDays = nextDay > currentDay ? (nextDay - currentDay) : (7 - currentDay + nextDay);
            }
        }

        // 初始化下一个发货日期
        let nextDate = new Date(currentDate);
        nextDate.setDate(currentDate.getDate() + deltaDays);
        nextDate.setHours(this.hour);
        nextDate.setMinutes(this.minute);
        nextDate.setSeconds(0);

        // 检查是否为节假日(精确匹配年/月/日)
        const isOffDate = (date) => {
            return this.offDates.some(offDate => {
                return offDate.getFullYear() === date.getFullYear() &&
                       offDate.getMonth() === date.getMonth() &&
                       offDate.getDate() === date.getDate();
            });
        };

        // 循环跳过节假日,找到下一个有效发货日
        while (isOffDate(nextDate)) {
            const currentNextDayIndex = this.days.indexOf(nextDate.getDay());
            nextDay = this.days[(currentNextDayIndex + 1) % this.days.length];
            const daysToAdd = nextDay > nextDate.getDay() ? (nextDay - nextDate.getDay()) : (7 - nextDate.getDay() + nextDay);
            nextDate.setDate(nextDate.getDate() + daysToAdd);
            nextDate.setHours(this.hour);
            nextDate.setMinutes(this.minute);
            nextDate.setSeconds(0);
        }

        this.nextShipmentUnix = Math.floor(nextDate.getTime() / 1000);
    },
    calculateTimeLeft() {
        let currentTimeUnix = Math.floor(new Date().getTime() / 1000);
        let secondsLeft = this.nextShipmentUnix - currentTimeUnix;

        // 循环检查,确保剩余秒数为正
        while (secondsLeft <= 0) {
            this.calculateNextShipment();
            currentTimeUnix = Math.floor(new Date().getTime() / 1000);
            secondsLeft = this.nextShipmentUnix - currentTimeUnix;
        }

        const parts = [];

        const days = Math.floor(secondsLeft / 86400);
        if (days > 0) {
            secondsLeft -= days * 86400;
            parts.push(`${days}d`);
        }

        const hours = Math.floor(secondsLeft / 3600);
        if (hours > 0) {
            secondsLeft -= hours * 3600;
            parts.push(`${hours}h`); // 原代码用't',改为通用的'h',需保留可自行改回
        }

        const minutes = Math.floor(secondsLeft / 60);
        if (minutes > 0) {
            secondsLeft -= minutes * 60;
            parts.push(`${minutes}m`);
        }

        parts.push(`${secondsLeft}s`);

        this.timeLeftString = parts.join(' ');
    },
})

核心修复要点

  1. 修正时间判断逻辑:用currentHour > this.hour || (currentHour === this.hour && currentMinute >= this.minute)准确判断当日发货时间是否已过。
  2. 修复节假日判断:精确比较日期的年、月、日,避免错误跳过正常发货日。
  3. 强化剩余秒数检查:用while循环替代if,确保重新计算发货时间后剩余秒数一定为正。
  4. 优化发货日计算:调整跨周发货日的天数计算逻辑,确保跳转正确。
  5. 统一时间单位标识:将小时后缀从t改为通用的h,符合用户认知习惯。

内容的提问来源于stack exchange,提问作者insidethehorror

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 14:07:02