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

MERN待办应用中每日任务的创建与定时展示实现方案咨询

MERN待办应用:重复任务与定时展示实现思路

一、模型设计:合并为单一Task模型(更易维护)

不用拆分Task和DailyTask,通过字段区分任务类型与重复规则,避免冗余:

const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
  title: { type: String, required: true },
  description: String,
  // 任务类型:普通/重复
  type: { type: String, enum: ['normal', 'repeat'], default: 'normal' },
  // 重复规则(仅repeat类型生效)
  repeatRule: {
    frequency: { type: String, enum: ['daily', 'weekly'] },
    // weekly类型时,存储目标星期几(0=周日,1-6=周一至周六),比如[1,3,5]代表周一、三、五
    weekdays: [Number]
  },
  // 普通任务的截止时间 / 重复任务的初始执行时间
  dueDate: Date,
  // 重复任务的下次执行时间(核心字段,用于前端计算剩余时间)
  nextOccurrence: Date,
  // 重复任务的上次完成时间(用于计算下一次执行时间)
  lastCompleted: Date,
  completed: { type: Boolean, default: false }
});

module.exports = mongoose.model('Task', taskSchema);

二、后端核心逻辑:计算与更新下次执行时间

后端负责处理重复任务的nextOccurrence计算,确保每次任务完成或创建时生成正确的下次时间:

1. 创建重复任务时初始化nextOccurrence

function initRepeatTaskNextOccurrence(task) {
  if (task.type !== 'repeat') return task;
  
  const now = new Date();
  let nextDate = new Date(task.dueDate);
  
  // 如果初始时间已过期,直接计算下一次符合规则的时间
  if (nextDate <= now) {
    nextDate = calculateNextOccurrence(task);
  }
  
  task.nextOccurrence = nextDate;
  return task;
}

2. 完成重复任务时更新nextOccurrence

function calculateNextOccurrence(task) {
  const now = new Date();
  let nextDate;

  if (task.repeatRule.frequency === 'daily') {
    // 每日重复:当前日期加1天
    nextDate = new Date(task.nextOccurrence || task.dueDate);
    nextDate.setDate(nextDate.getDate() + 1);
    // 若加1天后仍早于当前时间,继续加(避免同一天多次完成的情况)
    while (nextDate <= now) {
      nextDate.setDate(nextDate.getDate() + 1);
    }
  } else if (task.repeatRule.frequency === 'weekly') {
    const targetWeekdays = task.repeatRule.weekdays.sort();
    const currentDay = now.getDay();
    nextDate = new Date(now);
    
    // 寻找最近的下一个目标星期几
    let found = false;
    for (const day of targetWeekdays) {
      if (day > currentDay) {
        nextDate.setDate(now.getDate() + (day - currentDay));
        found = true;
        break;
      }
    }
    
    // 若今天之后无目标星期几,取下周第一个目标日
    if (!found) {
      const firstDay = targetWeekdays[0];
      nextDate.setDate(now.getDate() + (7 - currentDay + firstDay));
    }
    
    // 保持与初始dueDate一致的时分秒
    nextDate.setHours(
      task.dueDate.getHours(),
      task.dueDate.getMinutes(),
      task.dueDate.getSeconds()
    );
  }

  return nextDate;
}

三、前端实现:剩余时间计算与定时展示

1. 剩余时间计算工具函数

// 传入目标日期(普通任务用dueDate,重复任务用nextOccurrence)
function getRemainingTime(targetDate) {
  const now = new Date();
  const diff = targetDate.getTime() - now.getTime();
  
  if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0 };

  const days = Math.floor(diff / (1000 * 60 * 60 * 24));
  const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
  const seconds = Math.floor((diff % (1000 * 60)) / 1000);

  return { days, hours, minutes, seconds };
}

2. 实时更新剩余时间

以React为例,用useEffect+setInterval实现每秒刷新:

import { useState, useEffect } from 'react';

function TaskItem({ task }) {
  const [remainingTime, setRemainingTime] = useState({});

  useEffect(() => {
    const targetDate = task.type === 'repeat' ? task.nextOccurrence : task.dueDate;
    // 初始化剩余时间
    setRemainingTime(getRemainingTime(new Date(targetDate)));

    // 每秒更新一次
    const timer = setInterval(() => {
      setRemainingTime(getRemainingTime(new Date(targetDate)));
    }, 1000);

    return () => clearInterval(timer);
  }, [task]);

  return (
    <div className="task">
      <h3>{task.title}</h3>
      <p>剩余时间:{remainingTime.days}天 {remainingTime.hours}时 {remainingTime.minutes}分 {remainingTime.seconds}秒</p>
    </div>
  );
}

3. 定时刷新任务数据

为避免后端更新nextOccurrence后前端数据过时,可设置定时刷新(比如每小时一次),或在用户完成任务后立即重新获取任务列表:

// 定时刷新任务列表
useEffect(() => {
  const fetchTasks = async () => {
    const res = await fetch('/api/tasks');
    const tasks = await res.json();
    setTasks(tasks);
  };

  fetchTasks();
  const interval = setInterval(fetchTasks, 3600000); // 每小时刷新一次

  return () => clearInterval(interval);
}, []);

四、关键逻辑说明

  • 重复任务完成后不标记为completed: true,仅更新lastCompleted和nextOccurrence,保证后续仍会展示。
  • 普通任务完成后标记completed: true,前端可根据状态隐藏或灰化展示。
  • 后端计算nextOccurrence时,需处理“当前时间已过目标时间”的边界情况,避免出现负的剩余时间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 12:15:42