使用 HTML + JavaScript 实现一个日历任务管理系统

发布于:2025-05-31 ⋅ 阅读:(21) ⋅ 点赞:(0)

在现代快节奏的生活中,有效的时间管理变得越来越重要。本项目是一个基于 HTML 和 JavaScript 开发的日历任务管理系统,旨在为用户提供一个直观、便捷的时间管理工具。系统不仅能够清晰地展示当月日期,还支持事件的添加、编辑和删除操作,并通过本地存储(localStorage)实现数据的保存。

效果演示

image-20250531215620228

image-20250531215651307

项目概述

本项目主要包含以下核心功能:

  • 日历展示:动态生成当月日期,并支持月份切换
  • 事件管理:支持添加、编辑、删除事件
  • 类型区分:支持三种类型事件

页面结构

HTML 部分定义了页面的主要布局,包含日历头部、日历主体、模态框三大部分,其中日历日期将通过JavaScript动态生成。

<div class="calendar-container">
    <div class="calendar-header">
        <h1 id="current-month"></h1>
        <div class="calendar-nav">
            <button id="prev-month">上个月</button>
            <button id="next-month">下个月</button>
            <button id="add-event">添加事件</button>
        </div>
    </div>
    <div class="calendar-grid" id="calendar-grid">
        <div class="calendar-day-header">周日</div>
        <div class="calendar-day-header">周一</div>
        <div class="calendar-day-header">周二</div>
        <div class="calendar-day-header">周三</div>
        <div class="calendar-day-header">周四</div>
        <div class="calendar-day-header">周五</div>
        <div class="calendar-day-header">周六</div>
        <!-- 日历日期将通过JavaScript动态生成 -->
    </div>
</div>
<div id="event-modal" class="modal">
    <div class="modal-content">
        <span class="close">&times;</span>
        <h2 id="modal-title">添加新事件</h2>
        <form id="event-form">
            <input type="hidden" id="event-id">
            <input type="hidden" id="event-date">

            <div class="form-group">
                <label for="event-title">标题</label>
                <input type="text" id="event-title" required>
            </div>
            <div class="form-group">
                <label for="event-type">类型</label>
                <select id="event-type">
                    <option value="event">事件</option>
                    <option value="task">任务</option>
                    <option value="important">重要</option>
                </select>
            </div>
            <div class="form-group">
                <label for="event-start">开始时间</label>
                <input type="time" id="event-start">
            </div>
            <div class="form-group">
                <label for="event-end">结束时间</label>
                <input type="time" id="event-end">
            </div>
            <div class="form-group">
                <label for="event-description">描述</label>
                <textarea id="event-description" rows="3"></textarea>
            </div>
            <div class="form-actions">
                <button type="button" id="delete-event" style="display: none; background: #f44336; color: white; border: none;">删除</button>
                <button type="button" id="cancel-event">取消</button>
                <button type="submit" id="save-event" style="background: #4CAF50; color: white; border: none;">保存</button>
            </div>
        </form>
    </div>
</div>

核心功能实现

定义基础数据

获取页面核心 DOM 元素,为后续操作做准备

const calendarGrid = document.getElementById('calendar-grid');
const currentMonthElement = document.getElementById('current-month');
const prevMonthButton = document.getElementById('prev-month');
const nextMonthButton = document.getElementById('next-month');
const addEventButton = document.getElementById('add-event');
const modal = document.getElementById('event-modal');
const closeButton = document.querySelector('.close');
const cancelButton = document.getElementById('cancel-event');
const deleteButton = document.getElementById('delete-event');
const eventForm = document.getElementById('event-form');

初始化当前日期信息

let currentDate = new Date();
let currentYear = currentDate.getFullYear();
let currentMonth = currentDate.getMonth();

读取或初始化事件数据

let events = JSON.parse(localStorage.getItem('calendarEvents')) || [];
渲染日历

日历渲染算法流程如下:

  1. 计算当月第一天和最后一天的日期对象

  2. 确定当月第一天是星期几,用于定位起始位置

  3. 填充上个月末尾的日期(灰色显示)

  4. 循环生成当月的所有日期单元格

  5. 计算是否需要补充下个月初的日期

  6. 为今天添加特殊样式

  7. 最后调用 renderEvents 方法渲染当月所有事件

function renderCalendar(month, year) {
    // 更新当前月份显示
    currentMonthElement.textContent = `${year}${month + 1}`;
    // 清除之前的日历日期
    while (calendarGrid.children.length > 7) {
        calendarGrid.removeChild(calendarGrid.lastChild);
    }
    // 获取当月第一天和最后一天
    const firstDay = new Date(year, month, 1);
    const lastDay = new Date(year, month + 1, 0);
    // 获取第一天是星期几 (0-6, 0是周日)
    const firstDayOfWeek = firstDay.getDay();
    // 获取上个月的最后几天
    const prevMonthLastDay = new Date(year, month, 0).getDate();
    // 添加上个月的几天
    for (let i = firstDayOfWeek - 1; i >= 0; i--) {
        const dayElement = createDayElement(prevMonthLastDay - i, true);
        calendarGrid.appendChild(dayElement);
    }
    // 添加当月的所有天
    for (let i = 1; i <= lastDay.getDate(); i++) {
        const dayElement = createDayElement(i, false);
        // 检查是否是今天
        const today = new Date();
        if (year === today.getFullYear() && month === today.getMonth() && i === today.getDate()) {
            dayElement.style.backgroundColor = '#e8f5e9';
        }
        calendarGrid.appendChild(dayElement);
    }
    // 计算还需要添加多少天
    const totalDays = firstDayOfWeek + lastDay.getDate();
    const remainingDays = 7 - (totalDays % 7);
    // 添加下个月的前几天
    if (remainingDays < 7) {
        for (let i = 1; i <= remainingDays; i++) {
            const dayElement = createDayElement(i, true);
            calendarGrid.appendChild(dayElement);
        }
    }
    // 渲染事件
    renderEvents();
}
渲染事件

每个日期单元格都可能包含多个事件标记。

function renderEvents() {
    // 获取所有日期元素
    const dayElements = document.querySelectorAll('.calendar-day:not(.inactive)');
    dayElements.forEach(dayElement => {
        // 清除之前的事件
        const existingEvents = dayElement.querySelectorAll('.event');
        existingEvents.forEach(event => event.remove());
        // 获取当前日期
        const day = parseInt(dayElement.querySelector('.day-number').textContent);
        const date = new Date(currentYear, currentMonth, day);
        // 查找当天的所有事件
        const dayEvents = events.filter(event => {
            const eventDate = new Date(event.date);
            return eventDate.toDateString() === date.toDateString();
        });
        // 添加事件到日期
        dayEvents.forEach(event => {
            const eventElement = document.createElement('div');
            eventElement.className = `event ${event.type}`;
            eventElement.textContent = `${event.startTime} ${event.title}`;
            eventElement.dataset.id = event.id;
            // 添加点击事件
            eventElement.addEventListener('click', (e) => {
                e.stopPropagation();
                openModal(null, event.id);
            });
            dayElement.appendChild(eventElement);
        });
    });
}
新增/编辑事件

打开表单的模态框,点击空白日期时打开添加事件模态框,点击已有事件时打开编辑该事件的模态框。

function openModal(date = null, eventId = null) {
    const modalTitle = document.getElementById('modal-title');
    const eventDateInput = document.getElementById('event-date');
    const eventIdInput = document.getElementById('event-id');
    const eventTitleInput = document.getElementById('event-title');
    const eventTypeInput = document.getElementById('event-type');
    const eventStartInput = document.getElementById('event-start');
    const eventEndInput = document.getElementById('event-end');
    const eventDescriptionInput = document.getElementById('event-description');
    // 重置表单
    eventForm.reset();
    if (eventId) {
        // 编辑现有事件
        const event = events.find(e => e.id === eventId);
        if (event) {
            modalTitle.textContent = '编辑事件';
            eventIdInput.value = event.id;
            eventDateInput.value = event.date;
            eventTitleInput.value = event.title;
            eventTypeInput.value = event.type;
            eventStartInput.value = event.startTime || '';
            eventEndInput.value = event.endTime || '';
            eventDescriptionInput.value = event.description || '';
            deleteButton.style.display = 'inline-block';
        }
    } else {
        // 添加新事件
        modalTitle.textContent = '添加新事件';
        eventIdInput.value = generateId();
        if (date) {
            // 设置日期为点击的日期
            const formattedDate = formatDate(date);
            eventDateInput.value = formattedDate;
        } else {
            // 默认为今天
            const today = new Date();
            const formattedDate = formatDate(today);
            eventDateInput.value = formattedDate;
        }
        deleteButton.style.display = 'none';
    }
    modal.style.display = 'block';
}

事件验证后把事件保存到本地,并重新渲染日历。

function saveEvent() {
    const eventId = document.getElementById('event-id').value;
    const eventDate = document.getElementById('event-date').value;
    const eventTitle = document.getElementById('event-title').value;
    const eventType = document.getElementById('event-type').value;
    const eventStart = document.getElementById('event-start').value;
    const eventEnd = document.getElementById('event-end').value;
    const eventDescription = document.getElementById('event-description').value;
    // 验证
    if (!eventTitle) {
        alert('请输入标题');
        return;
    }
    // 查找是否已存在该事件
    const existingEventIndex = events.findIndex(e => e.id === eventId);
    const event = {
        id: eventId,
        date: eventDate,
        title: eventTitle,
        type: eventType,
        startTime: eventStart,
        endTime: eventEnd,
        description: eventDescription
    };
    if (existingEventIndex !== -1) {
        // 更新现有事件
        events[existingEventIndex] = event;
    } else {
        // 添加新事件
        events.push(event);
    }
    // 保存到本地存储
    localStorage.setItem('calendarEvents', JSON.stringify(events));
    // 重新渲染日历
    renderCalendar(currentMonth, currentYear);
    // 关闭模态框
    closeModal();
}
删除事件
function deleteEvent() {
    const eventId = document.getElementById('event-id').value;
    if (confirm('确定要删除这个事件吗?')) {
        // 从数组中移除事件
        events = events.filter(e => e.id !== eventId);
        // 保存到本地存储
        localStorage.setItem('calendarEvents', JSON.stringify(events));
        // 重新渲染日历
        renderCalendar(currentMonth, currentYear);
        // 关闭模态框
        closeModal();
    }
}
月份切换

点击【上个月】【下个月】按钮切换月份,系统会自动更新日历显示。

prevMonthButton.addEventListener('click', () => {
    currentMonth--;
    if (currentMonth < 0) {
        currentMonth = 11;
        currentYear--;
    }
    renderCalendar(currentMonth, currentYear);
});

nextMonthButton.addEventListener('click', () => {
    currentMonth++;
    if (currentMonth > 11) {
        currentMonth = 0;
        currentYear++;
    }
    renderCalendar(currentMonth, currentYear);
});

扩展建议

  • 增加事件提醒功能
  • 支持“日”、“周”、“月”视图切换功能
  • 添加拖拽排序功能
  • 添加右键菜单快速操作功能

完整代码

<!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>
        body {
            margin: 0;
            padding: 20px;
        }

        .calendar-container {
            max-width: 1000px;
            margin: 0 auto;
        }

        .calendar-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }

        .calendar-nav button {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
        }

        .calendar-nav button:hover {
            background: #45a049;
        }

        .calendar-grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 10px;
        }

        .calendar-day-header {
            text-align: center;
            font-weight: bold;
            padding: 10px;
            background: #f2f2f2;
        }

        .calendar-day {
            border: 1px solid #ddd;
            min-height: 100px;
            padding: 5px;
            position: relative;
        }

        .calendar-day.inactive {
            background: #f9f9f9;
            color: #aaa;
        }

        .day-number {
            font-weight: bold;
            margin-bottom: 5px;
        }

        .event {
            background: #e3f2fd;
            border-left: 3px solid #2196F3;
            padding: 2px 5px;
            margin: 2px 0;
            font-size: 12px;
            border-radius: 2px;
            cursor: pointer;
        }

        .event.task {
            background: #e8f5e9;
            border-left-color: #4CAF50;
        }

        .event.important {
            background: #ffebee;
            border-left-color: #f44336;
        }

        .modal {
            display: none;
            position: fixed;
            z-index: 1;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.4);
        }

        .modal-content {
            background-color: #fefefe;
            margin: 10% auto;
            padding: 20px;
            border: 1px solid #888;
            width: 80%;
            max-width: 500px;
        }

        .close {
            color: #aaa;
            float: right;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
        }

        .form-group {
            margin-bottom: 15px;
        }

        .form-group label {
            display: block;
            margin-bottom: 5px;
        }

        .form-group input, .form-group select, .form-group textarea {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
        }

        .form-actions {
            text-align: right;
        }

        .form-actions button {
            padding: 8px 16px;
            margin-left: 10px;
        }
    </style>
</head>
<body>
<div class="calendar-container">
    <div class="calendar-header">
        <h1 id="current-month"></h1>
        <div class="calendar-nav">
            <button id="prev-month">上个月</button>
            <button id="next-month">下个月</button>
            <button id="add-event">添加事件</button>
        </div>
    </div>
    <div class="calendar-grid" id="calendar-grid">
        <!-- 日历头部 - 星期几 -->
        <div class="calendar-day-header">周日</div>
        <div class="calendar-day-header">周一</div>
        <div class="calendar-day-header">周二</div>
        <div class="calendar-day-header">周三</div>
        <div class="calendar-day-header">周四</div>
        <div class="calendar-day-header">周五</div>
        <div class="calendar-day-header">周六</div>
        <!-- 日历日期将通过JavaScript动态生成 -->
    </div>
</div>
<!-- 添加/编辑事件的模态框 -->
<div id="event-modal" class="modal">
    <div class="modal-content">
        <span class="close">&times;</span>
        <h2 id="modal-title">添加新事件</h2>
        <form id="event-form">
            <input type="hidden" id="event-id">
            <input type="hidden" id="event-date">
            <div class="form-group">
                <label for="event-title">标题</label>
                <input type="text" id="event-title" required>
            </div>
            <div class="form-group">
                <label for="event-type">类型</label>
                <select id="event-type">
                    <option value="event">事件</option>
                    <option value="task">任务</option>
                    <option value="important">重要</option>
                </select>
            </div>
            <div class="form-group">
                <label for="event-start">开始时间</label>
                <input type="time" id="event-start">
            </div>
            <div class="form-group">
                <label for="event-end">结束时间</label>
                <input type="time" id="event-end">
            </div>
            <div class="form-group">
                <label for="event-description">描述</label>
                <textarea id="event-description" rows="3"></textarea>
            </div>
            <div class="form-actions">
                <button type="button" id="delete-event" style="display: none; background: #f44336; color: white; border: none;">删除</button>
                <button type="button" id="cancel-event">取消</button>
                <button type="submit" id="save-event" style="background: #4CAF50; color: white; border: none;">保存</button>
            </div>
        </form>
    </div>
</div>
<script>
    document.addEventListener('DOMContentLoaded', function() {
        const calendarGrid = document.getElementById('calendar-grid');
        const currentMonthElement = document.getElementById('current-month');
        const prevMonthButton = document.getElementById('prev-month');
        const nextMonthButton = document.getElementById('next-month');
        const addEventButton = document.getElementById('add-event');
        const modal = document.getElementById('event-modal');
        const closeButton = document.querySelector('.close');
        const cancelButton = document.getElementById('cancel-event');
        const deleteButton = document.getElementById('delete-event');
        const eventForm = document.getElementById('event-form');

        let currentDate = new Date();
        let currentYear = currentDate.getFullYear();
        let currentMonth = currentDate.getMonth();

        let events = JSON.parse(localStorage.getItem('calendarEvents')) || [];

        // 初始化日历
        renderCalendar(currentMonth, currentYear);

        // 月份切换
        prevMonthButton.addEventListener('click', () => {
            currentMonth--;
            if (currentMonth < 0) {
                currentMonth = 11;
                currentYear--;
            }
            renderCalendar(currentMonth, currentYear);
        });

        nextMonthButton.addEventListener('click', () => {
            currentMonth++;
            if (currentMonth > 11) {
                currentMonth = 0;
                currentYear++;
            }
            renderCalendar(currentMonth, currentYear);
        });

        addEventButton.addEventListener('click', () => {
            openModal();
        });

        closeButton.addEventListener('click', () => {
            closeModal();
        });

        cancelButton.addEventListener('click', () => {
            closeModal();
        });

        window.addEventListener('click', (event) => {
            if (event.target === modal) {
                closeModal();
            }
        });

        eventForm.addEventListener('submit', (e) => {
            e.preventDefault();
            saveEvent();
        });

        deleteButton.addEventListener('click', () => {
            deleteEvent();
        });

        // 渲染日历
        function renderCalendar(month, year) {
            // 更新当前月份显示
            currentMonthElement.textContent = `${year}${month + 1}`;
            // 清除之前的日历日期
            while (calendarGrid.children.length > 7) {
                calendarGrid.removeChild(calendarGrid.lastChild);
            }
            // 获取当月第一天和最后一天
            const firstDay = new Date(year, month, 1);
            const lastDay = new Date(year, month + 1, 0);
            // 获取第一天是星期几 (0-6, 0是周日)
            const firstDayOfWeek = firstDay.getDay();
            // 获取上个月的最后几天
            const prevMonthLastDay = new Date(year, month, 0).getDate();
            // 添加上个月的几天
            for (let i = firstDayOfWeek - 1; i >= 0; i--) {
                const dayElement = createDayElement(prevMonthLastDay - i, true);
                calendarGrid.appendChild(dayElement);
            }
            // 添加当月的所有天
            for (let i = 1; i <= lastDay.getDate(); i++) {
                const dayElement = createDayElement(i, false);
                // 检查是否是今天
                const today = new Date();
                if (year === today.getFullYear() && month === today.getMonth() && i === today.getDate()) {
                    dayElement.style.backgroundColor = '#e8f5e9';
                }
                calendarGrid.appendChild(dayElement);
            }
            // 计算还需要添加多少天
            const totalDays = firstDayOfWeek + lastDay.getDate();
            const remainingDays = 7 - (totalDays % 7);
            // 添加下个月的前几天
            if (remainingDays < 7) {
                for (let i = 1; i <= remainingDays; i++) {
                    const dayElement = createDayElement(i, true);
                    calendarGrid.appendChild(dayElement);
                }
            }
            // 渲染事件
            renderEvents();
        }
        // 创建日期元素
        function createDayElement(day, inactive) {
            const dayElement = document.createElement('div');
            dayElement.className = 'calendar-day' + (inactive ? ' inactive' : '');
            const dayNumber = document.createElement('div');
            dayNumber.className = 'day-number';
            dayNumber.textContent = day;
            dayElement.appendChild(dayNumber);
            // 如果不是非活动日期,添加点击事件
            if (!inactive) {
                dayElement.addEventListener('click', () => {
                    const currentDate = new Date(currentYear, currentMonth, day);
                    openModal(currentDate);
                });
            }
            return dayElement;
        }
        // 渲染事件到日历
        function renderEvents() {
            // 获取所有日期元素
            const dayElements = document.querySelectorAll('.calendar-day:not(.inactive)');
            dayElements.forEach(dayElement => {
                // 清除之前的事件
                const existingEvents = dayElement.querySelectorAll('.event');
                existingEvents.forEach(event => event.remove());
                // 获取当前日期
                const day = parseInt(dayElement.querySelector('.day-number').textContent);
                const date = new Date(currentYear, currentMonth, day);
                // 查找当天的所有事件
                const dayEvents = events.filter(event => {
                    const eventDate = new Date(event.date);
                    return eventDate.toDateString() === date.toDateString();
                });
                // 添加事件到日期
                dayEvents.forEach(event => {
                    const eventElement = document.createElement('div');
                    eventElement.className = `event ${event.type}`;
                    eventElement.textContent = `${event.startTime} ${event.title}`;
                    eventElement.dataset.id = event.id;
                    // 添加点击事件
                    eventElement.addEventListener('click', (e) => {
                        e.stopPropagation();
                        openModal(null, event.id);
                    });
                    dayElement.appendChild(eventElement);
                });
            });
        }
        // 打开模态框
        function openModal(date = null, eventId = null) {
            const modalTitle = document.getElementById('modal-title');
            const eventDateInput = document.getElementById('event-date');
            const eventIdInput = document.getElementById('event-id');
            const eventTitleInput = document.getElementById('event-title');
            const eventTypeInput = document.getElementById('event-type');
            const eventStartInput = document.getElementById('event-start');
            const eventEndInput = document.getElementById('event-end');
            const eventDescriptionInput = document.getElementById('event-description');
            // 重置表单
            eventForm.reset();
            if (eventId) {
                // 编辑现有事件
                const event = events.find(e => e.id === eventId);
                if (event) {
                    modalTitle.textContent = '编辑事件';
                    eventIdInput.value = event.id;
                    eventDateInput.value = event.date;
                    eventTitleInput.value = event.title;
                    eventTypeInput.value = event.type;
                    eventStartInput.value = event.startTime || '';
                    eventEndInput.value = event.endTime || '';
                    eventDescriptionInput.value = event.description || '';
                    deleteButton.style.display = 'inline-block';
                }
            } else {
                // 添加新事件
                modalTitle.textContent = '添加新事件';
                eventIdInput.value = generateId();
                if (date) {
                    // 设置日期为点击的日期
                    const formattedDate = formatDate(date);
                    eventDateInput.value = formattedDate;
                } else {
                    // 默认为今天
                    const today = new Date();
                    const formattedDate = formatDate(today);
                    eventDateInput.value = formattedDate;
                }
                deleteButton.style.display = 'none';
            }
            modal.style.display = 'block';
        }
        // 关闭模态框
        function closeModal() {
            modal.style.display = 'none';
        }
        // 保存事件
        function saveEvent() {
            const eventId = document.getElementById('event-id').value;
            const eventDate = document.getElementById('event-date').value;
            const eventTitle = document.getElementById('event-title').value;
            const eventType = document.getElementById('event-type').value;
            const eventStart = document.getElementById('event-start').value;
            const eventEnd = document.getElementById('event-end').value;
            const eventDescription = document.getElementById('event-description').value;
            // 验证
            if (!eventTitle) {
                alert('请输入标题');
                return;
            }
            // 查找是否已存在该事件
            const existingEventIndex = events.findIndex(e => e.id === eventId);
            const event = {
                id: eventId,
                date: eventDate,
                title: eventTitle,
                type: eventType,
                startTime: eventStart,
                endTime: eventEnd,
                description: eventDescription
            };
            if (existingEventIndex !== -1) {
                // 更新现有事件
                events[existingEventIndex] = event;
            } else {
                // 添加新事件
                events.push(event);
            }
            // 保存到本地存储
            localStorage.setItem('calendarEvents', JSON.stringify(events));
            // 重新渲染日历
            renderCalendar(currentMonth, currentYear);
            // 关闭模态框
            closeModal();
        }
        // 删除事件
        function deleteEvent() {
            const eventId = document.getElementById('event-id').value;
            if (confirm('确定要删除这个事件吗?')) {
                // 从数组中移除事件
                events = events.filter(e => e.id !== eventId);
                // 保存到本地存储
                localStorage.setItem('calendarEvents', JSON.stringify(events));
                // 重新渲染日历
                renderCalendar(currentMonth, currentYear);
                // 关闭模态框
                closeModal();
            }
        }
        // 生成唯一ID
        function generateId() {
            return Date.now().toString(36) + Math.random().toString(36).substr(2);
        }
        // 格式化日期为YYYY-MM-DD
        function 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}`;
        }
    });
</script>
</body>
</html>