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


项目概述
本项目主要包含以下核心功能:
- 日历展示:动态生成当月日期,并支持月份切换
- 事件管理:支持添加、编辑、删除事件
- 类型区分:支持三种类型事件
页面结构
HTML 部分定义了页面的主要布局,包含日历头部、日历主体、模态框三大部分,其中日历日期将通过JavaScript动态生成。
html
<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">×</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 元素,为后续操作做准备
js
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');
初始化当前日期信息
js
let currentDate = new Date();
let currentYear = currentDate.getFullYear();
let currentMonth = currentDate.getMonth();
读取或初始化事件数据
js
let events = JSON.parse(localStorage.getItem('calendarEvents')) || [];
渲染日历
日历渲染算法流程如下:
-
计算当月第一天和最后一天的日期对象
-
确定当月第一天是星期几,用于定位起始位置
-
填充上个月末尾的日期(灰色显示)
-
循环生成当月的所有日期单元格
-
计算是否需要补充下个月初的日期
-
为今天添加特殊样式
-
最后调用 renderEvents 方法渲染当月所有事件
js
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();
}
渲染事件
每个日期单元格都可能包含多个事件标记。
js
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);
});
});
}
新增/编辑事件
打开表单的模态框,点击空白日期时打开添加事件模态框,点击已有事件时打开编辑该事件的模态框。
js
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';
}
事件验证后把事件保存到本地,并重新渲染日历。
js
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();
}
删除事件
js
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();
}
}
月份切换
点击【上个月】【下个月】按钮切换月份,系统会自动更新日历显示。
js
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);
});
扩展建议
- 增加事件提醒功能
- 支持"日"、"周"、"月"视图切换功能
- 添加拖拽排序功能
- 添加右键菜单快速操作功能
完整代码
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>
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">×</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>