【Html网页模板】日历活动网页设计(附源码)

在这里插入图片描述

专栏导读

  • 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手

  • 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注

  • 👍 该系列文章专栏:请点击——>Python办公自动化专栏求订阅

  • 🕷 此外还有爬虫专栏:请点击——>Python爬虫基础专栏求订阅

  • 📕 此外还有python基础专栏:请点击——>Python基础学习专栏求订阅

  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏

  • ❤️ 欢迎各位佬关注! ❤️

项目简介

  • 这是一个功能完整的日历计划表应用,采用纯前端技术栈开发,支持活动的增删改查,并具备智能的悬停提示功能。用户可以直观地查看每日活动安排,通过鼠标悬停快速预览活动详情。

功能特性

🗓️ 核心功能

  • 月份切换支持前后月份的快速切换浏览
  • 日期选择点击日期可选中并设置为活动日期
  • 活动管理完整的CRUD操作(创建、读取、更新、删除)
  • 今日高亮自动标识当前日期
  • 活动指示器有活动的日期显示红色圆点标记

✨ 特色功能

  • 悬停提示鼠标悬停在有活动的日期上时,显示活动详情提示框
  • 本地存储使用localStorage实现数据持久化
  • 响应式设计适配不同屏幕尺寸的设备
  • 优雅动画流畅的过渡效果和交互反馈

技术实现

技术栈

  • HTML5语义化标签构建页面结构
  • CSS3现代样式特性,包括Grid布局、动画效果
  • JavaScript ES6+面向对象编程,模块化代码组织

核心技术点

1. 日历渲染算法
renderCalendar() {
    const year = this.currentDate.getFullYear();
    const month = this.currentDate.getMonth();
    
    // 获取月份第一天和最后一天
    const firstDay = new Date(year, month, 1);
    const lastDay = new Date(year, month + 1, 0);
    const startDate = new Date(firstDay);
    startDate.setDate(startDate.getDate() - firstDay.getDay());

    // 生成42天(6周)的完整日历视图
    for (let i = 0; i < 42; i++) {
        const date = new Date(startDate);
        date.setDate(startDate.getDate() + i);
        
        const dayElement = this.createDayElement(date, month);
        calendarDays.appendChild(dayElement);
    }
}
2. 悬停提示功能实现
addHoverTooltip(dayElement, events) {
    let tooltip = null;
    
    dayElement.addEventListener('mouseenter', (e) => {
        // 创建提示框
        tooltip = document.createElement('div');
        tooltip.className = 'event-tooltip';
        
        const tooltipContent = events.map(event => {
            return `<div class="tooltip-event">
                <div class="tooltip-title">${event.title}</div>
                <div class="tooltip-time">${event.time || '时间未设置'}</div>
            </div>`;
        }).join('');
        
        tooltip.innerHTML = tooltipContent;
        document.body.appendChild(tooltip);
        
        // 智能定位提示框
        const rect = dayElement.getBoundingClientRect();
        tooltip.style.left = rect.left + rect.width / 2 + 'px';
        tooltip.style.top = rect.top - 10 + 'px';
    });
    
    dayElement.addEventListener('mouseleave', () => {
        if (tooltip) {
            document.body.removeChild(tooltip);
            tooltip = null;
        }
    });
}
3. CSS动画与样式设计
.event-tooltip {
    position: absolute;
    background: rgba(0, 0, 0, 0.9);
    color: white;
    padding: 10px;
    border-radius: 8px;
    font-size: 12px;
    z-index: 1000;
    max-width: 200px;
    transform: translateX(-50%) translateY(-100%);
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    animation: tooltipFadeIn 0.2s ease;
}

@keyframes tooltipFadeIn {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(-100%) scale(0.9);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(-100%) scale(1);
    }
}

项目结构

日历计划表/
├── index.html          # 主页面结构
├── style.css           # 样式文件
├── script.js           # 核心逻辑

开发亮点

1. 面向对象设计

  • 采用ES6 Class语法,将日历功能封装在CalendarApp类中,代码结构清晰,易于维护和扩展。

2. 事件驱动架构

  • 通过事件监听器实现用户交互,包括点击、悬停、表单提交等操作的响应处理。

3. 数据持久化

  • 使用localStorage实现客户端数据存储,确保用户数据在浏览器关闭后仍能保持。

4. 响应式布局

  • 采用CSS Grid和Flexbox布局,配合媒体查询实现多设备适配。

5. 用户体验优化

  • 平滑的动画过渡效果
  • 直观的视觉反馈
  • 智能的提示框定位
  • 优雅的交互设计

使用方法

  1. 添加活动填写活动表单,点击"添加活动"按钮
  2. 查看活动点击有红点标记的日期查看详情,或将鼠标悬停快速预览
  3. 删除活动点击活动详情中的"删除活动"按钮
  4. 切换月份使用左右箭头按钮浏览不同月份

技术特色

智能提示框定位

提示框会根据日期元素的位置自动调整显示位置,确保在任何情况下都能完整显示内容。

优雅的动画效果

  • 日期悬停时的缩放效果
  • 提示框的淡入动画
  • 按钮的悬停反馈
  • 模态框的滑入效果

现代化UI设计

  • 渐变背景和阴影效果
  • 圆角设计语言
  • 合理的色彩搭配
  • 清晰的信息层级

浏览器兼容性

  • Chrome 60+
  • Firefox 55+
  • Safari 12+
  • Edge 79+

未来扩展

  • 添加活动分类和颜色标记
  • 支持重复活动设置
  • 集成提醒功能
  • 数据导入导出
  • 多语言支持

总结

  • 这个日历计划表项目展示了现代前端开发的最佳实践,通过合理的架构设计、优雅的用户界面和流畅的交互体验,为用户提供了一个实用且美观的日程管理工具。悬停提示功能的加入,进一步提升了用户体验,使得活动信息的查看更加便捷高效。

技术栈:HTML5 + CSS3 + JavaScript ES6+

源码

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>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>我的日历计划表</h1>
        </header>
        
        <div class="calendar-controls">
            <button id="prevMonth">&lt;</button>
            <h2 id="currentMonth"></h2>
            <button id="nextMonth">&gt;</button>
        </div>
        
        <div class="calendar-grid">
            <div class="weekdays">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
            <div id="calendarDays" class="days"></div>
        </div>
        
        <div class="event-section">
            <h3>添加活动</h3>
            <form id="eventForm">
                <input type="date" id="eventDate" required>
                <input type="text" id="eventTitle" placeholder="活动标题" required>
                <input type="time" id="eventTime">
                <textarea id="eventDescription" placeholder="活动描述"></textarea>
                <button type="submit">添加活动</button>
            </form>
        </div>
        
        <div class="events-list">
            <h3>今日活动</h3>
            <div id="todayEvents"></div>
        </div>
    </div>
    
    <!-- 活动详情模态框 -->
    <div id="eventModal" class="modal">
        <div class="modal-content">
            <span class="close">&times;</span>
            <h3>活动详情</h3>
            <div id="eventDetails"></div>
            <button id="deleteEvent">删除活动</button>
        </div>
    </div>
    
    <script src="script.js"></script>
</body>
</html>

script.js

class CalendarApp {
    constructor() {
        this.currentDate = new Date();
        this.selectedDate = null;
        this.events = JSON.parse(localStorage.getItem('calendarEvents')) || {};
        this.init();
    }

    init() {
        this.bindEvents();
        this.renderCalendar();
        this.updateTodayEvents();
    }

    bindEvents() {
        // 月份切换
        document.getElementById('prevMonth').addEventListener('click', () => {
            this.currentDate.setMonth(this.currentDate.getMonth() - 1);
            this.renderCalendar();
        });

        document.getElementById('nextMonth').addEventListener('click', () => {
            this.currentDate.setMonth(this.currentDate.getMonth() + 1);
            this.renderCalendar();
        });

        // 活动表单提交
        document.getElementById('eventForm').addEventListener('submit', (e) => {
            e.preventDefault();
            this.addEvent();
        });

        // 模态框关闭
        document.querySelector('.close').addEventListener('click', () => {
            this.closeModal();
        });

        document.getElementById('eventModal').addEventListener('click', (e) => {
            if (e.target === document.getElementById('eventModal')) {
                this.closeModal();
            }
        });

        // 删除活动
        document.getElementById('deleteEvent').addEventListener('click', () => {
            this.deleteCurrentEvent();
        });
    }

    renderCalendar() {
        const year = this.currentDate.getFullYear();
        const month = this.currentDate.getMonth();
        
        // 更新月份标题
        const monthNames = [
            '一月', '二月', '三月', '四月', '五月', '六月',
            '七月', '八月', '九月', '十月', '十一月', '十二月'
        ];
        document.getElementById('currentMonth').textContent = `${year}${monthNames[month]}`;

        // 获取月份第一天和最后一天
        const firstDay = new Date(year, month, 1);
        const lastDay = new Date(year, month + 1, 0);
        const startDate = new Date(firstDay);
        startDate.setDate(startDate.getDate() - firstDay.getDay());

        // 清空日历
        const calendarDays = document.getElementById('calendarDays');
        calendarDays.innerHTML = '';

        // 生成42天(6周)
        for (let i = 0; i < 42; i++) {
            const date = new Date(startDate);
            date.setDate(startDate.getDate() + i);
            
            const dayElement = this.createDayElement(date, month);
            calendarDays.appendChild(dayElement);
        }
    }

    createDayElement(date, currentMonth) {
        const dayDiv = document.createElement('div');
        dayDiv.className = 'day';
        
        const dayNumber = document.createElement('div');
        dayNumber.className = 'day-number';
        dayNumber.textContent = date.getDate();
        dayDiv.appendChild(dayNumber);

        // 添加样式类
        if (date.getMonth() !== currentMonth) {
            dayDiv.classList.add('other-month');
        }

        if (this.isToday(date)) {
            dayDiv.classList.add('today');
        }

        // 检查是否有活动
        const dateKey = this.formatDate(date);
        if (this.events[dateKey] && this.events[dateKey].length > 0) {
            const indicator = document.createElement('div');
            indicator.className = 'event-indicator';
            dayDiv.appendChild(indicator);
            
            // 添加悬停显示活动详情功能
            this.addHoverTooltip(dayDiv, this.events[dateKey]);
        }

        // 点击事件
        dayDiv.addEventListener('click', () => {
            this.selectDate(date);
        });

        return dayDiv;
    }

    addHoverTooltip(dayElement, events) {
        let tooltip = null;
        
        dayElement.addEventListener('mouseenter', (e) => {
            // 创建提示框
            tooltip = document.createElement('div');
            tooltip.className = 'event-tooltip';
            
            const tooltipContent = events.map(event => {
                return `<div class="tooltip-event">
                    <div class="tooltip-title">${event.title}</div>
                    <div class="tooltip-time">${event.time || '时间未设置'}</div>
                </div>`;
            }).join('');
            
            tooltip.innerHTML = tooltipContent;
            document.body.appendChild(tooltip);
            
            // 定位提示框
            const rect = dayElement.getBoundingClientRect();
            tooltip.style.left = rect.left + rect.width / 2 + 'px';
            tooltip.style.top = rect.top - 10 + 'px';
        });
        
        dayElement.addEventListener('mouseleave', () => {
            if (tooltip) {
                document.body.removeChild(tooltip);
                tooltip = null;
            }
        });
    }

    selectDate(date) {
        // 移除之前的选中状态
        document.querySelectorAll('.day.selected').forEach(day => {
            day.classList.remove('selected');
        });

        // 添加选中状态
        event.target.closest('.day').classList.add('selected');
        this.selectedDate = new Date(date);
        
        // 更新表单日期
        document.getElementById('eventDate').value = this.formatDate(date);
        
        // 显示该日期的活动
        this.showDateEvents(date);
    }

    showDateEvents(date) {
        const dateKey = this.formatDate(date);
        const events = this.events[dateKey] || [];
        
        if (events.length > 0) {
            this.showEventModal(events[0], dateKey, 0);
        }
    }

    addEvent() {
        const date = document.getElementById('eventDate').value;
        const title = document.getElementById('eventTitle').value;
        const time = document.getElementById('eventTime').value;
        const description = document.getElementById('eventDescription').value;

        if (!date || !title) {
            alert('请填写日期和活动标题!');
            return;
        }

        const event = {
            id: Date.now(),
            title,
            time,
            description,
            date
        };

        if (!this.events[date]) {
            this.events[date] = [];
        }
        
        this.events[date].push(event);
        this.saveEvents();
        this.renderCalendar();
        this.updateTodayEvents();
        
        // 清空表单
        document.getElementById('eventForm').reset();
        
        alert('活动添加成功!');
    }

    showEventModal(event, dateKey, eventIndex) {
        const modal = document.getElementById('eventModal');
        const details = document.getElementById('eventDetails');
        
        details.innerHTML = `
            <h4>${event.title}</h4>
            <p><strong>日期:</strong>${event.date}</p>
            <p><strong>时间:</strong>${event.time || '未设置'}</p>
            <p><strong>描述:</strong>${event.description || '无描述'}</p>
        `;
        
        // 存储当前活动信息用于删除
        modal.dataset.dateKey = dateKey;
        modal.dataset.eventIndex = eventIndex;
        
        modal.style.display = 'block';
    }

    closeModal() {
        document.getElementById('eventModal').style.display = 'none';
    }

    deleteCurrentEvent() {
        const modal = document.getElementById('eventModal');
        const dateKey = modal.dataset.dateKey;
        const eventIndex = parseInt(modal.dataset.eventIndex);
        
        if (dateKey && this.events[dateKey]) {
            this.events[dateKey].splice(eventIndex, 1);
            
            // 如果该日期没有活动了,删除该日期键
            if (this.events[dateKey].length === 0) {
                delete this.events[dateKey];
            }
            
            this.saveEvents();
            this.renderCalendar();
            this.updateTodayEvents();
            this.closeModal();
            
            alert('活动已删除!');
        }
    }

    updateTodayEvents() {
        const today = this.formatDate(new Date());
        const todayEvents = this.events[today] || [];
        const container = document.getElementById('todayEvents');
        
        if (todayEvents.length === 0) {
            container.innerHTML = '<p style="color: #6c757d;">今天没有安排活动</p>';
            return;
        }
        
        container.innerHTML = todayEvents.map((event, index) => `
            <div class="event-item" onclick="app.showEventModal(${JSON.stringify(event).replace(/"/g, '&quot;')}, '${today}', ${index})">
                <div class="event-title">${event.title}</div>
                <div class="event-time">${event.time || '时间未设置'}</div>
            </div>
        `).join('');
    }

    saveEvents() {
        localStorage.setItem('calendarEvents', JSON.stringify(this.events));
    }

    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}`;
    }

    isToday(date) {
        const today = new Date();
        return date.getDate() === today.getDate() &&
               date.getMonth() === today.getMonth() &&
               date.getFullYear() === today.getFullYear();
    }
}

// 初始化应用
const app = new CalendarApp();

// 设置今天的日期为默认值
document.getElementById('eventDate').value = app.formatDate(new Date());

style.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Arial', sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    padding: 20px;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    background: white;
    border-radius: 15px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
    overflow: hidden;
}

header {
    background: linear-gradient(45deg, #4CAF50, #45a049);
    color: white;
    text-align: center;
    padding: 20px;
}

header h1 {
    font-size: 2.5em;
    font-weight: 300;
}

.calendar-controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 40px;
    background: #f8f9fa;
    border-bottom: 1px solid #e9ecef;
}

.calendar-controls button {
    background: #007bff;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 25px;
    cursor: pointer;
    font-size: 18px;
    transition: all 0.3s ease;
}

.calendar-controls button:hover {
    background: #0056b3;
    transform: translateY(-2px);
}

#currentMonth {
    font-size: 1.8em;
    color: #333;
    font-weight: 400;
}

.calendar-grid {
    padding: 20px;
}

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

.weekdays div {
    background: #6c757d;
    color: white;
    text-align: center;
    padding: 15px;
    font-weight: bold;
    font-size: 14px;
}

.days {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 1px;
    background: #e9ecef;
}

.day {
    background: white;
    min-height: 100px;
    padding: 10px;
    border: 1px solid #e9ecef;
    cursor: pointer;
    transition: all 0.3s ease;
    position: relative;
}

.day:hover {
    background: #f8f9fa;
    transform: scale(1.02);
}

.day.other-month {
    background: #f8f9fa;
    color: #6c757d;
}

.day.today {
    background: #007bff;
    color: white;
    font-weight: bold;
}

.day.selected {
    background: #28a745;
    color: white;
}

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

.event-indicator {
    width: 8px;
    height: 8px;
    background: #dc3545;
    border-radius: 50%;
    position: absolute;
    top: 5px;
    right: 5px;
}

.event-section {
    background: #f8f9fa;
    padding: 30px;
    border-top: 1px solid #e9ecef;
}

.event-section h3 {
    margin-bottom: 20px;
    color: #333;
    font-size: 1.5em;
}

#eventForm {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    gap: 15px;
    align-items: start;
}

#eventForm input,
#eventForm textarea {
    padding: 12px;
    border: 2px solid #e9ecef;
    border-radius: 8px;
    font-size: 14px;
    transition: border-color 0.3s ease;
}

#eventForm input:focus,
#eventForm textarea:focus {
    outline: none;
    border-color: #007bff;
}

#eventDescription {
    grid-column: span 3;
    resize: vertical;
    min-height: 80px;
}

#eventForm button {
    grid-column: span 3;
    background: #28a745;
    color: white;
    border: none;
    padding: 15px;
    border-radius: 8px;
    font-size: 16px;
    cursor: pointer;
    transition: all 0.3s ease;
}

#eventForm button:hover {
    background: #218838;
    transform: translateY(-2px);
}

.events-list {
    padding: 30px;
    border-top: 1px solid #e9ecef;
}

.events-list h3 {
    margin-bottom: 20px;
    color: #333;
    font-size: 1.5em;
}

.event-item {
    background: white;
    border: 1px solid #e9ecef;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 10px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.event-item:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    transform: translateY(-2px);
}

.event-title {
    font-weight: bold;
    color: #333;
    margin-bottom: 5px;
}

.event-time {
    color: #6c757d;
    font-size: 14px;
}

/* 模态框样式 */
.modal {
    display: none;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
}

.modal-content {
    background-color: white;
    margin: 15% auto;
    padding: 30px;
    border-radius: 15px;
    width: 80%;
    max-width: 500px;
    position: relative;
    animation: modalSlideIn 0.3s ease;
}

@keyframes modalSlideIn {
    from {
        transform: translateY(-50px);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

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

.close:hover {
    color: #000;
}

#deleteEvent {
    background: #dc3545;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 15px;
    transition: background 0.3s ease;
}

#deleteEvent:hover {
    background: #c82333;
}

/* 悬停提示框样式 */
.event-tooltip {
    position: absolute;
    background: rgba(0, 0, 0, 0.9);
    color: white;
    padding: 10px;
    border-radius: 8px;
    font-size: 12px;
    z-index: 1000;
    max-width: 200px;
    transform: translateX(-50%) translateY(-100%);
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    animation: tooltipFadeIn 0.2s ease;
}

.event-tooltip::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent;
    border-top-color: rgba(0, 0, 0, 0.9);
}

@keyframes tooltipFadeIn {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(-100%) scale(0.9);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(-100%) scale(1);
    }
}

.tooltip-event {
    margin-bottom: 8px;
}

.tooltip-event:last-child {
    margin-bottom: 0;
}

.tooltip-title {
    font-weight: bold;
    margin-bottom: 2px;
}

.tooltip-time {
    font-size: 11px;
    color: #ccc;
}

/* 响应式设计 */
@media (max-width: 768px) {
    .container {
        margin: 10px;
        border-radius: 10px;
    }
    
    .calendar-controls {
        padding: 15px 20px;
    }
    
    #currentMonth {
        font-size: 1.4em;
    }
    
    .day {
        min-height: 80px;
        padding: 5px;
    }
    
    #eventForm {
        grid-template-columns: 1fr;
    }
    
    #eventDescription {
        grid-column: span 1;
    }
    
    #eventForm button {
        grid-column: span 1;
    }
    
    .modal-content {
        width: 95%;
        margin: 10% auto;
    }
}
  • 希望对初学者有帮助;致力于办公自动化的小小程序员一枚

  • 希望能得到大家的【❤️一个免费关注❤️】感谢!

  • 求个 🤞 关注 🤞 +❤️ 喜欢 ❤️ +👍 收藏 👍

  • 此外还有办公自动化专栏,欢迎大家订阅:Python办公自动化专栏

  • 此外还有爬虫专栏,欢迎大家订阅:Python爬虫基础专栏

  • 此外还有Python基础专栏,欢迎大家订阅:Python基础学习专栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小庄-Python办公

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值