日历案例

wen java案例 3
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">📅 日历案例 · 优雅月视图</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
        }
        body {
            background: linear-gradient(145deg, #f4f7fc 0%, #e9eef4 100%);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 1.5rem;
        }
        .calendar-card {
            background: rgba(255, 255, 255, 0.7);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border-radius: 2.5rem;
            box-shadow: 0 20px 40px rgba(0, 10, 30, 0.2), 0 8px 20px rgba(0, 0, 0, 0.06);
            width: 100%;
            max-width: 850px;
            padding: 2rem 1.8rem 2.2rem;
            border: 1px solid rgba(255,255,255,0.4);
            transition: 0.3s ease;
        }
        /* 头部区域 */
        .calendar-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 2rem;
            flex-wrap: wrap;
        }
        .month-year {
            display: flex;
            align-items: baseline;
            gap: 0.5rem;
        }
        .month-year .month {
            font-size: 2.4rem;
            font-weight: 600;
            color: #1f2b3c;
            letter-spacing: -0.5px;
        }
        .month-year .year {
            font-size: 1.6rem;
            font-weight: 400;
            color: #5b6b7f;
        }
        .nav-buttons {
            display: flex;
            gap: 0.6rem;
        }
        .nav-btn {
            background: white;
            border: none;
            width: 48px;
            height: 48px;
            border-radius: 20px;
            font-size: 1.6rem;
            font-weight: 300;
            color: #2a405c;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            box-shadow: 0 6px 12px rgba(0, 20, 40, 0.08);
            transition: all 0.2s;
            border: 1px solid rgba(255,255,255,0.7);
        }
        .nav-btn:hover {
            background: #ffffff;
            transform: translateY(-2px);
            box-shadow: 0 12px 18px rgba(0, 20, 40, 0.12);
            color: #0f2a44;
        }
        .nav-btn:active {
            transform: translateY(1px);
            box-shadow: 0 5px 10px rgba(0, 20, 40, 0.08);
        }
        /* 星期缩写行 */
        .weekdays {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            text-align: center;
            margin-bottom: 0.75rem;
            font-weight: 600;
            color: #4f6479;
            font-size: 0.8rem;
            text-transform: uppercase;
            letter-spacing: 0.06em;
            padding: 0 0.2rem;
        }
        .weekdays div {
            padding: 0.5rem 0;
        }
        /* 日期网格 */
        .calendar-grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 8px;
            margin-bottom: 1.5rem;
        }
        .day-cell {
            aspect-ratio: 1 / 1.05;
            background: rgba(255, 255, 255, 0.5);
            border-radius: 18px;
            padding: 0.3rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: flex-start;
            transition: all 0.15s;
            border: 1px solid rgba(255,255,255,0.3);
            cursor: pointer;
            position: relative;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
            backdrop-filter: blur(4px);
        }
        .day-cell:hover {
            background: rgba(255, 255, 255, 0.85);
            transform: scale(1.02);
            border-color: #9ab8da;
            box-shadow: 0 8px 18px rgba(0, 40, 80, 0.08);
        }
        .day-cell.empty {
            background: transparent;
            border: 1px solid transparent;
            box-shadow: none;
            pointer-events: none;
            backdrop-filter: none;
        }
        .date-number {
            font-size: 1.2rem;
            font-weight: 500;
            color: #20344c;
            width: 100%;
            text-align: left;
            padding-left: 0.3rem;
            padding-top: 0.2rem;
        }
        .event-dots {
            display: flex;
            flex-wrap: wrap;
            justify-content: flex-start;
            gap: 3px;
            padding: 0.2rem 0.3rem;
            margin-top: auto;
            width: 100%;
        }
        .event-dot {
            width: 7px;
            height: 7px;
            border-radius: 10px;
            background: #4f8bd9;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        }
        .event-dot.more {
            background: #a75fbf;
        }
        /* 选中日期高亮 */
        .selected {
            background: #d8e7ff;
            border: 2px solid #3c7bc9;
            box-shadow: 0 0 0 3px rgba(73, 138, 221, 0.2);
        }
        /* 今日标记 */
        .today .date-number {
            font-weight: 700;
            color: #0d59a0;
        }
        .today::after {
            content: '●';
            position: absolute;
            bottom: 6px;
            right: 8px;
            font-size: 9px;
            color: #2b7ecc;
        }
        /* 底部事件面板 */
        .event-panel {
            background: rgba(255, 255, 255, 0.6);
            border-radius: 2rem;
            padding: 1.2rem 1.5rem 1rem;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255,255,255,0.6);
            box-shadow: inset 0 1px 4px rgba(255,255,255,0.5);
        }
        .panel-title {
            display: flex;
            align-items: center;
            justify-content: space-between;
            color: #2c3e50;
            font-weight: 600;
            font-size: 1rem;
            letter-spacing: 0.02em;
            border-bottom: 2px dashed #cbd6e3;
            padding-bottom: 0.6rem;
            margin-bottom: 0.8rem;
        }
        .event-list {
            min-height: 56px;
            display: flex;
            flex-direction: column;
            gap: 6px;
        }
        .event-item {
            background: white;
            padding: 0.6rem 1rem;
            border-radius: 40px;
            font-size: 0.9rem;
            color: #1a2c3f;
            display: flex;
            align-items: center;
            gap: 10px;
            box-shadow: 0 3px 10px rgba(0,0,0,0.02);
            border: 1px solid rgba(200, 210, 225, 0.3);
        }
        .event-badge {
            width: 8px;
            height: 8px;
            border-radius: 10px;
            background: #3f7ec6;
            display: inline-block;
        }
        .event-time {
            font-size: 0.75rem;
            color: #6a7f99;
            letter-spacing: 0.03em;
            background: #f0f4fa;
            padding: 0.2rem 0.5rem;
            border-radius: 30px;
            font-weight: 500;
        }
        .no-event {
            color: #7b8a9e;
            font-style: italic;
            padding-left: 0.8rem;
            letter-spacing: 0.02em;
        }
        .add-event-hint {
            color: #1470d2;
            font-size: 0.9rem;
            margin-top: 0.5rem;
            border-top: 1px dashed #cadaef;
            padding-top: 0.7rem; 
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .add-event-hint button {
            background: #3c7bc9;
            border: none;
            color: white;
            padding: 0.5rem 1.1rem;
            border-radius: 40px;
            font-weight: 500;
            font-size: 0.8rem;
            cursor: pointer;
            transition: 0.2s;
            box-shadow: 0 4px 8px rgba(60, 123, 201, 0.2);
        }
        .add-event-hint button:hover {
            background: #2c66a8;
            transform: translateY(-1px);
        }
        .add-event-hint input {
            background: white;
            border: 1px solid #cddae9;
            border-radius: 40px;
            padding: 0.55rem 1rem;
            width: 60%;
            font-size: 0.85rem;
            outline: none;
        }
        .add-event-hint input:focus {
            border-color: #3f7ec6;
            box-shadow: 0 0 0 3px rgba(63, 126, 198, 0.15);
        }
        .toast-msg {
            font-size: 0.75rem;
            color: #a3316b;
            padding-top: 0.3rem;
            padding-left: 0.5rem;
        }
        @media (max-width: 580px) {
            .calendar-card { padding: 1.2rem; }
            .month-year .month { font-size: 1.8rem; }
            .day-cell { border-radius: 14px; }
            .nav-btn { width: 42px; height: 42px; }
        }
    </style>
</head>
<body>
    <div class="calendar-card" id="calendarApp">
        <!-- 头部导航 -->
        <div class="calendar-header">
            <div class="month-year">
                <span class="month" id="monthDisplay">October</span>
                <span class="year" id="yearDisplay">2025</span>
            </div>
            <div class="nav-buttons">
                <button class="nav-btn" id="prevBtn" aria-label="上一月">←</button>
                <button class="nav-btn" id="nextBtn" aria-label="下一月">→</button>
            </div>
        </div>
        <!-- 星期 -->
        <div class="weekdays">
            <div>周一</div><div>周二</div><div>周三</div><div>周四</div>
            <div>周五</div><div>周六</div><div>周日</div>
        </div>
        <!-- 日期网格 -->
        <div class="calendar-grid" id="calendarGrid"></div>
        <!-- 事件详情面板 -->
        <div class="event-panel">
            <div class="panel-title">
                <span id="selectedDateLabel">📌 2025年10月1日</span>
            </div>
            <div class="event-list" id="eventListContainer">
                <div class="no-event">✨ 今天还没有安排,点击日期或添加事件</div>
            </div>
            <!-- 快速添加事件行 -->
            <div class="add-event-hint">
                <input type="text" id="eventInput" placeholder="团队会议 17:00" maxlength="40" />
                <button id="addEventBtn">+ 添加</button>
            </div>
            <div id="toastMsg" class="toast-msg"></div>
        </div>
    </div>
    <script>
        (function(){
            // ---------- 核心状态 ----------
            const currentDate = new Date();            // 实际今天
            // 当前展示的年月 (显示月份)
            let displayYear = currentDate.getFullYear();
            let displayMonth = currentDate.getMonth(); // 0-11
            // 选中的日期 (默认今天)
            let selectedDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
            // 事件存储:key = 'YYYY-M-D' (本地)
            const eventStore = new Map();
            // ---------- DOM 元素 ----------
            const monthDisplay = document.getElementById('monthDisplay');
            const yearDisplay = document.getElementById('yearDisplay');
            const calendarGrid = document.getElementById('calendarGrid');
            const prevBtn = document.getElementById('prevBtn');
            const nextBtn = document.getElementById('nextBtn');
            const selectedDateLabel = document.getElementById('selectedDateLabel');
            const eventListContainer = document.getElementById('eventListContainer');
            const eventInput = document.getElementById('eventInput');
            const addEventBtn = document.getElementById('addEventBtn');
            const toastMsg = document.getElementById('toastMsg');
            // ---------- 工具函数 ----------
            const monthNames = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
            const monthNamesFull = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'];
            // 根据年月获取该月第一天是星期几(周一作为第一列)
            function getFirstDayOfMonth(year, month) {
                const day = new Date(year, month, 1).getDay(); // 0=日, 1=一... 6=六
                // 周一(1) -> 索引0, 周日(0) -> 索引6
                return day === 0 ? 6 : day - 1;
            }
            // 获取某年某月总天数
            function daysInMonth(year, month) {
                return new Date(year, month + 1, 0).getDate();
            }
            // 格式化日期为 YYYY-M-D (本地)
            function formatKey(date) {
                return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
            }
            // 判断两个日期是否为同一天
            function isSameDay(d1, d2) {
                return d1.getFullYear() === d2.getFullYear() &&
                       d1.getMonth() === d2.getMonth() &&
                       d1.getDate() === d2.getDate();
            }
            // 获取某日期的事件数组
            function getEventsForDate(date) {
                const key = formatKey(date);
                return eventStore.get(key) || [];
            }
            // ---------- 渲染日历网格 ----------
            function renderCalendar() {
                // 更新头部
                monthDisplay.textContent = monthNamesFull[displayMonth];
                yearDisplay.textContent = displayYear;
                // 获取网格信息
                const firstDayOffset = getFirstDayOfMonth(displayYear, displayMonth);
                const totalDays = daysInMonth(displayYear, displayMonth);
                // 清空网格
                calendarGrid.innerHTML = '';
                // 填充空白 (前导)
                for (let i = 0; i < firstDayOffset; i++) {
                    const emptyDiv = document.createElement('div');
                    emptyDiv.className = 'day-cell empty';
                    calendarGrid.appendChild(emptyDiv);
                }
                // 填充日期
                for (let day = 1; day <= totalDays; day++) {
                    const cellDate = new Date(displayYear, displayMonth, day);
                    const cell = document.createElement('div');
                    cell.className = 'day-cell';
                    // 今日标记
                    if (isSameDay(cellDate, currentDate)) {
                        cell.classList.add('today');
                    }
                    // 选中状态
                    if (isSameDay(cellDate, selectedDate)) {
                        cell.classList.add('selected');
                    }
                    // 日期数字
                    const dateSpan = document.createElement('span');
                    dateSpan.className = 'date-number';
                    dateSpan.textContent = day;
                    cell.appendChild(dateSpan);
                    // 事件圆点(最多显示2个)
                    const events = getEventsForDate(cellDate);
                    if (events.length > 0) {
                        const dotsContainer = document.createElement('div');
                        dotsContainer.className = 'event-dots';
                        const maxDots = Math.min(events.length, 3);
                        for (let i = 0; i < maxDots; i++) {
                            const dot = document.createElement('span');
                            dot.className = i === 2 && events.length > 3 ? 'event-dot more' : 'event-dot';
                            dotsContainer.appendChild(dot);
                        }
                        cell.appendChild(dotsContainer);
                    }
                    // 绑定点击事件
                    cell.addEventListener('click', (function(d){
                        return function() {
                            // 更新选中日期
                            selectedDate = d;
                            // 让与选中相关的高亮更新
                            updateSelectedHighlight();
                            renderEventPanel();
                        };
                    })(cellDate));
                    calendarGrid.appendChild(cell);
                }
                // 更新事件面板显示
                renderEventPanel();
            }
            // 高亮更新(移除所有选中类,重新添加)
            function updateSelectedHighlight() {
                const allCells = document.querySelectorAll('.day-cell');
                const firstDayOffset = getFirstDayOfMonth(displayYear, displayMonth);
                const totalDays = daysInMonth(displayYear, displayMonth);
                let idx = 0;
                // 匹配到每个日期格子,并重新根据选中态设置
                for (let i = 0; i < firstDayOffset; i++) {
                    idx++;
                }
                for (let day = 1; day <= totalDays; day++) {
                    const cell = allCells[firstDayOffset + day - 1];
                    const cellDate = new Date(displayYear, displayMonth, day);
                    if (isSameDay(cellDate, selectedDate)) {
                        cell.classList.add('selected');
                    } else {
                        cell.classList.remove('selected');
                    }
                }
            }
            // 渲染事件面板
            function renderEventPanel() {
                const events = getEventsForDate(selectedDate);
                const dateStr = `${selectedDate.getFullYear()}年 ${monthNames[selectedDate.getMonth()]} ${selectedDate.getDate()}日`;
                selectedDateLabel.textContent = `📌 ${dateStr}`;
                if (events.length === 0) {
                    eventListContainer.innerHTML = `<div class="no-event">✨ 这一天暂无安排,添加一个吧</div>`;
                } else {
                    let html = '';
                    events.forEach((ev, index) => {
                        // 简单拆分时间部分,让展示更友好
                        let timePart = '';
                        let titlePart = ev;
                        const parts = ev.split(' ');
                        // 简单判断:如果最后部分包含 ':' 就视为时间
                        if (parts.length > 1 && /^\d{1,2}:\d{2}$/.test(parts[parts.length-1])) {
                            timePart = parts[parts.length-1];
                            titlePart = parts.slice(0, -1).join(' ');
                        } else if (/^\d{1,2}:\d{2}$/.test(ev)) {
                            timePart = ev;
                            titlePart = '事件';
                        }
                        html += `<div class="event-item">
                            <span class="event-badge"></span>
                            <span>${titlePart}</span>
                            ${timePart ? `<span class="event-time">${timePart}</span>` : ''}
                            <span style="margin-left:auto; font-size:0.8rem; color:#9babc1; cursor:pointer;" data-index="${index}" data-key="${formatKey(selectedDate)}" class="delete-event">✕</span>
                        </div>`;
                    });
                    eventListContainer.innerHTML = html;
                    // 绑定删除事件
                    document.querySelectorAll('.delete-event').forEach(btn => {
                        btn.addEventListener('click', function(e) {
                            e.stopPropagation();
                            const idx = parseInt(this.dataset.index, 10);
                            const key = this.dataset.key;
                            const eventsArray = eventStore.get(key) || [];
                            if (idx >= 0 && idx < eventsArray.length) {
                                eventsArray.splice(idx, 1);
                                eventStore.set(key, eventsArray);
                                // 刷新日历和面板
                                renderCalendar(); 
                                renderEventPanel();
                            }
                        });
                    });
                }
            }
            // 添加事件逻辑
            function addEventFromInput() {
                const raw = eventInput.value.trim();
                if (!raw) {
                    showToast('请输入事件内容');
                    return;
                }
                const key = formatKey(selectedDate);
                const existing = eventStore.get(key) || [];
                // 防止重复或过量
                if (existing.length >= 8) {
                    showToast('最多允许8个事件/天,请删除一些');
                    return;
                }
                existing.push(raw);
                eventStore.set(key, existing);
                eventInput.value = '';
                // 重新渲染日历(更新圆点)以及面板
                renderCalendar();  // 重绘全部会保留选中状态
                renderEventPanel();
                showToast('✓ 已添加到 ' + selectedDate.getFullYear() + '/' + (selectedDate.getMonth()+1) + '/' + selectedDate.getDate());
            }
            // 提示消息
            function showToast(msg) {
                toastMsg.textContent = msg;
                toastMsg.style.opacity = 1;
                setTimeout(() => {
                    toastMsg.style.transition = 'opacity 0.3s';
                    toastMsg.style.opacity = 0;
                }, 1800);
            }
            // 月份切换
            function changeMonth(offset) {
                let newMonth = displayMonth + offset;
                let newYear = displayYear;
                if (newMonth > 11) {
                    newMonth = 0;
                    newYear++;
                } else if (newMonth < 0) {
                    newMonth = 11;
                    newYear--;
                }
                displayYear = newYear;
                displayMonth = newMonth;
                // 确保选中日期在该月中,如果不在,则设置为该月1号(体验更佳)
                if (selectedDate.getFullYear() !== displayYear || selectedDate.getMonth() !== displayMonth) {
                    selectedDate = new Date(displayYear, displayMonth, 1);
                }
                renderCalendar();
            }
            // 初始化一些示例事件 (让演示有内容)
            function seedEvents() {
                const today = new Date();
                const addEvent = (dateOffset, eventText, monthOffset = 0) => {
                    const base = new Date(today.getFullYear(), today.getMonth() + monthOffset, today.getDate() + dateOffset);
                    const key = formatKey(base);
                    if (!eventStore.has(key)) eventStore.set(key, []);
                    eventStore.get(key).push(eventText);
                };
                // 在今天附近加点事
                addEvent(0, '💻 站会 10:00');
                addEvent(1, '📝 设计评审 14:30');
                addEvent(-1, '🧘 拉伸运动 08:30');
                // 再向当前显示月填充一些演示 (如果当前不是下个月,就补一些)
                const nextMonth = new Date(today.getFullYear(), today.getMonth()+1, 3);
                const keyNext = formatKey(nextMonth);
                if (!eventStore.has(keyNext)) {
                    eventStore.set(keyNext, ['📅 里程碑规划', '☕ 咖啡约 15:00']);
                }
                const prevMonth = new Date(today.getFullYear(), today.getMonth()-1, 15);
                const keyPrev = formatKey(prevMonth);
                if (!eventStore.has(keyPrev)) {
                    eventStore.set(keyPrev, ['🎯 月度总结']);
                }
            }
            // ---------- 事件绑定 ----------
            prevBtn.addEventListener('click', () => changeMonth(-1));
            nextBtn.addEventListener('click', () => changeMonth(1));
            addEventBtn.addEventListener('click', addEventFromInput);
            eventInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') addEventFromInput();
            });
            // 初始化加载
            seedEvents();
            renderCalendar();
            // 修复外部点击删除后闪烁问题
            window.addEventListener('load', ()=>{
                renderEventPanel();
            });
        })();
    </script>
</body>
</html>

日历功能与交互

您可以轻松管理每日安排,整个界面围绕“查看”和“记录”两个核心操作设计。

日历案例

  • 日期导航与选择:点击顶部的左右箭头按钮可以切换月份,日历网格中,今天和当前选中的日期会有高亮标记,点击任意日期即可快速选中。
  • 事件添加与管理:在底部面板的输入框中输入事件描述(可包含时间,如“团队会议 17:00”),点击“添加”按钮即可将事件绑定到选中日期,已添加的事件会以圆点形式显示在日历上,并在面板中列出,您可以通过点击事件旁的“✕”按钮删除。
  • 数据持久化:示例数据会在页面加载时自动创建,您添加的事件会保存在当前页面的内存中,刷新页面后会自动重置为示例数据,方便您反复测试。

抱歉,评论功能暂时关闭!