LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

使用 HTML + JavaScript 实现日程管理卡片(附完整代码)

admin
2026年1月9日 11:40 本文热度 333
在个人时间管理需求持续攀升的背景下,简洁高效的日程管理系统,是帮助用户合理规划每日任务的核心工具。通过整合日历视图与日程列表的卡片式界面,用户可兼顾月度日程全局概览与当日任务细节查看,既能快速洞悉日程分布、便捷添加重要事项,更能以直观的交互设计,提升日程管理效率。本文将介绍如何使用 HTML、CSS 和 JavaScript 实现日程卡片。

效果演示

这个日程管理卡片提供了完整的日程管理功能。用户可以在日历中切换月份,点击日期选择特定日期,在下方的任务列表中查看该日期的安排。用户可以添加新任务,包括标题、时间和描述,也可以删除现有任务。当某一天有任务时,日历中的日期会显示一个小圆点提示。

页面结构

页面主要包括以下几个区域:

日历区域

显示月历,包含月份切换按钮和日期网格。
<div class="calendar">  <div class="calendar-header">    <div class="month-year" id="monthYear"></div>    <div class="btn-group">      <button class="btn-round nav-btn" id="prevMonth"></button>      <button class="btn-round nav-btn" id="nextMonth"></button>    </div>  </div>  <div class="calendar-grid" id="calendar"></div></div>

任务列表区域

显示当前选择日期的任务。

<div class="tasks-title">  <div>任务安排 <span class="tasks-count" id="tasksCount">0</span></div>  <div class="btn-group">    <button class="btn-round opt-btn" id="todayBtn" style="display: none; margin-right: 8px;"></button>    <button class="btn-round opt-btn" id="addBtn">+</button>  </div></div><div class="task-list" id="taskList"><div class="no-tasks">暂无安排</div></div>

表单弹窗

用于添加新任务的模态框。
<div class="overlay" id="overlay"></div><div class="form" id="form">  <h4>添加任务</h4>  <form id="taskForm">    <div class="form-group">      <label>任务标题</label>      <input type="text" id="taskTitle" required>    </div>    <div class="form-group">      <label>时间</label>      <input type="time" id="taskTime" required>    </div>    <div class="form-group">      <label>描述</label>      <textarea id="taskDesc"></textarea>    </div>    <div class="form-buttons">      <button type="button" class="btn btn-secondary" id="cancelBtn">取消</button>      <button type="submit" class="btn btn-primary">添加</button>    </div>  </form></div>

核心功能实现

日历渲染机制

日历渲染的核心是计算每个月的日期网格,包括显示前一个月和后一个月的日期以填满网格。ScheduleCard 类的 renderCalendar 方法处理这个逻辑,首先生成星期标题,然后计算当月第一天是星期几,从而确定起始日期,最后生成42个日期格子。
renderCalendar() {  const calendar = document.getElementById('calendar');  const monthYear = document.getElementById('monthYear');  const monthNames = ['1月''2月''3月''4月''5月''6月''7月''8月''9月''10月''11月''12月'];  monthYear.textContent = `${this.currentDate.getFullYear()}${monthNames[this.currentDate.getMonth()]}`;  calendar.innerHTML = this.getWeekdayElements();  const firstDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);  const startDate = new Date(firstDay);  const dayOfWeek = firstDay.getDay();  const offset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;  startDate.setDate(startDate.getDate() - offset);  const days = [];  for (let i = 0; i < 42; i++) {    const date = new Date(startDate);    date.setDate(startDate.getDate() + i);    days.push(this.createDayElement(date));  }  calendar.innerHTML += days.join('');  const todayBtn = document.getElementById('todayBtn');  const today = new Date();  todayBtn.style.display = this.isSameDate(this.selectedDate, today) ? 'none' : 'block';}

任务数据管理

任务数据通过 localStorage 进行持久化存储,使用 loadTasks 和 saveTasks 方法管理数据。ScheduleCard 类初始化时加载存储的任务数据,如果不存在则使用默认示例数据。
saveTasks() {  localStorage.setItem('scheduleTasks'JSON.stringify(this.tasks));}
loadTasks() {  const saved = localStorage.getItem('scheduleTasks');  return saved ? JSON.parse(saved) : this.getDefaultTasks();}

日期选择与任务显示

selectDate 方法处理日期选择逻辑,更新选中日期并重新渲染日历和任务列表。renderTasks 方法根据当前选择的日期过滤任务并显示在列表中。
selectDate(date) {  this.selectedDate = new Date(date);  this.renderCalendar();  this.renderTasks();}
renderTasks() {  const taskList = document.getElementById('taskList');  const tasksCount = document.getElementById('tasksCount');  const dateStr = this.formatDate(this.selectedDate);  const dayTasks = this.tasks.filter(task => task.date === dateStr);  tasksCount.textContent = dayTasks.length;  if (dayTasks.length === 0) {    taskList.innerHTML = '<div class="no-tasks">暂无安排</div>';    return;  }  dayTasks.sort((a, b) => a.time.localeCompare(b.time));  taskList.innerHTML = dayTasks.map(task => `<div class="task-item" data-task-id="${task.id}">      <button class="btn-round delete-btn">×</button>      <div class="task-time">${task.time}</div>      <div class="task-title">${task.title}</div>      ${task.description ? `<div class="task-desc">${task.description}</div>` : ''}    </div>`).join('');}

扩展建议

  • 添加任务编辑功能,允许修改已创建的任务

  • 实现任务分类标签,支持不同颜色标识

  • 添加任务搜索功能,快速查找特定任务

  • 添加重复任务功能,支持周期性任务设置

完整代码

git地址:https://gitee.com/ironpro/hjdemo/blob/master/schedule/index.html
<!DOCTYPE html><html lang="zh-CN"><head>  <meta charset="utf-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>日程卡片</title>  <style>    * { margin0padding0box-sizing: border-box; }    body { background#f5f5f5min-height100vhpadding20px; }    .container { margin0 auto; background: white; box-shadow0 4px 16px rgba(0000.08); overflow: hidden; width320px; }    .header { background#0086f6color: white; text-align: center; padding10px 0; }    .header h1 { font-size18pxfont-weight600; }    .calendar { padding16pxbackground: white; color#333; }    .calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom14px; }    .month-year { font-size16pxfont-weight600color#1e293b; }    .btn-group { display: flex; }    .btn-round { border-radius50%cursor: pointer; font-size14pxborder: none; }    .nav-btn { border1px solid #e2e8f0background#f1f5f9color#64748bwidth28pxheight28pxmargin0 4pxtransition: all 0.2s; }    .nav-btn:hover { background#e2e8f0color#475569; }    .opt-btn { width32pxheight32pxfont-size16pxbackground#f1f5f9color#64748bborder1px solid #e2e8f0transition: all 0.2s; }    .opt-btn:hover { background#e2e8f0color#475569; }    .delete-btn { position: absolute; top5pxright5pxwidth20pxheight20pxcolor#ef4444font-size16pxbackground: none; border: none; cursor: pointer; transition: all 0.2s; }    .delete-btn:hover { transformscale(1.1); color#dc2626; }    .btn { padding6px 14pxborder: none; cursor: pointer; font-size14pxfont-weight500transition: all 0.2s; }    .btn-primary { background#3b82f6color: white; }    .btn-primary:hover { background#2563eb; }    .btn-secondary { background#e2e8f0color#64748b; }    .btn-secondary:hover { background#cbd5e1; }    .calendar-grid { display: grid; grid-template-columnsrepeat(71fr); gap4px; }    .weekday { text-align: center; font-size11pxpadding4px 0font-weight600color#64748b; }    .day { aspect-ratio1display: flex; align-items: center; justify-content: center; border-radius8pxcursor: pointer; font-size14pxposition: relative; font-weight500color#1e293btransition: all 0.2s; }    .day:hover:not(.other-month):not(.disabled) { background#bfe0fccolor#0f172a; }    .day.other-month { opacity0.5color#94a3b8cursor: default; background: none; }    .day.today { background: transparent; color#0086f6font-weight700border1px solid #0086f6; }    .day.selected { background#0086f6color: white; font-weight700; }    .day.has-task::after { content''position: absolute; bottom3pxwidth5pxheight5pxbackground#f59e0bborder-radius50%; }    .tasks { padding16pxdisplay: flex; flex-direction: column; }    .tasks-title { display: flex; justify-content: space-between; align-items: center; font-size14pxfont-weight600color#1e293bpadding6px 14pxbackground-color#f8fafcborder-top1px solid #e2e8f0border-bottom1px solid #e2e8f0; }    .tasks-count { background#dbeafecolor#1d4ed8font-size11pxpadding2px 8pxborder-radius10pxfont-weight600; }    .task-item { padding14pxmargin-bottom8pxbackground#f8fafcborder-radius8pxposition: relative; border1px solid #e2e8f0transition: all 0.2s; }    .task-item:hover { background#f1f5f9box-shadow0 1px 4px rgba(0000.05); }    .task-time { font-size14pxcolor#3b82f6margin-bottom4pxfont-weight600; }    .task-title { font-size14pxfont-weight600color#1e293bmargin-bottom4pxoverflow: hidden; text-overflow: ellipsis; white-space: nowrap; }    .task-desc { font-size14pxcolor#64748boverflow: hidden; text-overflow: ellipsis; white-space: nowrap; }    .no-tasks { text-align: center; padding20px 10pxcolor#94a3b8font-size13pxbackground#f8fafcborder1px dashed #cbd5e1border-radius8pxmargin-top8px; }    .task-list { flex1height300pxoverflow-y: auto; padding8px; }    .form { position: fixed; top50%left50%transformtranslate(-50%, -50%); background: white; padding24pxbox-shadow0 10px 30px rgba(0000.15); z-index1000width340pxdisplay: none; border1px solid #e2e8f0; }    .form h4 { margin-bottom16pxcolor#1e293bfont-size18pxfont-weight600; }    .form-group { margin-bottom16px; }    .form-group label { display: block; margin-bottom6pxfont-size13pxcolor#334155font-weight500; }    .form-group input.form-group textarea { width100%padding10px 14pxborder1px solid #cbd5e1font-size14pxbackground#ffftransition: all 0.2s; }    .form-group input:focus.form-group textarea:focus { outline: none; border-color#3b82f6box-shadow0 0 0 3px rgba(591302460.2); }    .form-group textarea { resize: vertical; min-height60px; }    .form-buttons { display: flex; gap8pxjustify-content: flex-end; margin-top8px; }    .overlay { position: fixed; top0left0width100%height100%backgroundrgba(0000.4); z-index999display: none; }
  </style></head><body><div class="container">  <div class="header"><h1>我的任务</h1></div>  <div class="schedule-card">    <div class="calendar">      <div class="calendar-header">        <div class="month-year" id="monthYear"></div>        <div class="btn-group">          <button class="btn-round nav-btn" id="prevMonth"></button>          <button class="btn-round nav-btn" id="nextMonth"></button>        </div>      </div>      <div class="calendar-grid" id="calendar"></div>    </div>    <div class="tasks-title">      <div>任务安排 <span class="tasks-count" id="tasksCount">0</span></div>      <div class="btn-group">        <button class="btn-round opt-btn" id="todayBtn" style="display: none; margin-right: 8px;"></button>        <button class="btn-round opt-btn" id="addBtn">+</button>      </div>    </div>    <div class="task-list" id="taskList"><div class="no-tasks">暂无安排</div></div>  </div></div><div class="overlay" id="overlay"></div><div class="form" id="form">  <h4>添加任务</h4>  <form id="taskForm">    <div class="form-group">      <label>任务标题</label>      <input type="text" id="taskTitle" required>    </div>    <div class="form-group">      <label>时间</label>      <input type="time" id="taskTime" required>    </div>    <div class="form-group">      <label>描述</label>      <textarea id="taskDesc"></textarea>    </div>    <div class="form-buttons">      <button type="button" class="btn btn-secondary" id="cancelBtn">取消</button>      <button type="submit" class="btn btn-primary">添加</button>    </div>  </form></div>
<script>  class ScheduleCard {    constructor() {      this.currentDate = new Date();      this.selectedDate = new Date();      this.tasks = this.loadTasks();      this.init();    }
    init() {      this.renderCalendar();      this.renderTasks();      this.bindEvents();    }
    bindEvents() {      document.getElementById('prevMonth').addEventListener('click'() => this.changeMonth(-1));      document.getElementById('nextMonth').addEventListener('click'() => this.changeMonth(1));      document.getElementById('todayBtn').addEventListener('click'() => this.goToToday());      document.getElementById('addBtn').addEventListener('click'() => this.showForm());      document.getElementById('cancelBtn').addEventListener('click'() => this.hideForm());      document.getElementById('overlay').addEventListener('click'() => this.hideForm());      document.getElementById('taskForm').addEventListener('submit'(e) => this.addTask(e));
      document.getElementById('taskList').addEventListener('click'(e) => {        if (e.target.classList.contains('delete-btn')) {          const taskItem = e.target.closest('.task-item');          const taskId = parseInt(taskItem.dataset.taskId);          this.deleteTask(taskId);        }      });
      document.getElementById('calendar').addEventListener('click'(e) => {        if (e.target.classList.contains('day') && !e.target.classList.contains('other-month')) {          const day = parseInt(e.target.textContent);          const date = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), day);          this.selectDate(date);        }      });    }
    changeMonth(direction) {      this.currentDate.setMonth(this.currentDate.getMonth() + direction);      this.renderCalendar();    }
    goToToday() {      this.currentDate = this.selectedDate = new Date();      this.renderCalendar();      this.renderTasks();    }
    renderCalendar() {      const calendar = document.getElementById('calendar');      const monthYear = document.getElementById('monthYear');      const monthNames = ['1月''2月''3月''4月''5月''6月''7月''8月''9月''10月''11月''12月'];      monthYear.textContent = `${this.currentDate.getFullYear()}${monthNames[this.currentDate.getMonth()]}`;      calendar.innerHTML = this.getWeekdayElements();      const firstDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);      const startDate = new Date(firstDay);      const dayOfWeek = firstDay.getDay();      const offset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;      startDate.setDate(startDate.getDate() - offset);      const days = [];      for (let i = 0; i < 42; i++) {        const date = new Date(startDate);        date.setDate(startDate.getDate() + i);        days.push(this.createDayElement(date));      }      calendar.innerHTML += days.join('');      const todayBtn = document.getElementById('todayBtn');      const today = new Date();      todayBtn.style.display = this.isSameDate(this.selectedDate, today) ? 'none' : 'block';    }
    getWeekdayElements() {      const weekdays = ['一''二''三''四''五''六''日'];      return weekdays.map(day => `<div class="weekday">${day}</div>`).join('');    }
    createDayElement(date) {      const today = new Date();      const isCurrentMonth = date.getMonth() === this.currentDate.getMonth();      const isToday = this.isSameDate(date, today);      const isSelected = this.isSameDate(date, this.selectedDate);      const hasTask = this.hasTasks(date);      const classes = ['day', !isCurrentMonth && 'other-month', isToday && 'today', isSelected && 'selected', hasTask && 'has-task'].filter(Boolean).join(' ');      return `<div class="${classes}">${date.getDate()}</div>`;    }
    selectDate(date) {      this.selectedDate = new Date(date);      this.renderCalendar();      this.renderTasks();    }
    renderTasks() {      const taskList = document.getElementById('taskList');      const tasksCount = document.getElementById('tasksCount');      const dateStr = this.formatDate(this.selectedDate);      const dayTasks = this.tasks.filter(task => task.date === dateStr);      tasksCount.textContent = dayTasks.length;      if (dayTasks.length === 0) {        taskList.innerHTML = '<div class="no-tasks">暂无安排</div>';        return;      }      dayTasks.sort((a, b) => a.time.localeCompare(b.time));      taskList.innerHTML = dayTasks.map(task => `<div class="task-item" data-task-id="${task.id}">          <button class="btn-round delete-btn">×</button>          <div class="task-time">${task.time}</div>          <div class="task-title">${task.title}</div>          ${task.description ? `<div class="task-desc">${task.description}</div>` : ''}        </div>`).join('');    }
    showForm() {      document.getElementById('overlay').style.display = 'block';      document.getElementById('form').style.display = 'block';    }
    hideForm() {      document.getElementById('overlay').style.display = 'none';      document.getElementById('form').style.display = 'none';      document.getElementById('taskForm').reset();    }
    addTask(e) {      e.preventDefault();      const title = document.getElementById('taskTitle').value;      const time = document.getElementById('taskTime').value;      const description = document.getElementById('taskDesc').value;      const newTask = {        idDate.now(),        title,        datethis.formatDate(this.selectedDate),        time,        description      };      this.tasks.push(newTask);      this.saveTasks();      this.hideForm();      this.renderCalendar();      this.renderTasks();    }
    deleteTask(taskId) {      if (confirm('确定要删除这个任务吗?')) {        this.tasks = this.tasks.filter(task => task.id !== taskId);        this.saveTasks();        this.renderCalendar();        this.renderTasks();      }    }
    formatDate(date) {      const year = date.getFullYear();      const month = String(date.getMonth() + 1).padStart(2'0');      const day = String(date.getDate()).padStart(2'0');      return `${year}-${month}-${day}`;    }
    isSameDate(date1, date2) {      return date1.getFullYear() === date2.getFullYear() &&        date1.getMonth() === date2.getMonth() &&        date1.getDate() === date2.getDate();    }
    hasTasks(date) {      const dateStr = this.formatDate(date);      return this.tasks.some(task => task.date === dateStr);    }
    saveTasks() {      localStorage.setItem('scheduleTasks'JSON.stringify(this.tasks));    }
    loadTasks() {      const saved = localStorage.getItem('scheduleTasks');      return saved ? JSON.parse(saved) : this.getDefaultTasks();    }
    getDefaultTasks() {      const today = new Date();      const tomorrow = new Date(today);      tomorrow.setDate(tomorrow.getDate() + 1);      return [        { id1title'晨会'datethis.formatDate(today), time'09:00'description'团队日常晨会' },        { id2title'项目评审'datethis.formatDate(today), time'14:00'description'Q4项目评审会议' },        { id3title'健身'datethis.formatDate(tomorrow), time'18:00'description'游泳一小时' }      ];    }  }  new ScheduleCard();</script></body></html>


阅读原文:https://mp.weixin.qq.com/s/eMdLiVG9iiPDt6Y12yBDbA


该文章在 2026/1/9 11:41:46 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved